abies 11 years ago
commit bd69385571
  1. 1
      jme3-android/src/main/java/com/jme3/renderer/android/TextureUtil.java
  2. 4
      jme3-android/src/main/java/com/jme3/texture/plugins/AndroidImageLoader.java
  3. 3
      jme3-android/src/main/java/com/jme3/texture/plugins/AndroidNativeImageLoader.java
  4. 244
      jme3-bullet/src/common/java/com/jme3/bullet/control/KinematicRagdollControl.java
  5. 13
      jme3-core/src/main/java/com/jme3/animation/Bone.java
  6. 23
      jme3-core/src/main/java/com/jme3/cinematic/events/AnimationEvent.java
  7. 9
      jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java
  8. 6
      jme3-core/src/main/java/com/jme3/material/Material.java
  9. 8
      jme3-core/src/main/java/com/jme3/math/ColorRGBA.java
  10. 2
      jme3-core/src/main/java/com/jme3/renderer/RenderManager.java
  11. 2
      jme3-core/src/main/java/com/jme3/renderer/Statistics.java
  12. 8
      jme3-core/src/main/java/com/jme3/scene/BatchNode.java
  13. 16
      jme3-core/src/main/java/com/jme3/scene/Geometry.java
  14. 14
      jme3-core/src/main/java/com/jme3/scene/GeometryGroupNode.java
  15. 26
      jme3-core/src/main/java/com/jme3/scene/Mesh.java
  16. 76
      jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java
  17. 515
      jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java
  18. 335
      jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java
  19. 25
      jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md
  20. 22
      jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert
  21. 2
      jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.j3md
  22. 18
      jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md
  23. 1
      jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.j3md
  24. 6
      jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert
  25. 7
      jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.vert
  26. 6
      jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadow.vert
  27. 23
      jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib
  28. 6
      jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.vert
  29. 93
      jme3-examples/src/main/java/jme3test/bullet/TestIK.java
  30. 192
      jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNode.java
  31. 146
      jme3-examples/src/main/java/jme3test/scene/instancing/TestInstancing.java
  32. 15
      jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java

@ -6,7 +6,6 @@ import android.opengl.ETC1Util.ETC1Texture;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import com.jme3.asset.AndroidImageInfo;
import com.jme3.math.FastMath;
import com.jme3.renderer.RendererException;
import com.jme3.texture.Image;
import com.jme3.texture.Image.Format;

