diff --git a/jme3-android/src/main/java/com/jme3/renderer/android/TextureUtil.java b/jme3-android/src/main/java/com/jme3/renderer/android/TextureUtil.java index db4ea5ad7..daecb05c9 100644 --- a/jme3-android/src/main/java/com/jme3/renderer/android/TextureUtil.java +++ b/jme3-android/src/main/java/com/jme3/renderer/android/TextureUtil.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; diff --git a/jme3-android/src/main/java/com/jme3/texture/plugins/AndroidImageLoader.java b/jme3-android/src/main/java/com/jme3/texture/plugins/AndroidImageLoader.java index 17f850e7a..739e031de 100644 --- a/jme3-android/src/main/java/com/jme3/texture/plugins/AndroidImageLoader.java +++ b/jme3-android/src/main/java/com/jme3/texture/plugins/AndroidImageLoader.java @@ -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; } diff --git a/jme3-android/src/main/java/com/jme3/texture/plugins/AndroidNativeImageLoader.java b/jme3-android/src/main/java/com/jme3/texture/plugins/AndroidNativeImageLoader.java index 9a38d199c..5e6d20e93 100644 --- a/jme3-android/src/main/java/com/jme3/texture/plugins/AndroidNativeImageLoader.java +++ b/jme3-android/src/main/java/com/jme3/texture/plugins/AndroidNativeImageLoader.java @@ -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; } diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/KinematicRagdollControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/KinematicRagdollControl.java index aa6616199..d81d6a267 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/control/KinematicRagdollControl.java +++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/KinematicRagdollControl.java @@ -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 ikTargets = new HashMap(); + private Map ikChainDepth = new HashMap(); + 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 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); } /** diff --git a/jme3-core/src/main/java/com/jme3/animation/Bone.java b/jme3-core/src/main/java/com/jme3/animation/Bone.java index 16c066a85..819a77383 100644 --- a/jme3-core/src/main/java/com/jme3/animation/Bone.java +++ b/jme3-core/src/main/java/com/jme3/animation/Bone.java @@ -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; + } } diff --git a/jme3-core/src/main/java/com/jme3/cinematic/events/AnimationEvent.java b/jme3-core/src/main/java/com/jme3/cinematic/events/AnimationEvent.java index 1376f78eb..e8bfc5392 100644 --- a/jme3-core/src/main/java/com/jme3/cinematic/events/AnimationEvent.java +++ b/jme3-core/src/main/java/com/jme3/cinematic/events/AnimationEvent.java @@ -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(); + int numChannels = model.getControl(AnimControl.class).getNumChannels(); + for(int i = 0; i < numChannels; i++){ + ((HashMap)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 diff --git a/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java b/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java index 3db67a1a5..28bfd595d 100644 --- a/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java +++ b/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java @@ -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) { diff --git a/jme3-core/src/main/java/com/jme3/material/Material.java b/jme3-core/src/main/java/com/jme3/material/Material.java index 956fc7744..242f41773 100644 --- a/jme3-core/src/main/java/com/jme3/material/Material.java +++ b/jme3-core/src/main/java/com/jme3/material/Material.java @@ -702,7 +702,11 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { int lodLevel = geom.getLodLevel(); if (geom instanceof InstancedGeometry) { InstancedGeometry instGeom = (InstancedGeometry) geom; - renderer.renderMesh(mesh, lodLevel, instGeom.getCurrentNumInstances(), instGeom.getAllInstanceData()); + int numInstances = instGeom.getActualNumInstances(); + if (numInstances == 0) { + return; + } + renderer.renderMesh(mesh, lodLevel, numInstances, instGeom.getAllInstanceData()); } else { renderer.renderMesh(mesh, lodLevel, 1, null); } diff --git a/jme3-core/src/main/java/com/jme3/math/ColorRGBA.java b/jme3-core/src/main/java/com/jme3/math/ColorRGBA.java index 563c4195e..f7dd7940f 100644 --- a/jme3-core/src/main/java/com/jme3/math/ColorRGBA.java +++ b/jme3-core/src/main/java/com/jme3/math/ColorRGBA.java @@ -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); } /** diff --git a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java index 5b3382ccd..f6f951ab3 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java +++ b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java @@ -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); } diff --git a/jme3-core/src/main/java/com/jme3/renderer/Statistics.java b/jme3-core/src/main/java/com/jme3/renderer/Statistics.java index 5928e9879..47f5da2fe 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/Statistics.java +++ b/jme3-core/src/main/java/com/jme3/renderer/Statistics.java @@ -125,7 +125,7 @@ public class Statistics { if( !enabled ) return; - numObjects += count; + numObjects += 1; numTriangles += mesh.getTriangleCount(lod) * count; numVertices += mesh.getVertexCount() * count; } diff --git a/jme3-core/src/main/java/com/jme3/scene/BatchNode.java b/jme3-core/src/main/java/com/jme3/scene/BatchNode.java index 8c12f3c32..f838d1fbc 100644 --- a/jme3-core/src/main/java/com/jme3/scene/BatchNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/BatchNode.java @@ -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.class); + clone.batchesByGeom = new HashMap(); clone.batch(); } return clone; diff --git a/jme3-core/src/main/java/com/jme3/scene/Geometry.java b/jme3-core/src/main/java/com/jme3/scene/Geometry.java index 1ce159c78..50ca01912 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Geometry.java +++ b/jme3-core/src/main/java/com/jme3/scene/Geometry.java @@ -81,7 +81,7 @@ public class Geometry extends Spatial { * The start index of this Geometry's 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(); diff --git a/jme3-core/src/main/java/com/jme3/scene/GeometryGroupNode.java b/jme3-core/src/main/java/com/jme3/scene/GeometryGroupNode.java index 52b299d56..ea94e4b89 100644 --- a/jme3-core/src/main/java/com/jme3/scene/GeometryGroupNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/GeometryGroupNode.java @@ -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 GeometryGroupNode */ diff --git a/jme3-core/src/main/java/com/jme3/scene/Mesh.java b/jme3-core/src/main/java/com/jme3/scene/Mesh.java index 3db35c417..8b3ffc80e 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Mesh.java +++ b/jme3-core/src/main/java/com/jme3/scene/Mesh.java @@ -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); diff --git a/jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java b/jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java index 9400dc466..c67435b78 100644 --- a/jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java +++ b/jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java @@ -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 VertexBuffer. 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(); diff --git a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java index 86f922c2a..7f0bb601b 100644 --- a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java +++ b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java @@ -39,172 +39,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; -/** - * InstancedGeometry allows rendering many similar - * geometries efficiently through a feature called geometry - * instancing. - * - *

- * All rendered geometries share material, mesh, and lod level - * but have different world transforms or possibly other parameters. - * The settings for all instances are inherited from this geometry's - * {@link #setMesh(com.jme3.scene.Mesh) mesh}, - * {@link #setMaterial(com.jme3.material.Material) material} and - * {@link #setLodLevel(int) lod level} and cannot be changed per-instance. - *

- * - *

- * In order to receive any per-instance parameters, the material's shader - * must be changed to retrieve per-instance data via - * {@link VertexBuffer#setInstanced(boolean) instanced vertex attributes} - * or uniform arrays indexed with the GLSL built-in uniform - * gl_InstanceID. At the very least, they should use the - * functions specified in Instancing.glsllib shader library - * to transform vertex positions and normals instead of multiplying by the - * built-in matrix uniforms. - *

- * - *

- * This class can operate in two modes, {@link InstancedGeometry.Mode#Auto} - * and {@link InstancedGeometry.Mode#Manual}. See the respective enums - * for more information

- * - *

- * Prior to usage, the maximum number of instances must be set via - * {@link #setMaxNumInstances(int) } and the current number of instances set - * via {@link #setCurrentNumInstances(int) }. The user is then - * expected to provide transforms for all instances up to the number - * of current instances. - *

- * - * @author Kirill Vainer - */ public class InstancedGeometry extends Geometry { - /** - * Indicates how the per-instance data is to be specified. - */ - public static enum Mode { - - /** - * The user must specify all per-instance transforms and - * parameters manually via - * {@link InstancedGeometry#setGlobalUserInstanceData(com.jme3.scene.VertexBuffer[]) } - * or - * {@link InstancedGeometry#setCameraUserInstanceData(com.jme3.renderer.Camera, com.jme3.scene.VertexBuffer) }. - */ - Manual, - - /** - * The user - * {@link InstancedGeometry#setInstanceTransform(int, com.jme3.math.Transform) provides world transforms} - * and then uses the Instancing.glsllib transform functions in the - * shader to transform vertex attributes to the respective spaces. - * Additional per-instance data can be specified via - * {@link InstancedGeometry#setManualGlobalInstanceData(com.jme3.scene.VertexBuffer[]) }. - * {@link #setManualCameraInstanceData(com.jme3.renderer.Camera, com.jme3.scene.VertexBuffer) } - * cannot be used at this mode since it is computed automatically. - */ - Auto - } - - private static class InstancedGeometryControl extends AbstractControl { - - private InstancedGeometry geom; - - public InstancedGeometryControl() { - } - - public InstancedGeometryControl(InstancedGeometry geom) { - this.geom = geom; - } - - @Override - protected void controlUpdate(float tpf) { - } - - @Override - protected void controlRender(RenderManager rm, ViewPort vp) { - geom.renderFromControl(vp.getCamera()); - } - } - private static final int INSTANCE_SIZE = 16; - private InstancedGeometry.Mode mode; - private InstancedGeometryControl control; - private int currentNumInstances = 1; - private Camera lastCamera = null; - private Matrix4f[] worldMatrices = new Matrix4f[1]; private VertexBuffer[] globalInstanceData; + private VertexBuffer transformInstanceData; + private Geometry[] geometries = new Geometry[1]; - private final HashMap instanceDataPerCam - = new HashMap(); - - // TODO: determine if perhaps its better to use TempVars here. - - private final Matrix4f tempMat4 = new Matrix4f(); - private final Matrix4f tempMat4_2 = new Matrix4f(); - private final Matrix3f tempMat3 = new Matrix3f(); - private final Quaternion tempQuat = new Quaternion(); - private final float[] tempFloatArray = new float[16]; - + private int firstUnusedIndex = 0; + /** * Serialization only. Do not use. */ public InstancedGeometry() { super(); setIgnoreTransform(true); + 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 null if none was specified - * for the given camera. - * - * @throws IllegalArgumentException If camera is null. - * @throws IllegalStateException If {@link #getMode() mode} is set to - * {@link Mode#Auto}. - * - * @see Mode - * @see #setCameraUserInstanceData(com.jme3.renderer.Camera, com.jme3.scene.VertexBuffer) + * @param transformInstanceData The transforms for each instance. */ - public VertexBuffer getCameraUserInstanceData(Camera camera) { - if (mode == Mode.Auto) { - throw new IllegalStateException("Not allowed in auto mode"); - } - if (camera == null) { - throw new IllegalArgumentException("camera cannot be null"); - } - return instanceDataPerCam.get(camera); + public void setTransformUserInstanceData(VertexBuffer transformInstanceData) { + this.transformInstanceData = transformInstanceData; } /** - * Return a read only map with the mappings between cameras and camera - * specific per-instance data. - * - * Only applies when operating in {@link Mode#Manual}. - * When operating in {@link Mode#Auto}, this data is computed automatically, - * and using this method is not allowed. + * Return 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 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 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]; } } } diff --git a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java new file mode 100644 index 000000000..b3cb26da0 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java @@ -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 igByGeom + = new HashMap(); + + private InstanceTypeKey lookUp = new InstanceTypeKey(); + + private HashMap instancesMap = + new HashMap(); + + public InstancedNode() { + super(); + // NOTE: since we are deserializing, + // the control is going to be added automatically here. + } + + public InstancedNode(String name) { + super(name); + 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(); + clone.instancesMap = new HashMap(); + + 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); + } +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md index d27adb9f4..779242445 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md @@ -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 } } diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert index c7cb3e5db..1bd192c94 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert @@ -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 diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.j3md index a2e2172c1..63b7470b2 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.j3md @@ -10,6 +10,8 @@ MaterialDef Debug Normals { WorldParameters { WorldViewProjectionMatrix + ViewProjectionMatrix + ViewMatrix ProjectionMatrix } diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md index 5a57426d4..aee284705 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md @@ -59,7 +59,8 @@ MaterialDef Unshaded { WorldParameters { WorldViewProjectionMatrix - ProjectionMatrix + ViewProjectionMatrix + ViewMatrix } Defines { @@ -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 } } } \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.j3md b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.j3md index 1ac99f079..af80bdae3 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.j3md @@ -29,6 +29,7 @@ MaterialDef Post Shadow { Float PCFEdge Float ShadowMapSize + } Technique { diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert index 262f14fe9..9c2318a9a 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert @@ -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 diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.vert b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.vert index c473cafaa..034e5bef6 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.vert @@ -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; diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadow.vert b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadow.vert index e57d45141..9bb5d3a87 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadow.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadow.vert @@ -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; } \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib index 4bf3fc1e9..7ed91181d 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib @@ -23,6 +23,7 @@ uniform mat4 g_ViewMatrix; uniform mat4 g_ProjectionMatrix; uniform mat4 g_WorldViewMatrix; uniform mat4 g_WorldViewProjectionMatrix; +uniform mat4 g_ViewProjectionMatrix; uniform mat3 g_NormalMatrix; #if defined INSTANCING @@ -37,29 +38,36 @@ uniform mat3 g_NormalMatrix; // 2 vertex attributes which now can be used for additional per-vertex data. attribute mat4 inInstanceData; -// Extract the world view matrix out of the instance data, leaving out the +// Extract the world matrix out of the instance data, leaving out the // quaternion at the end. -mat4 worldViewMatrix = mat4(vec4(inInstanceData[0].xyz, 0.0), +mat4 worldMatrix = mat4(vec4(inInstanceData[0].xyz, 0.0), vec4(inInstanceData[1].xyz, 0.0), vec4(inInstanceData[2].xyz, 0.0), vec4(inInstanceData[3].xyz, 1.0)); +vec4 TransformWorld(vec4 position) +{ + return (worldMatrix * position); +} vec4 TransformWorldView(vec4 position) { - return worldViewMatrix * position; + return g_ViewMatrix * TransformWorld(position); } vec4 TransformWorldViewProjection(vec4 position) { - return g_ProjectionMatrix * TransformWorldView(position); + return g_ViewProjectionMatrix * TransformWorld(position); } vec3 TransformNormal(vec3 vec) { vec4 quat = vec4(inInstanceData[0].w, inInstanceData[1].w, inInstanceData[2].w, inInstanceData[3].w); - return vec + vec3(2.0) * cross(cross(vec, quat.xyz) + vec3(quat.w) * vec, quat.xyz); + + vec3 worldNormal = vec + vec3(2.0) * cross(cross(vec, quat.xyz) + vec3(quat.w) * vec, quat.xyz); + + return (g_ViewMatrix * vec4(worldNormal, 0.0)).xyz; } // Prevent user from using g_** matrices which will have invalid data in this case. @@ -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; diff --git a/jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.vert b/jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.vert index 7eb36c40c..82a08f392 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.vert +++ b/jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.vert @@ -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; diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestIK.java b/jme3-examples/src/main/java/jme3test/bullet/TestIK.java new file mode 100644 index 000000000..43313e789 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/TestIK.java @@ -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)); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNode.java b/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNode.java new file mode 100644 index 000000000..41e711c7f --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNode.java @@ -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); + } + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstancing.java b/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstancing.java deleted file mode 100644 index 2496b4cb2..000000000 --- a/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstancing.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package jme3test.scene.instancing; - -import com.jme3.app.SimpleApplication; -import com.jme3.input.KeyInput; -import com.jme3.input.controls.ActionListener; -import com.jme3.input.controls.AnalogListener; -import com.jme3.input.controls.KeyTrigger; -import com.jme3.material.Material; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.scene.Geometry; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.Spatial.CullHint; -import com.jme3.scene.instancing.InstancedGeometry; -import com.jme3.scene.shape.Sphere; - -public class TestInstancing extends SimpleApplication { - - private InstancedGeometry instancedGeometry; - private Node instancedGeoms; - private Material material; - private boolean enabled = true; - - public static void main(String[] args){ - TestInstancing app = new TestInstancing(); - //app.setShowSettings(false); - //app.setDisplayFps(false); - //app.setDisplayStatView(false); - app.start(); - } - - private Geometry createInstance(float x, float z) { - // Note: it doesn't matter what mesh or material we set here. - Geometry geometry = new Geometry("randomGeom", instancedGeometry.getMesh()); - geometry.setMaterial(instancedGeometry.getMaterial()); - geometry.setLocalTranslation(x, 0, z); - return geometry; - } - - @Override - public void simpleInitApp() { - initInputs(); - - Sphere sphere = new Sphere(10, 10, 0.5f, true, false); - material = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md"); - material.setBoolean("UseInstancing", true); - - instancedGeometry = new InstancedGeometry(InstancedGeometry.Mode.Auto, "instanced_geom"); - instancedGeometry.setMaxNumInstances(60 * 60); - instancedGeometry.setCurrentNumInstances(60 * 60); - instancedGeometry.setCullHint(CullHint.Never); - instancedGeometry.setMesh(sphere); - instancedGeometry.setMaterial(material); - rootNode.attachChild(instancedGeometry); - - instancedGeoms = new Node("instances_node"); - - // Important: Do not render these geometries, only - // use their world transforms to instance them via - // InstancedGeometry. - instancedGeoms.setCullHint(CullHint.Always); - - for (int y = -30; y < 30; y++) { - for (int x = -30; x < 30; x++) { - Geometry instance = createInstance(x, y); - instancedGeoms.attachChild(instance); - } - } - - rootNode.attachChild(instancedGeoms); - rootNode.setCullHint(CullHint.Never); - - int instanceIndex = 0; - for (Spatial child : instancedGeoms.getChildren()) { - if (instanceIndex < instancedGeometry.getMaxNumInstances()) { - instancedGeometry.setInstanceTransform(instanceIndex++, child.getWorldTransform()); - } - } - - instancedGeometry.setCurrentNumInstances(instanceIndex); - - cam.setLocation(new Vector3f(38.373516f, 6.689055f, 38.482082f)); - cam.setRotation(new Quaternion(-0.04004206f, 0.918326f, -0.096310444f, -0.38183528f)); - flyCam.setMoveSpeed(15); - } - - private void initInputs() { - inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE)); - - ActionListener acl = new ActionListener() { - - public void onAction(String name, boolean keyPressed, float tpf) { - if (name.equals("toggle") && keyPressed) { - if (enabled) { - enabled = false; - instancedGeoms.setCullHint(CullHint.Dynamic); - instancedGeometry.setCullHint(CullHint.Always); - material.setBoolean("UseInstancing", false); - System.out.println("Instancing OFF"); - } else { - enabled = true; - instancedGeoms.setCullHint(CullHint.Always); - instancedGeometry.setCullHint(CullHint.Never); - material.setBoolean("UseInstancing", true); - System.out.println("Instancing ON"); - } - } - } - }; - - inputManager.addListener(acl, "toggle"); - } -} diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java index 408146655..b093467fc 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java +++ b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java @@ -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);