|
@@ -39,172 +39,49 @@ import com.jme3.export.Savable;
|
|
import com.jme3.math.Matrix3f;
|
|
import com.jme3.math.Matrix3f;
|
|
import com.jme3.math.Matrix4f;
|
|
import com.jme3.math.Matrix4f;
|
|
import com.jme3.math.Quaternion;
|
|
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.Geometry;
|
|
-import com.jme3.scene.Mesh;
|
|
|
|
import com.jme3.scene.Spatial;
|
|
import com.jme3.scene.Spatial;
|
|
import com.jme3.scene.VertexBuffer;
|
|
import com.jme3.scene.VertexBuffer;
|
|
import com.jme3.scene.VertexBuffer.Format;
|
|
import com.jme3.scene.VertexBuffer.Format;
|
|
import com.jme3.scene.VertexBuffer.Type;
|
|
import com.jme3.scene.VertexBuffer.Type;
|
|
import com.jme3.scene.VertexBuffer.Usage;
|
|
import com.jme3.scene.VertexBuffer.Usage;
|
|
-import com.jme3.scene.control.AbstractControl;
|
|
|
|
import com.jme3.util.BufferUtils;
|
|
import com.jme3.util.BufferUtils;
|
|
|
|
+import com.jme3.util.TempVars;
|
|
import java.io.IOException;
|
|
import java.io.IOException;
|
|
import java.nio.FloatBuffer;
|
|
import java.nio.FloatBuffer;
|
|
import java.util.ArrayList;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Arrays;
|
|
-import java.util.Collections;
|
|
|
|
-import java.util.HashMap;
|
|
|
|
-import java.util.Map;
|
|
|
|
|
|
|
|
-/**
|
|
|
|
- * <code>InstancedGeometry</code> allows rendering many similar
|
|
|
|
- * geometries efficiently through a feature called geometry
|
|
|
|
- * instancing.
|
|
|
|
- *
|
|
|
|
- * <p>
|
|
|
|
- * 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.
|
|
|
|
- * </p>
|
|
|
|
- *
|
|
|
|
- * <p>
|
|
|
|
- * 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
|
|
|
|
- * <code>gl_InstanceID</code>. At the very least, they should use the
|
|
|
|
- * functions specified in <code>Instancing.glsllib</code> shader library
|
|
|
|
- * to transform vertex positions and normals instead of multiplying by the
|
|
|
|
- * built-in matrix uniforms.
|
|
|
|
- * </p>
|
|
|
|
- *
|
|
|
|
- * <p>
|
|
|
|
- * This class can operate in two modes, {@link InstancedGeometry.Mode#Auto}
|
|
|
|
- * and {@link InstancedGeometry.Mode#Manual}. See the respective enums
|
|
|
|
- * for more information</p>
|
|
|
|
- *
|
|
|
|
- * <p>
|
|
|
|
- * 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.
|
|
|
|
- * </p>
|
|
|
|
- *
|
|
|
|
- * @author Kirill Vainer
|
|
|
|
- */
|
|
|
|
public class InstancedGeometry extends Geometry {
|
|
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 <code>Instancing.glsllib</code> 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 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[] globalInstanceData;
|
|
|
|
+ private VertexBuffer transformInstanceData;
|
|
|
|
+ private Geometry[] geometries = new Geometry[1];
|
|
|
|
|
|
- private final HashMap<Camera, VertexBuffer> instanceDataPerCam
|
|
|
|
- = new HashMap<Camera, VertexBuffer>();
|
|
|
|
-
|
|
|
|
- // 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.
|
|
* Serialization only. Do not use.
|
|
*/
|
|
*/
|
|
public InstancedGeometry() {
|
|
public InstancedGeometry() {
|
|
super();
|
|
super();
|
|
setIgnoreTransform(true);
|
|
setIgnoreTransform(true);
|
|
|
|
+ setMaxNumInstances(1);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
* Creates instanced geometry with the specified mode and name.
|
|
* 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.
|
|
* @param name The name of the spatial.
|
|
*
|
|
*
|
|
- * @see Mode
|
|
|
|
* @see Spatial#Spatial(java.lang.String)
|
|
* @see Spatial#Spatial(java.lang.String)
|
|
*/
|
|
*/
|
|
- public InstancedGeometry(InstancedGeometry.Mode mode, String name) {
|
|
|
|
|
|
+ public InstancedGeometry(String name) {
|
|
super(name);
|
|
super(name);
|
|
- this.mode = mode;
|
|
|
|
setIgnoreTransform(true);
|
|
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.
|
|
* 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 <code>null</code> 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<Camera, VertexBuffer> 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();
|
|
tempMat3.invertLocal();
|
|
-
|
|
|
|
|
|
+
|
|
// NOTE: No need to take the transpose in order to encode
|
|
// NOTE: No need to take the transpose in order to encode
|
|
// into quaternion, the multiplication in the shader is vec * quat
|
|
// into quaternion, the multiplication in the shader is vec * quat
|
|
// apparently...
|
|
// apparently...
|
|
tempQuat.fromRotationMatrix(tempMat3);
|
|
tempQuat.fromRotationMatrix(tempMat3);
|
|
-
|
|
|
|
|
|
+
|
|
// Column-major encoding. The "W" field in each of the encoded
|
|
// Column-major encoding. The "W" field in each of the encoded
|
|
// vectors represents the quaternion.
|
|
// 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 + 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 + 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 + 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();
|
|
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
|
|
* Set the maximum amount of instances that can be rendered by this
|
|
* instanced geometry when mode is set to auto.
|
|
* 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 IllegalStateException If mode is set to manual.
|
|
* @throws IllegalArgumentException If maxNumInstances is zero or negative
|
|
* @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) {
|
|
if (maxNumInstances < 1) {
|
|
throw new IllegalArgumentException("maxNumInstances must be 1 or higher");
|
|
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() {
|
|
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() {
|
|
public VertexBuffer[] getAllInstanceData() {
|
|
- VertexBuffer instanceDataForCam = instanceDataPerCam.get(lastCamera);
|
|
|
|
ArrayList<VertexBuffer> allData = new ArrayList();
|
|
ArrayList<VertexBuffer> allData = new ArrayList();
|
|
-
|
|
|
|
- if (instanceDataForCam != null) {
|
|
|
|
- allData.add(instanceDataForCam);
|
|
|
|
|
|
+ if (transformInstanceData != null) {
|
|
|
|
+ allData.add(transformInstanceData);
|
|
}
|
|
}
|
|
if (globalInstanceData != null) {
|
|
if (globalInstanceData != null) {
|
|
allData.addAll(Arrays.asList(globalInstanceData));
|
|
allData.addAll(Arrays.asList(globalInstanceData));
|
|
}
|
|
}
|
|
-
|
|
|
|
return allData.toArray(new VertexBuffer[allData.size()]);
|
|
return allData.toArray(new VertexBuffer[allData.size()]);
|
|
}
|
|
}
|
|
|
|
|
|
@@ -504,30 +341,20 @@ public class InstancedGeometry extends Geometry {
|
|
public void write(JmeExporter exporter) throws IOException {
|
|
public void write(JmeExporter exporter) throws IOException {
|
|
super.write(exporter);
|
|
super.write(exporter);
|
|
OutputCapsule capsule = exporter.getCapsule(this);
|
|
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
|
|
@Override
|
|
public void read(JmeImporter importer) throws IOException {
|
|
public void read(JmeImporter importer) throws IOException {
|
|
super.read(importer);
|
|
super.read(importer);
|
|
InputCapsule capsule = importer.getCapsule(this);
|
|
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];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|