@ -5,6 +5,7 @@ import com.jme3.asset.AndroidImageInfo;
import com.jme3.asset.AssetInfo;
import com.jme3.asset.AssetLoader;
import com.jme3.texture.Image;
import com.jme3.texture.image.ColorSpace;
import java.io.IOException;
public class AndroidImageLoader implements AssetLoader {
@ -13,7 +14,8 @@ public class AndroidImageLoader implements AssetLoader {
AndroidImageInfo imageInfo = new AndroidImageInfo(info);
Bitmap bitmap = imageInfo.getBitmap();
Image image = new Image(imageInfo.getFormat(), bitmap.getWidth(), bitmap.getHeight(), null);
Image image = new Image(imageInfo.getFormat(), bitmap.getWidth(), bitmap.getHeight(), null, ColorSpace.sRGB);
image.setEfficentData(imageInfo);
return image;
}

@ -5,6 +5,7 @@ import com.jme3.asset.AssetLoadException;
import com.jme3.asset.AssetLoader;
import com.jme3.asset.TextureKey;
import com.jme3.texture.Image;
import com.jme3.texture.image.ColorSpace;
import com.jme3.util.BufferUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@ -90,7 +91,7 @@ public class AndroidNativeImageLoader implements AssetLoader {
BufferUtils.destroyDirectBuffer(origDataBuffer);
BufferUtils.destroyDirectBuffer(headerDataBuffer);
Image img = new Image(getImageFormat(numComponents), width, height, imageDataBuffer);
Image img = new Image(getImageFormat(numComponents), width, height, imageDataBuffer, ColorSpace.sRGB);
return img;
}

@ -52,6 +52,7 @@ import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.export.Savable;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
@ -113,11 +114,17 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P
protected float eventDispatchImpulseThreshold = 10;
protected float rootMass = 15;
protected float totalMass = 0;
private Map<String, Vector3f> ikTargets = new HashMap<String, Vector3f>();
private Map<String, Integer> ikChainDepth = new HashMap<String, Integer>();
private float ikRotSpeed = 7f;
private float limbDampening = 0.6f;
private float IKThreshold = 0.1f;
public static enum Mode {
Kinematic,
Ragdoll
Ragdoll,
IK
}
public class PhysicsBoneLink implements Savable {
@ -189,9 +196,10 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P
if (!enabled) {
return;
}
//if the ragdoll has the control of the skeleton, we update each bone with its position in physic world space.
if (mode == mode.Ragdoll && targetModel.getLocalTranslation().equals(modelPosition)) {
if(mode == Mode.IK){
ikUpdate(tpf);
} else if (mode == mode.Ragdoll && targetModel.getLocalTranslation().equals(modelPosition)) {
//if the ragdoll has the control of the skeleton, we update each bone with its position in physic world space.
ragDollUpdate(tpf);
} else {
kinematicUpdate(tpf);
@ -260,6 +268,9 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P
Quaternion tmpRot2 = vars.quat2;
Vector3f position = vars.vect1;
for (PhysicsBoneLink link : boneLinks.values()) {
// if(link.usedbyIK){
// continue;
// }
//if blended control this means, keyframed animation is updating the skeleton,
//but to allow smooth transition, we blend this transformation with the saved position of the ragdoll
if (blendedControl) {
@ -300,6 +311,94 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P
}
vars.release();
}
private void ikUpdate(float tpf){
TempVars vars = TempVars.get();
Quaternion tmpRot1 = vars.quat1;
Quaternion[] tmpRot2 = new Quaternion[]{vars.quat2, new Quaternion()};
Iterator<String> it = ikTargets.keySet().iterator();
float distance;
Bone bone;
String boneName;
while (it.hasNext()) {
boneName = it.next();
bone = (Bone) boneLinks.get(boneName).bone;
if (!bone.hasUserControl()) {
Logger.getLogger(KinematicRagdollControl.class.getSimpleName()).log(Level.FINE, "{0} doesn't have user control", boneName);
continue;
}
distance = bone.getModelSpacePosition().distance(ikTargets.get(boneName));
if (distance < IKThreshold) {
Logger.getLogger(KinematicRagdollControl.class.getSimpleName()).log(Level.FINE, "Distance is close enough");
continue;
}
int depth = 0;
int maxDepth = ikChainDepth.get(bone.getName());
updateBone(boneLinks.get(bone.getName()), tpf * (float) FastMath.sqrt(distance), vars, tmpRot1, tmpRot2, bone, ikTargets.get(boneName), depth, maxDepth);
Vector3f position = vars.vect1;
for (PhysicsBoneLink link : boneLinks.values()) {
matchPhysicObjectToBone(link, position, tmpRot1);
}
}
vars.release();
}
public void updateBone(PhysicsBoneLink link, float tpf, TempVars vars, Quaternion tmpRot1, Quaternion[] tmpRot2, Bone tipBone, Vector3f target, int depth, int maxDepth) {
if (link == null || link.bone.getParent() == null) {
return;
}
Quaternion preQuat = link.bone.getLocalRotation();
Vector3f vectorAxis;
float[] measureDist = new float[]{Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY};
for (int dirIndex = 0; dirIndex < 3; dirIndex++) {
if (dirIndex == 0) {
vectorAxis = Vector3f.UNIT_Z;
} else if (dirIndex == 1) {
vectorAxis = Vector3f.UNIT_X;
} else {
vectorAxis = Vector3f.UNIT_Y;
}
for (int posOrNeg = 0; posOrNeg < 2; posOrNeg++) {
float rot = ikRotSpeed * tpf / (link.rigidBody.getMass() * 2);
rot = FastMath.clamp(rot, link.joint.getRotationalLimitMotor(dirIndex).getLoLimit(), link.joint.getRotationalLimitMotor(dirIndex).getHiLimit());
tmpRot1.fromAngleAxis(rot, vectorAxis);
// tmpRot1.fromAngleAxis(rotSpeed * tpf / (link.rigidBody.getMass() * 2), vectorAxis);
tmpRot2[posOrNeg] = link.bone.getLocalRotation().mult(tmpRot1);
tmpRot2[posOrNeg].normalizeLocal();
ikRotSpeed = -ikRotSpeed;
link.bone.setLocalRotation(tmpRot2[posOrNeg]);
link.bone.update();
measureDist[posOrNeg] = tipBone.getModelSpacePosition().distance(target);
link.bone.setLocalRotation(preQuat);
}
if (measureDist[0] < measureDist[1]) {
link.bone.setLocalRotation(tmpRot2[0]);
} else if (measureDist[0] > measureDist[1]) {
link.bone.setLocalRotation(tmpRot2[1]);
}
}
link.bone.getLocalRotation().normalizeLocal();
link.bone.update();
// link.usedbyIK = true;
if (link.bone.getParent() != null && depth < maxDepth) {
updateBone(boneLinks.get(link.bone.getParent().getName()), tpf * limbDampening, vars, tmpRot1, tmpRot2, tipBone, target, depth + 1, maxDepth);
}
}
/**
* Set the transforms of a rigidBody to match the transforms of a bone. this
@ -618,23 +717,28 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P
animControl.setEnabled(mode == Mode.Kinematic);
baseRigidBody.setKinematic(mode == Mode.Kinematic);
TempVars vars = TempVars.get();
for (PhysicsBoneLink link : boneLinks.values()) {
link.rigidBody.setKinematic(mode == Mode.Kinematic);
if (mode == Mode.Ragdoll) {
Quaternion tmpRot1 = vars.quat1;
Vector3f position = vars.vect1;
//making sure that the ragdoll is at the correct place.
matchPhysicObjectToBone(link, position, tmpRot1);
if (mode != Mode.IK) {
TempVars vars = TempVars.get();
for (PhysicsBoneLink link : boneLinks.values()) {
link.rigidBody.setKinematic(mode == Mode.Kinematic);
if (mode == Mode.Ragdoll) {
Quaternion tmpRot1 = vars.quat1;
Vector3f position = vars.vect1;
//making sure that the ragdoll is at the correct place.
matchPhysicObjectToBone(link, position, tmpRot1);
}
}
vars.release();
}
if(mode != Mode.IK){
for (Bone bone : skeleton.getRoots()) {
RagdollUtils.setUserControl(bone, mode == Mode.Ragdoll);
}
}
vars.release();
for (Bone bone : skeleton.getRoots()) {
RagdollUtils.setUserControl(bone, mode == Mode.Ragdoll);
}
}
/**
@ -703,6 +807,16 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P
}
}
/**
* Sets the control into Inverse Kinematics mode. The affected bones are affected by IK.
* physics.
*/
public void setIKMode() {
if (mode != Mode.IK) {
setMode(Mode.IK);
}
}
/**
* retruns the mode of this control
*
@ -804,7 +918,97 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P
control.setApplyPhysicsLocal(applyLocal);
return control;
}
public Vector3f setIKTarget(Bone bone, Vector3f worldPos, int chainLength) {
Vector3f target = worldPos.subtract(targetModel.getWorldTranslation());
ikTargets.put(bone.getName(), target);
ikChainDepth.put(bone.getName(), chainLength);
int i = 0;
while (i < chainLength+2 && bone.getParent() != null) {
if (!bone.hasUserControl()) {
bone.setUserControl(true);
}
bone = bone.getParent();
i++;
}
// setIKMode();
return target;
}
public void removeIKTarget(Bone bone) {
int depth = ikChainDepth.remove(bone.getName());
int i = 0;
while (i < depth+2 && bone.getParent() != null) {
if (bone.hasUserControl()) {
// matchPhysicObjectToBone(boneLinks.get(bone.getName()), position, tmpRot1);
bone.setUserControl(false);
}
bone = bone.getParent();
i++;
}
}
public void removeAllIKTargets(){
ikTargets.clear();
ikChainDepth.clear();
applyUserControl();
}
public void applyUserControl() {
for (Bone bone : skeleton.getRoots()) {
RagdollUtils.setUserControl(bone, false);
}
if (ikTargets.isEmpty()) {
setKinematicMode();
} else {
Iterator iterator = ikTargets.keySet().iterator();
TempVars vars = TempVars.get();
while (iterator.hasNext()) {
Bone bone = (Bone) iterator.next();
while (bone.getParent() != null) {
Quaternion tmpRot1 = vars.quat1;
Vector3f position = vars.vect1;
matchPhysicObjectToBone(boneLinks.get(bone.getName()), position, tmpRot1);
bone.setUserControl(true);
bone = bone.getParent();
}
}
vars.release();
}
}
public float getIkRotSpeed() {
return ikRotSpeed;
}
public void setIkRotSpeed(float ikRotSpeed) {
this.ikRotSpeed = ikRotSpeed;
}
public float getIKThreshold() {
return IKThreshold;
}
public void setIKThreshold(float IKThreshold) {
this.IKThreshold = IKThreshold;
}
public float getLimbDampening() {
return limbDampening;
}
public void setLimbDampening(float limbDampening) {
this.limbDampening = limbDampening;
}
public Bone getBone(String name){
return skeleton.getBone(name);
}
/**
* serialize this control
*
@ -831,6 +1035,8 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P
oc.write(eventDispatchImpulseThreshold, "eventDispatchImpulseThreshold", 10);
oc.write(rootMass, "rootMass", 15);
oc.write(totalMass, "totalMass", 0);
oc.write(ikRotSpeed, "rotSpeed", 7f);
oc.write(limbDampening, "limbDampening", 0.6f);
}
/**

@ -459,7 +459,7 @@ public final class Bone implements Savable {
/**
* Updates world transforms for this bone and it's children.
*/
final void update() {
public final void update() {
this.updateModelTransforms();
for (int i = children.size() - 1; i >= 0; i--) {
@ -796,4 +796,15 @@ public final class Bone implements Savable {
output.write(bindScale, "bindScale", new Vector3f(1.0f, 1.0f, 1.0f));
output.writeSavableArrayList(children, "children", null);
}
public void setLocalRotation(Quaternion rot){
if (!userControl) {
throw new IllegalStateException("User control must be on bone to allow user transforms");
}
this.localRot = rot;
}
public boolean hasUserControl(){
return userControl;
}
}

@ -221,6 +221,24 @@ public class AnimationEvent extends AbstractCinematicEvent {
initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName);
this.channelIndex = channelIndex;
}
/**
* creates an animation event
*
* @param model the model on which the animation will be played
* @param animationName the name of the animation to play
* @param channelIndex the index of the channel default is 0. Events on the
* @param blendTime the time during the animation are gonna be blended
* same channelIndex will use the same channel.
*/
public AnimationEvent(Spatial model, String animationName, LoopMode loopMode, int channelIndex, float blendTime) {
this.model = model;
this.animationName = animationName;
this.loopMode = loopMode;
initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName);
this.channelIndex = channelIndex;
this.blendTime = blendTime;
}
/**
* creates an animation event
@ -264,6 +282,10 @@ public class AnimationEvent extends AbstractCinematicEvent {
Object s = cinematic.getEventData(MODEL_CHANNELS, model);
if (s == null) {
s = new HashMap<Integer, AnimChannel>();
int numChannels = model.getControl(AnimControl.class).getNumChannels();
for(int i = 0; i < numChannels; i++){
((HashMap<Integer, AnimChannel>)s).put(i, model.getControl(AnimControl.class).getChannel(i));
}
cinematic.putEventData(MODEL_CHANNELS, model, s);
}
@ -319,6 +341,7 @@ public class AnimationEvent extends AbstractCinematicEvent {
channel.setTime(t);
channel.getControl().update(0);
}
}
@Override

@ -167,11 +167,12 @@ public class ParticleEmitter extends Geometry {
clone.endColor = endColor.clone();
clone.particleInfluencer = particleInfluencer.clone();
// remove wrong control
clone.controls.remove(control);
// remove original control from the clone
clone.controls.remove(this.control);
// put correct control
clone.controls.add(new ParticleEmitterControl(clone));
// put clone's control in
clone.control = new ParticleEmitterControl(clone);
clone.controls.add(clone.control);
// Reinitialize particle mesh
switch (meshType) {

@ -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);
}

@ -206,10 +206,10 @@ public final class ColorRGBA implements Savable, Cloneable, java.io.Serializable
* Saturate that color ensuring all channels have a value between 0 and 1
*/
public void clamp() {
FastMath.clamp(r, 0f, 1f);
FastMath.clamp(g, 0f, 1f);
FastMath.clamp(b, 0f, 1f);
FastMath.clamp(a, 0f, 1f);
r = FastMath.clamp(r, 0f, 1f);
g = FastMath.clamp(g, 0f, 1f);
b = FastMath.clamp(b, 0f, 1f);
a = FastMath.clamp(a, 0f, 1f);
}
/**

@ -575,7 +575,7 @@ public class RenderManager {
Geometry gm = (Geometry) s;
RenderQueue.ShadowMode shadowMode = s.getShadowMode();
if (shadowMode != RenderQueue.ShadowMode.Off && shadowMode != RenderQueue.ShadowMode.Receive) {
if (shadowMode != RenderQueue.ShadowMode.Off && shadowMode != RenderQueue.ShadowMode.Receive && !gm.isGrouped()) {
//forcing adding to shadow cast mode, culled objects doesn't have to be in the receiver queue
rq.addToShadowQueue(gm, RenderQueue.ShadowMode.Cast);
}

@ -125,7 +125,7 @@ public class Statistics {
if( !enabled )
return;
numObjects += count;
numObjects += 1;
numTriangles += mesh.getTriangleCount(lod) * count;
numVertices += mesh.getVertexCount() * count;
}

@ -773,10 +773,6 @@ public class BatchNode extends GeometryGroupNode implements Savable {
this.needsFullRebatch = needsFullRebatch;
}
public int getOffsetIndex(Geometry batchedGeometry) {
return batchedGeometry.startIndex;
}
@Override
public Node clone(boolean cloneMaterials) {
BatchNode clone = (BatchNode)super.clone(cloneMaterials);
@ -790,8 +786,8 @@ public class BatchNode extends GeometryGroupNode implements Savable {
}
}
clone.needsFullRebatch = true;
clone.batches.clear();
clone.batchesByGeom.clear();
clone.batches = new SafeArrayList<Batch>(Batch.class);
clone.batchesByGeom = new HashMap<Geometry, Batch>();
clone.batch();
}
return clone;

@ -81,7 +81,7 @@ public class Geometry extends Spatial {
* The start index of this <code>Geometry's</code> inside
* the {@link GeometryGroupNode}.
*/
protected int startIndex;
protected int startIndex = -1;
/**
* Serialization only. Do not use.
*/
@ -316,7 +316,7 @@ public class Geometry extends Spatial {
* @param node Which {@link GeometryGroupNode} to associate with.
* @param startIndex The starting index of this geometry in the group.
*/
protected void associateWithGroupNode(GeometryGroupNode node, int startIndex) {
public void associateWithGroupNode(GeometryGroupNode node, int startIndex) {
if (isGrouped()) {
unassociateFromGroupNode();
}
@ -331,13 +331,15 @@ public class Geometry extends Spatial {
*
* Should only be called by the parent {@link GeometryGroupNode}.
*/
protected void unassociateFromGroupNode() {
public void unassociateFromGroupNode() {
if (groupNode != null) {
// Once the geometry is removed
// from the parent, the group node needs to be updated.
groupNode.onGeoemtryUnassociated(this);
groupNode = null;
startIndex = 0;
// change the default to -1 to make error detection easier
startIndex = -1;
}
}
@ -484,9 +486,9 @@ public class Geometry extends Spatial {
// This geometry is managed,
// but the cloned one is not attached to anything, hence not managed.
if (isGrouped()) {
groupNode = null;
startIndex = 0;
if (geomClone.isGrouped()) {
geomClone.groupNode = null;
geomClone.startIndex = -1;
}
geomClone.cachedWorldMat = cachedWorldMat.clone();

@ -8,6 +8,20 @@ package com.jme3.scene;
*/
public abstract class GeometryGroupNode extends Node {
protected static int getGeometryStartIndex(Geometry geom) {
if (geom.startIndex == -1) {
throw new AssertionError();
}
return geom.startIndex;
}
protected static void setGeometryStartIndex(Geometry geom, int startIndex) {
if (startIndex < -1) {
throw new AssertionError();
}
geom.startIndex = startIndex;
}
/**
* Construct a <code>GeometryGroupNode</code>
*/

@ -174,6 +174,7 @@ public class Mesh implements Savable, Cloneable {
private int vertCount = -1;
private int elementCount = -1;
private int instanceCount = -1;
private int maxNumWeights = -1; // only if using skeletal animation
private int[] elementLengths;
@ -242,6 +243,7 @@ public class Mesh implements Savable, Cloneable {
clone.vertexArrayID = -1;
clone.vertCount = vertCount;
clone.elementCount = elementCount;
clone.instanceCount = instanceCount;
// although this could change
// if the bone weight/index buffers are modified
@ -718,6 +720,17 @@ public class Mesh implements Savable, Cloneable {
}
}
private int computeInstanceCount() {
// Whatever the max of the base instance counts
int max = 0;
for( VertexBuffer vb : buffersList ) {
if( vb.getBaseInstanceCount() > max ) {
max = vb.getBaseInstanceCount();
}
}
return max;
}
/**
* Update the {@link #getVertexCount() vertex} and
* {@link #getTriangleCount() triangle} counts for this mesh
@ -741,7 +754,8 @@ public class Mesh implements Savable, Cloneable {
elementCount = computeNumElements(ib.getData().limit());
}else{
elementCount = computeNumElements(vertCount);
}
}
instanceCount = computeInstanceCount();
}
/**
@ -789,6 +803,14 @@ public class Mesh implements Savable, Cloneable {
public int getVertexCount(){
return vertCount;
}
/**
* Returns the number of instances this mesh contains. The instance
* count is based on any VertexBuffers with instancing set.
*/
public int getInstanceCount() {
return instanceCount;
}
/**
* Gets the triangle vertex positions at the given triangle index
@ -1333,6 +1355,7 @@ public class Mesh implements Savable, Cloneable {
out.write(meshBound, "modelBound", null);
out.write(vertCount, "vertCount", -1);
out.write(elementCount, "elementCount", -1);
out.write(instanceCount, "instanceCount", -1);
out.write(maxNumWeights, "max_num_weights", -1);
out.write(mode, "mode", Mode.Triangles);
out.write(collisionTree, "collisionTree", null);
@ -1370,6 +1393,7 @@ public class Mesh implements Savable, Cloneable {
meshBound = (BoundingVolume) in.readSavable("modelBound", null);
vertCount = in.readInt("vertCount", -1);
elementCount = in.readInt("elementCount", -1);
instanceCount = in.readInt("instanceCount", -1);
maxNumWeights = in.readInt("max_num_weights", -1);
mode = in.readEnum("mode", Mode.class, Mode.Triangles);
elementLengths = in.readIntArray("elementLengths", null);

@ -84,12 +84,12 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
*/
Color,
/**
* Tangent vector, normalized (4 floats) (x,y,z,w). The w component is
* called the binormal parity, is not normalized, and is either 1f or
* -1f. It's used to compute the direction on the binormal vector on the
* GPU at render time.
*/
/**
* Tangent vector, normalized (4 floats) (x,y,z,w). The w component is
* called the binormal parity, is not normalized, and is either 1f or
* -1f. It's used to compute the direction on the binormal vector on the
* GPU at render time.
*/
Tangent,
/**
@ -208,12 +208,12 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
HWBoneIndex,
/**
* Information about this instance.
* Information about this instance.
*
* Format should be {@link Format#Float} and number of components
* should be 16.
*/
InstanceData
* Format should be {@link Format#Float} and number of components
* should be 16.
*/
InstanceData
}
/**
@ -333,7 +333,7 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
protected Type bufType;
protected Format format;
protected boolean normalized = false;
protected transient boolean instanced = false;
protected int instanceSpan = 0;
protected transient boolean dataSizeChanged = false;
/**
@ -545,14 +545,39 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
}
/**
* TODO:
* Sets the instanceSpan to 1 or 0 depending on
* the value of instanced and the existing value of
* instanceSpan.
*/
public void setInstanced(boolean instanced) {
this.instanced = instanced;
if( instanced && instanceSpan == 0 ) {
instanceSpan = 1;
} else if( !instanced ) {
instanceSpan = 0;
}
}
/**
* Returns true if instanceSpan is more than 0 indicating
* that this vertex buffer contains per-instance data.
*/
public boolean isInstanced() {
return instanced;
return instanceSpan > 0;
}
/**
* Sets how this vertex buffer matches with rendered instances
* where 0 means no instancing at all, ie: all elements are
* per vertex. If set to 1 then each element goes with one
* instance. If set to 2 then each element goes with two
* instances and so on.
*/
public void setInstanceSpan(int i) {
this.instanceSpan = i;
}
public int getInstanceSpan() {
return instanceSpan;
}
/**
@ -587,6 +612,20 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
return elements;
}
/**
* Returns the number of 'instances' in this VertexBuffer. This
* is dependent on the current instanceSpan. When instanceSpan
* is 0 then 'instances' is 1. Otherwise, instances is elements *
* instanceSpan. It is possible to render a mesh with more instances
* but the instance data begins to repeat.
*/
public int getBaseInstanceCount() {
if( instanceSpan == 0 ) {
return 1;
}
return getNumElements() * instanceSpan;
}
/**
* Called to initialize the data in the <code>VertexBuffer</code>. Must only
* be called once.
@ -1009,7 +1048,7 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
vb.handleRef = new Object();
vb.id = -1;
vb.normalized = normalized;
vb.instanced = instanced;
vb.instanceSpan = instanceSpan;
vb.offset = offset;
vb.stride = stride;
vb.updateNeeded = true;
@ -1060,9 +1099,6 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
@Override
public void write(JmeExporter ex) throws IOException {
if (instanced) {
throw new IOException("Serialization of instanced data not allowed");
}
OutputCapsule oc = ex.getCapsule(this);
oc.write(components, "components", 0);
@ -1072,6 +1108,7 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
oc.write(normalized, "normalized", false);
oc.write(offset, "offset", 0);
oc.write(stride, "stride", 0);
oc.write(instanceSpan, "instanceSpan", 0);
String dataName = "data" + format.name();
Buffer roData = getDataReadOnly();
@ -1107,6 +1144,7 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
normalized = ic.readBoolean("normalized", false);
offset = ic.readInt("offset", 0);
stride = ic.readInt("stride", 0);
instanceSpan = ic.readInt("instanceSpan", 0);
componentsLength = components * format.getComponentSize();
String dataName = "data" + format.name();

@ -39,172 +39,51 @@ 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;
/**
* <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 {
/**
* 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 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<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.
*/
public InstancedGeometry() {
super();
setIgnoreTransform(true);
setBatchHint(BatchHint.Never);
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;
setBatchHint(BatchHint.Never);
setMaxNumInstances(1);
}
/**
@ -238,170 +117,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 <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 user per-instance transform data.
*
* @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}.
*
* @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();
// 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 +178,168 @@ 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();
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++;
setInstanceTransform(instanceIndex, tempMat4.clone());
geometries[freeIndex] = geometry;
InstancedNode.setGeometryStartIndex2(geometry, freeIndex);
}
public Geometry[] getGeometries() {
return geometries;
}
public VertexBuffer[] getAllInstanceData() {
VertexBuffer instanceDataForCam = instanceDataPerCam.get(lastCamera);
ArrayList<VertexBuffer> 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 +347,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];
}
}
}

@ -0,0 +1,335 @@
/*
* Copyright (c) 2014 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.instancing;
import com.jme3.material.Material;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
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.Control;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.material.MatParam;
import java.io.IOException;
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 implements Control {
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.
}
public void setSpatial(Spatial spatial){
}
public void update(float tpf){
}
public void render(RenderManager rm, ViewPort vp) {
node.renderFromControl();
}
public void write(JmeExporter ex) throws IOException {
}
public void read(JmeImporter im) throws IOException {
}
}
protected InstancedNodeControl control;
protected HashMap<Geometry, InstancedGeometry> igByGeom
= new HashMap<Geometry, InstancedGeometry>();
private InstanceTypeKey lookUp = new InstanceTypeKey();
private HashMap<InstanceTypeKey, InstancedGeometry> instancesMap =
new HashMap<InstanceTypeKey, InstancedGeometry>();
public InstancedNode() {
super();
// NOTE: since we are deserializing,
// the control is going to be added automatically here.
}
public InstancedNode(String name) {
super(name);
control = new InstancedNodeControl(this);
addControl(control);
}
private void renderFromControl() {
for (InstancedGeometry ig : instancesMap.values()) {
ig.updateInstances();
}
}
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(
"mesh-" + System.identityHashCode(lookUp.mesh) + "," +
"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 addToInstancedGeometry(Geometry geom) {
Material material = geom.getMaterial();
MatParam param = material.getParam("UseInstancing");
if (param == null || !((Boolean)param.getValue()).booleanValue()) {
throw new IllegalStateException("You must set the 'UseInstancing' "
+ "parameter to true on the material prior "
+ "to adding it to InstancedNode");
}
InstancedGeometry ig = lookUpByGeometry(geom);
igByGeom.put(geom, ig);
geom.associateWithGroupNode(this, 0);
ig.addInstance(geom);
}
private void removeFromInstancedGeometry(Geometry geom) {
InstancedGeometry ig = igByGeom.remove(geom);
if (ig != null) {
ig.deleteInstance(geom);
}
}
private void relocateInInstancedGeometry(Geometry geom) {
InstancedGeometry oldIG = igByGeom.get(geom);
InstancedGeometry newIG = lookUpByGeometry(geom);
if (oldIG != newIG) {
if (oldIG == null) {
throw new AssertionError();
}
oldIG.deleteInstance(geom);
newIG.addInstance(geom);
igByGeom.put(geom, newIG);
}
}
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) {
addToInstancedGeometry(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() {
return clone(true);
}
@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);
} else if (clone.children.get(i) instanceof Geometry) {
Geometry geom = (Geometry) clone.children.get(i);
if (geom.isGrouped()) {
throw new AssertionError();
}
}
}
}
// remove original control from the clone
clone.controls.remove(this.control);
// put clone's control in
clone.control = new InstancedNodeControl(clone);
clone.controls.add(clone.control);
clone.lookUp = new InstanceTypeKey();
clone.igByGeom = new HashMap<Geometry, InstancedGeometry>();
clone.instancesMap = new HashMap<InstanceTypeKey, InstancedGeometry>();
clone.instance();
return clone;
}
@Override
public void onTransformChange(Geometry geom) {
// Handled automatically
}
@Override
public void onMaterialChange(Geometry geom) {
relocateInInstancedGeometry(geom);
}
@Override
public void onMeshChange(Geometry geom) {
relocateInInstancedGeometry(geom);
}
@Override
public void onGeoemtryUnassociated(Geometry geom) {
removeFromInstancedGeometry(geom);
}
}

@ -132,6 +132,8 @@ MaterialDef Phong Lighting {
// For hardware skinning
Int NumberOfBones
Matrix4Array BoneMatrices
Boolean UseInstancing
}
Technique {
@ -148,6 +150,7 @@ MaterialDef Phong Lighting {
ViewMatrix
CameraPosition
WorldMatrix
ViewProjectionMatrix
}
Defines {
@ -177,6 +180,8 @@ MaterialDef Phong Lighting {
SPHERE_MAP : SphereMap
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
}
}
@ -188,12 +193,15 @@ MaterialDef Phong Lighting {
WorldParameters {
WorldViewProjectionMatrix
WorldViewMatrix
ViewProjectionMatrix
ViewMatrix
}
Defines {
COLOR_MAP : ColorMap
DISCARD_ALPHA : AlphaDiscardThreshold
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
}
ForcedRenderState {
@ -214,6 +222,8 @@ MaterialDef Phong Lighting {
WorldParameters {
WorldViewProjectionMatrix
WorldMatrix
ViewProjectionMatrix
ViewMatrix
}
Defines {
@ -227,6 +237,7 @@ MaterialDef Phong Lighting {
PSSM : Splits
POINTLIGHT : LightViewProjectionMatrix5
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
}
ForcedRenderState {
@ -243,6 +254,8 @@ MaterialDef Phong Lighting {
WorldParameters {
WorldViewProjectionMatrix
WorldMatrix
ViewProjectionMatrix
ViewMatrix
}
Defines {
@ -256,6 +269,7 @@ MaterialDef Phong Lighting {
PSSM : Splits
POINTLIGHT : LightViewProjectionMatrix5
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
}
ForcedRenderState {
@ -274,11 +288,14 @@ MaterialDef Phong Lighting {
WorldViewProjectionMatrix
WorldViewMatrix
NormalMatrix
ViewProjectionMatrix
ViewMatrix
}
Defines {
DIFFUSEMAP_ALPHA : DiffuseMap
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
}
}
@ -292,12 +309,15 @@ MaterialDef Phong Lighting {
WorldParameters {
WorldViewProjectionMatrix
WorldViewMatrix
NormalMatrix
NormalMatrix
ViewProjectionMatrix
ViewMatrix
}
Defines {
DIFFUSEMAP_ALPHA : DiffuseMap
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
}
}
@ -339,6 +359,8 @@ MaterialDef Phong Lighting {
WorldParameters {
WorldViewProjectionMatrix
ViewProjectionMatrix
ViewMatrix
}
Defines {
@ -347,6 +369,7 @@ MaterialDef Phong Lighting {
HAS_GLOWCOLOR : GlowColor
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
}
}

@ -1,12 +1,16 @@
#import "Common/ShaderLib/Instancing.glsllib"
#define ATTENUATION
//#define HQ_ATTENUATION
#import "Common/ShaderLib/Skinning.glsllib"
uniform mat4 g_WorldViewProjectionMatrix;
uniform mat4 g_WorldViewMatrix;
uniform mat3 g_NormalMatrix;
uniform mat4 g_ViewMatrix;
/*
uniform mat4 g_WorldViewProjectionMatrix;
uniform mat4 g_WorldViewMatrix;
uniform mat3 g_NormalMatrix;
uniform mat4 g_ViewMatrix;
*/
uniform vec4 m_Ambient;
uniform vec4 m_Diffuse;
@ -148,14 +152,14 @@ void main(){
#endif
#endif
gl_Position = g_WorldViewProjectionMatrix * modelSpacePos;
gl_Position = TransformWorldViewProjection(modelSpacePos);// g_WorldViewProjectionMatrix * modelSpacePos;
texCoord = inTexCoord;
#ifdef SEPARATE_TEXCOORD
texCoord2 = inTexCoord2;
#endif
vec3 wvPosition = (g_WorldViewMatrix * modelSpacePos).xyz;
vec3 wvNormal = normalize(g_NormalMatrix * modelSpaceNorm);
vec3 wvPosition = TransformWorldView(modelSpacePos).xyz;// (g_WorldViewMatrix * modelSpacePos).xyz;
vec3 wvNormal = normalize(TransformNormal(modelSpaceNorm));//normalize(g_NormalMatrix * modelSpaceNorm);
vec3 viewDir = normalize(-wvPosition);
//vec4 lightColor = g_LightColor[gl_InstanceID];
@ -168,7 +172,7 @@ void main(){
vec4 lightColor = g_LightColor;
#if defined(NORMALMAP) && !defined(VERTEX_LIGHTING)
vec3 wvTangent = normalize(g_NormalMatrix * modelSpaceTan);
vec3 wvTangent = normalize(TransformNormal(modelSpaceTan));
vec3 wvBinormal = cross(wvNormal, wvTangent);
mat3 tbnMat = mat3(wvTangent, wvBinormal * inTangent.w,wvNormal);
@ -187,7 +191,7 @@ void main(){
lightComputeDir(wvPosition, lightColor, wvLightPos, vLightDir);
#ifdef V_TANGENT
vNormal = normalize(g_NormalMatrix * inTangent.xyz);
vNormal = normalize(TransformNormal(inTangent.xyz));
vNormal = -cross(cross(vLightDir.xyz, vNormal), vNormal);
#endif
#endif

@ -10,6 +10,8 @@ MaterialDef Debug Normals {
WorldParameters {
WorldViewProjectionMatrix
ViewProjectionMatrix
ViewMatrix
ProjectionMatrix
}

@ -59,7 +59,8 @@ MaterialDef Unshaded {
WorldParameters {
WorldViewProjectionMatrix
ProjectionMatrix
ViewProjectionMatrix
ViewMatrix
}
Defines {
@ -86,10 +87,13 @@ MaterialDef Unshaded {
WorldViewProjectionMatrix
WorldViewMatrix
NormalMatrix
ViewProjectionMatrix
ViewMatrix
}
Defines {
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
}
}
@ -101,12 +105,15 @@ MaterialDef Unshaded {
WorldParameters {
WorldViewProjectionMatrix
WorldViewMatrix
ViewProjectionMatrix
ViewMatrix
}
Defines {
COLOR_MAP : ColorMap
DISCARD_ALPHA : AlphaDiscardThreshold
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
}
ForcedRenderState {
@ -127,6 +134,8 @@ MaterialDef Unshaded {
WorldParameters {
WorldViewProjectionMatrix
WorldMatrix
ViewProjectionMatrix
ViewMatrix
}
Defines {
@ -140,6 +149,7 @@ MaterialDef Unshaded {
PSSM : Splits
POINTLIGHT : LightViewProjectionMatrix5
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
}
ForcedRenderState {
@ -156,6 +166,8 @@ MaterialDef Unshaded {
WorldParameters {
WorldViewProjectionMatrix
WorldMatrix
ViewProjectionMatrix
ViewMatrix
}
Defines {
@ -169,6 +181,7 @@ MaterialDef Unshaded {
PSSM : Splits
POINTLIGHT : LightViewProjectionMatrix5
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
}
ForcedRenderState {
@ -185,6 +198,8 @@ MaterialDef Unshaded {
WorldParameters {
WorldViewProjectionMatrix
ViewProjectionMatrix
ViewMatrix
}
Defines {
@ -192,6 +207,7 @@ MaterialDef Unshaded {
HAS_GLOWMAP : GlowMap
HAS_GLOWCOLOR : GlowColor
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
}
}
}

@ -29,6 +29,7 @@ MaterialDef Post Shadow {
Float PCFEdge
Float ShadowMapSize
}
Technique {

@ -1,12 +1,10 @@
#import "Common/ShaderLib/Instancing.glsllib"
#import "Common/ShaderLib/Skinning.glsllib"
uniform mat4 m_LightViewProjectionMatrix0;
uniform mat4 m_LightViewProjectionMatrix1;
uniform mat4 m_LightViewProjectionMatrix2;
uniform mat4 m_LightViewProjectionMatrix3;
uniform mat4 g_WorldViewProjectionMatrix;
uniform mat4 g_WorldMatrix;
uniform mat4 g_ViewMatrix;
uniform vec3 m_LightPos;
varying vec4 projCoord0;
@ -52,7 +50,7 @@ void main(){
#ifdef NUM_BONES
Skinning_Compute(modelSpacePos);
#endif
gl_Position = g_WorldViewProjectionMatrix * modelSpacePos;
gl_Position = TransformWorldViewProjection(modelSpacePos);
#ifndef POINTLIGHT
#ifdef PSSM

@ -1,11 +1,10 @@
#import "Common/ShaderLib/Instancing.glsllib"
#import "Common/ShaderLib/Skinning.glsllib"
uniform mat4 m_LightViewProjectionMatrix0;
uniform mat4 m_LightViewProjectionMatrix1;
uniform mat4 m_LightViewProjectionMatrix2;
uniform mat4 m_LightViewProjectionMatrix3;
uniform mat4 g_WorldViewProjectionMatrix;
uniform mat4 g_WorldMatrix;
out vec4 projCoord0;
out vec4 projCoord1;
@ -51,7 +50,7 @@ void main(){
#ifdef NUM_BONES
Skinning_Compute(modelSpacePos);
#endif
gl_Position = g_WorldViewProjectionMatrix * modelSpacePos;
gl_Position = TransformWorldViewProjection(modelSpacePos);
#ifndef POINTLIGHT
#ifdef PSSM
@ -60,7 +59,7 @@ void main(){
vec4 worldPos=vec4(0.0);
#endif
// get the vertex in world space
worldPos = g_WorldMatrix * modelSpacePos;
worldPos = TransformWorld(modelSpacePos);
#ifdef DISCARD_ALPHA
texCoord = inTexCoord;

@ -1,10 +1,8 @@
#import "Common/ShaderLib/Instancing.glsllib"
#import "Common/ShaderLib/Skinning.glsllib"
attribute vec3 inPosition;
attribute vec2 inTexCoord;
uniform mat4 g_WorldViewProjectionMatrix;
uniform mat4 g_WorldViewMatrix;
varying vec2 texCoord;
void main(){
@ -13,6 +11,6 @@ void main(){
#ifdef NUM_BONES
Skinning_Compute(modelSpacePos);
#endif
gl_Position = g_WorldViewProjectionMatrix * modelSpacePos;
gl_Position = TransformWorldViewProjection(modelSpacePos);
texCoord = inTexCoord;
}

@ -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.
@ -70,6 +78,11 @@ vec3 TransformNormal(vec3 vec)
#else
vec4 TransformWorld(vec4 position)
{
return g_WorldMatrix * position;
}
vec4 TransformWorldView(vec4 position)
{
return g_WorldViewMatrix * position;

@ -1,6 +1,8 @@
#import "Common/ShaderLib/Instancing.glsllib"
#import "Common/ShaderLib/Skinning.glsllib"
uniform mat4 g_WorldViewProjectionMatrix;
uniform mat3 g_NormalMatrix;
// These are included in the above now
//uniform mat4 g_WorldViewProjectionMatrix;
//uniform mat3 g_NormalMatrix;
attribute vec3 inPosition;
attribute vec3 inNormal;

@ -0,0 +1,93 @@
/*
* Copyright (c) 2009-2010 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package jme3test.bullet;
import com.jme3.animation.AnimEventListener;
import com.jme3.animation.Bone;
import com.jme3.bullet.collision.RagdollCollisionListener;
import com.jme3.bullet.control.KinematicRagdollControl;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
/**
* @author reden
*/
public class TestIK extends TestBoneRagdoll implements RagdollCollisionListener, AnimEventListener {
Node targetNode = new Node("");
Vector3f targetPoint;
Bone mouseBone;
Vector3f oldMousePos;
public static void main(String[] args) {
TestIK app = new TestIK();
app.start();
}
@Override
public void simpleInitApp() {
super.simpleInitApp();
final KinematicRagdollControl ikControl = model.getControl(KinematicRagdollControl.class);
inputManager.addListener(new ActionListener() {
public void onAction(String name, boolean isPressed, float tpf) {
if (name.equals("stop") && isPressed) {
ikControl.setEnabled(!ikControl.isEnabled());
ikControl.setIKMode();
}
if (name.equals("one") && isPressed) {
//ragdoll.setKinematicMode();
targetPoint = model.getWorldTranslation().add(new Vector3f(0,2,4));
targetNode.setLocalTranslation(targetPoint);
ikControl.setIKTarget(ikControl.getBone("Hand.L"), targetPoint, 2);
ikControl.setIKMode();
}
if (name.equals("two") && isPressed) {
//ragdoll.setKinematicMode();
targetPoint = model.getWorldTranslation().add(new Vector3f(-3,3,0));
targetNode.setLocalTranslation(targetPoint);
ikControl.setIKTarget(ikControl.getBone("Hand.R"), targetPoint, 3);
ikControl.setIKMode();
}
}
}, "one", "two");
inputManager.addMapping("one", new KeyTrigger(KeyInput.KEY_1));
inputManager.addMapping("two", new KeyTrigger(KeyInput.KEY_2));
inputManager.addMapping("stop", new KeyTrigger(KeyInput.KEY_H));
}
}

@ -0,0 +1,192 @@
/*
* 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.Node;
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 Node instancedNode;
private float time = 0;
private boolean INSTANCING = false;
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", INSTANCING);
materials[0].setColor("Color", ColorRGBA.Red);
materials[1] = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
materials[1].setBoolean("UseInstancing", INSTANCING);
materials[1].setColor("Color", ColorRGBA.Green);
materials[2] = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
materials[2].setBoolean("UseInstancing", INSTANCING);
materials[2].setColor("Color", ColorRGBA.Blue);
materials[3] = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
materials[3].setBoolean("UseInstancing", INSTANCING);
materials[3].setColor("Color", ColorRGBA.Cyan);
materials[4] = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
materials[4].setBoolean("UseInstancing", INSTANCING);
materials[4].setColor("Color", ColorRGBA.Magenta);
materials[5] = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
materials[5].setBoolean("UseInstancing", INSTANCING);
materials[5].setColor("Color", ColorRGBA.Yellow);
instancedNode = new InstancedNode("instanced_node");
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);
}
}
if (INSTANCING) {
((InstancedNode)instancedNode).instance();
}
instancedNode = (InstancedNode) instancedNode.clone();
instancedNode.move(0, 5, 0);
rootNode.attachChild(instancedNode);
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);
}
}
}
}

@ -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");
}
}

@ -2226,7 +2226,6 @@ public class LwjglRenderer implements Renderer {
}
}
int slotsRequired = 1;
if (vb.isInstanced()) {
if (!ctxCaps.GL_ARB_instanced_arrays
|| !ctxCaps.GL_ARB_draw_instanced) {
@ -2234,7 +2233,10 @@ public class LwjglRenderer implements Renderer {
+ "but not supported by the "
+ "graphics hardware");
}
if (vb.getNumComponents() > 4 && vb.getNumComponents() % 4 != 0) {
}
int slotsRequired = 1;
if (vb.getNumComponents() > 4) {
if (vb.getNumComponents() % 4 != 0) {
throw new RendererException("Number of components in multi-slot "
+ "buffers must be divisible by 4");
}
@ -2273,7 +2275,7 @@ public class LwjglRenderer implements Renderer {
vb.getOffset());
} else {
for (int i = 0; i < slotsRequired; i++) {
// The pointer maps the next 4 floats in the slot.
// The pointer maps the next 4 floats in the slot.
// E.g.
// P1: XXXX____________XXXX____________
// P2: ____XXXX____________XXXX________
@ -2294,7 +2296,7 @@ public class LwjglRenderer implements Renderer {
int slot = loc + i;
if (vb.isInstanced() && (attribs[slot] == null || !attribs[slot].isInstanced())) {
// non-instanced -> instanced
glVertexAttribDivisorARB(slot, 1);
glVertexAttribDivisorARB(slot, vb.getInstanceSpan());
} else if (!vb.isInstanced() && attribs[slot] != null && attribs[slot].isInstanced()) {
// instanced -> non-instanced
glVertexAttribDivisorARB(slot, 0);
@ -2491,6 +2493,11 @@ public class LwjglRenderer implements Renderer {
}
private void renderMeshDefault(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
// Here while count is still passed in. Can be removed when/if
// the method is collapsed again. -pspeed
count = Math.max(mesh.getInstanceCount(), count);
VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData);
if (interleavedData != null && interleavedData.isUpdateNeeded()) {
updateBufferData(interleavedData);

Loading…
Cancel
Save