From ed6256ef47e57f6164caca99dbaede8752507d2b Mon Sep 17 00:00:00 2001 From: neph1 Date: Sat, 28 Jun 2014 12:57:18 +0200 Subject: [PATCH 01/23] Fix for handling multiple channels in AnimationEvent new constructor (variant) (First commit in github repo) --- .../jme3/cinematic/events/AnimationEvent.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) 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 From 8738f961eaf9b73c5f285b16948e259619057d26 Mon Sep 17 00:00:00 2001 From: neph1 Date: Sat, 28 Jun 2014 14:58:57 +0200 Subject: [PATCH 02/23] This is the long-overdue initial check in of KinematicRagdollControl with IK support. It also contains some modifications to Bone. Tested with TestBoneRagdoll to ensure backwards compatibility. Please review. --- .../control/KinematicRagdollControl.java | 231 ++++++++++++++++-- .../main/java/com/jme3/animation/Bone.java | 13 +- 2 files changed, 228 insertions(+), 16 deletions(-) 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..e0ae9855b 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,7 +196,9 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P if (!enabled) { return; } - + if(mode == Mode.IK){ + ikUpdate(tpf); + } //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)) { ragDollUpdate(tpf); @@ -260,6 +269,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 +312,95 @@ 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(); + Logger.getLogger(KinematicRagdollControl.class.getSimpleName()).log(Level.INFO, "updationg {0}", boneName); + bone = (Bone) boneLinks.get(boneName).bone; + if (!bone.hasUserControl()) { + Logger.getLogger(KinematicRagdollControl.class.getSimpleName()).log(Level.INFO, "{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.INFO, "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,19 +719,21 @@ 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); - } - - } - vars.release(); + 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(); + } for (Bone bone : skeleton.getRoots()) { RagdollUtils.setUserControl(bone, mode == Mode.Ragdoll); @@ -703,6 +806,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 +917,93 @@ 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; + } /** * serialize this control * @@ -831,6 +1030,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; + } } From ef1e69c182718d8582459704704b89220da7ef91 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Sat, 28 Jun 2014 19:05:04 -0400 Subject: [PATCH 03/23] * Instanced objects are now considered a single object when generating rendering statistics --- jme3-core/src/main/java/com/jme3/renderer/Statistics.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; } From 5b7a408bccd5e4b2c58a2e8986101fd9b7fd6586 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Sat, 28 Jun 2014 19:09:20 -0400 Subject: [PATCH 04/23] * Fix issue where BatchNode.clone() would still re-use the previous BatchNode's data structures --- jme3-core/src/main/java/com/jme3/scene/BatchNode.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) 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; From 52b93ba933a2300a33721a63bf9cc88ef0860981 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Sat, 28 Jun 2014 19:15:39 -0400 Subject: [PATCH 05/23] * Made Geometry.associate/unassociate To/From GroupNode methods public so they can be GeometryGroupNode implementations outside the com.jme3.scene package * Added ability to get the geometry start index for GeometryGroupNode implementations --- .../src/main/java/com/jme3/scene/Geometry.java | 12 +++++++----- .../java/com/jme3/scene/GeometryGroupNode.java | 14 ++++++++++++++ 2 files changed, 21 insertions(+), 5 deletions(-) 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..b33152c07 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; } } @@ -486,7 +488,7 @@ public class Geometry extends Spatial { // but the cloned one is not attached to anything, hence not managed. if (isGrouped()) { groupNode = null; - startIndex = 0; + 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 */ From eee43b470e7d5e796587294ffb4d026e2d569f19 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Sat, 28 Jun 2014 19:27:26 -0400 Subject: [PATCH 06/23] * Added InstancedNode: easy to use instancing with similar API to BatchNode. The underlying scene graph will be automatically optimized so instancing is used as much as possible, thus reducing number of draw calls and improving performance. Unlike BatchNode, it does not copy the geometry's mesh around, but only its transform. In order for it to work, it requires the Renderer to support the GeometryInstancing capability. * Replaced existing instancing test with TestInstanceNode, which demostrates how to use the new InstancedNode by changing the transform, mesh, and material of every instance periodically. * The lower-level InstancedGeometry API rewritten: Users don't need to manage the number of instances they have and their indices. Instead, they can use addInstance() and removeInstance() to add and remove instances as desired. Unlike InstancedNode, InstancedGeometry requires all Geometries to have the same mesh and material, but they can have different transforms. * Instancing.glsllib now requires the InstanceData to have world transforms instead of world view transforms. As a consequence, users of instancing must specify in the material the world parameters ViewProjectionMatrix and ViewMatrix instead of ProjectionMatrix. --- .../main/java/com/jme3/material/Material.java | 6 +- .../scene/instancing/InstancedGeometry.java | 509 ++++++------------ .../jme3/scene/instancing/InstancedNode.java | 259 +++++++++ .../Common/MatDefs/Misc/ShowNormals.j3md | 2 + .../Common/MatDefs/Misc/Unshaded.j3md | 3 +- .../Common/ShaderLib/Instancing.glsllib | 18 +- .../scene/instancing/TestInstanceNode.java | 186 +++++++ .../scene/instancing/TestInstancing.java | 146 ----- 8 files changed, 635 insertions(+), 494 deletions(-) create mode 100644 jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java create mode 100644 jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNode.java delete mode 100644 jme3-examples/src/main/java/jme3test/scene/instancing/TestInstancing.java diff --git a/jme3-core/src/main/java/com/jme3/material/Material.java b/jme3-core/src/main/java/com/jme3/material/Material.java index 956fc7744..242f41773 100644 --- a/jme3-core/src/main/java/com/jme3/material/Material.java +++ b/jme3-core/src/main/java/com/jme3/material/Material.java @@ -702,7 +702,11 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { int lodLevel = geom.getLodLevel(); if (geom instanceof InstancedGeometry) { InstancedGeometry instGeom = (InstancedGeometry) geom; - renderer.renderMesh(mesh, lodLevel, instGeom.getCurrentNumInstances(), instGeom.getAllInstanceData()); + int numInstances = instGeom.getActualNumInstances(); + if (numInstances == 0) { + return; + } + renderer.renderMesh(mesh, lodLevel, numInstances, instGeom.getAllInstanceData()); } else { renderer.renderMesh(mesh, lodLevel, 1, null); } diff --git a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java index 86f922c2a..d9259ab99 100644 --- a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java +++ b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java @@ -39,172 +39,49 @@ import com.jme3.export.Savable; import com.jme3.math.Matrix3f; import com.jme3.math.Matrix4f; import com.jme3.math.Quaternion; -import com.jme3.math.Transform; -import com.jme3.renderer.Camera; -import com.jme3.renderer.RenderManager; -import com.jme3.renderer.ViewPort; import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; import com.jme3.scene.Spatial; import com.jme3.scene.VertexBuffer; import com.jme3.scene.VertexBuffer.Format; import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.VertexBuffer.Usage; -import com.jme3.scene.control.AbstractControl; import com.jme3.util.BufferUtils; +import com.jme3.util.TempVars; import java.io.IOException; import java.nio.FloatBuffer; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -/** - * InstancedGeometry allows rendering many similar - * geometries efficiently through a feature called geometry - * instancing. - * - *

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

- * - *

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

- * - *

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

- * - *

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

- * - * @author Kirill Vainer - */ public class InstancedGeometry extends Geometry { - /** - * Indicates how the per-instance data is to be specified. - */ - public static enum Mode { - - /** - * The user must specify all per-instance transforms and - * parameters manually via - * {@link InstancedGeometry#setGlobalUserInstanceData(com.jme3.scene.VertexBuffer[]) } - * or - * {@link InstancedGeometry#setCameraUserInstanceData(com.jme3.renderer.Camera, com.jme3.scene.VertexBuffer) }. - */ - Manual, - - /** - * The user - * {@link InstancedGeometry#setInstanceTransform(int, com.jme3.math.Transform) provides world transforms} - * and then uses the Instancing.glsllib transform functions in the - * shader to transform vertex attributes to the respective spaces. - * Additional per-instance data can be specified via - * {@link InstancedGeometry#setManualGlobalInstanceData(com.jme3.scene.VertexBuffer[]) }. - * {@link #setManualCameraInstanceData(com.jme3.renderer.Camera, com.jme3.scene.VertexBuffer) } - * cannot be used at this mode since it is computed automatically. - */ - Auto - } - - private static class InstancedGeometryControl extends AbstractControl { - - private InstancedGeometry geom; - - public InstancedGeometryControl() { - } - - public InstancedGeometryControl(InstancedGeometry geom) { - this.geom = geom; - } - - @Override - protected void controlUpdate(float tpf) { - } - - @Override - protected void controlRender(RenderManager rm, ViewPort vp) { - geom.renderFromControl(vp.getCamera()); - } - } - private static final int INSTANCE_SIZE = 16; - private InstancedGeometry.Mode mode; - private InstancedGeometryControl control; - private int currentNumInstances = 1; - private Camera lastCamera = null; - private Matrix4f[] worldMatrices = new Matrix4f[1]; private VertexBuffer[] globalInstanceData; + private VertexBuffer transformInstanceData; + private Geometry[] geometries = new Geometry[1]; - private final HashMap instanceDataPerCam - = new HashMap(); - - // TODO: determine if perhaps its better to use TempVars here. - - private final Matrix4f tempMat4 = new Matrix4f(); - private final Matrix4f tempMat4_2 = new Matrix4f(); - private final Matrix3f tempMat3 = new Matrix3f(); - private final Quaternion tempQuat = new Quaternion(); - private final float[] tempFloatArray = new float[16]; - + private int firstUnusedIndex = 0; + /** * Serialization only. Do not use. */ public InstancedGeometry() { super(); setIgnoreTransform(true); + setMaxNumInstances(1); } /** * Creates instanced geometry with the specified mode and name. * - * @param mode The {@link Mode} at which the instanced geometry operates at. * @param name The name of the spatial. * - * @see Mode * @see Spatial#Spatial(java.lang.String) */ - public InstancedGeometry(InstancedGeometry.Mode mode, String name) { + public InstancedGeometry(String name) { super(name); - this.mode = mode; setIgnoreTransform(true); - if (mode == InstancedGeometry.Mode.Auto) { - control = new InstancedGeometryControl(this); - addControl(control); - } - } - - /** - * The mode with which this instanced geometry was initialized - * with. Cannot be changed after initialization. - * - * @return instanced geometry mode. - */ - public InstancedGeometry.Mode getMode() { - return mode; + setMaxNumInstances(1); } /** @@ -238,170 +115,54 @@ public class InstancedGeometry extends Geometry { /** * Specify camera specific user per-instance data. * - * Only applies when operating in {@link Mode#Manual}. - * When operating in {@link Mode#Auto}, this data is computed automatically, - * and using this method is not allowed. - * - * @param camera The camera for which per-instance data is to be set. - * @param cameraInstanceData The camera's per-instance data. - * - * @throws IllegalArgumentException If camera is null. - * @throws IllegalStateException If {@link #getMode() mode} is set to - * {@link Mode#Auto}. - * - * @see Mode - * @see #getCameraUserInstanceData(com.jme3.renderer.Camera) - */ - public void setCameraUserInstanceData(Camera camera, VertexBuffer cameraInstanceData) { - if (mode == Mode.Auto) { - throw new IllegalStateException("Not allowed in auto mode"); - } - if (camera == null) { - throw new IllegalArgumentException("camera cannot be null"); - } - instanceDataPerCam.put(camera, cameraInstanceData); - } - - /** - * Return camera specific user per-instance data. - * - * Only applies when operating in {@link Mode#Manual}. - * When operating in {@link Mode#Auto}, this data is computed automatically, - * and using this method is not allowed. - * - * @param camera The camera to look up the per-instance data for. - * @return The per-instance data, or null if none was specified - * for the given camera. - * - * @throws IllegalArgumentException If camera is null. - * @throws IllegalStateException If {@link #getMode() mode} is set to - * {@link Mode#Auto}. - * - * @see Mode - * @see #setCameraUserInstanceData(com.jme3.renderer.Camera, com.jme3.scene.VertexBuffer) + * @param transformInstanceData The transforms for each instance. */ - public VertexBuffer getCameraUserInstanceData(Camera camera) { - if (mode == Mode.Auto) { - throw new IllegalStateException("Not allowed in auto mode"); - } - if (camera == null) { - throw new IllegalArgumentException("camera cannot be null"); - } - return instanceDataPerCam.get(camera); + public void setTransformUserInstanceData(VertexBuffer transformInstanceData) { + this.transformInstanceData = transformInstanceData; } /** - * Return a read only map with the mappings between cameras and camera - * specific per-instance data. - * - * Only applies when operating in {@link Mode#Manual}. - * When operating in {@link Mode#Auto}, this data is computed automatically, - * and using this method is not allowed. - * - * @return read only map with the mappings between cameras and camera - * specific per-instance data. - * - * @throws IllegalStateException If {@link #getMode() mode} is set to - * {@link Mode#Auto}. + * Return user per-instance transform data. * - * @see Mode - * @see #setCameraUserInstanceData(com.jme3.renderer.Camera, com.jme3.scene.VertexBuffer) + * @return The per-instance transform data. + * + * @see #setTransformUserInstanceData(com.jme3.scene.VertexBuffer) */ - public Map getAllCameraUserInstanceData() { - if (mode == Mode.Auto) { - throw new IllegalStateException("Not allowed in auto mode"); - } - return Collections.unmodifiableMap(instanceDataPerCam); + public VertexBuffer getTransformUserInstanceData() { + return transformInstanceData; } - private void updateInstance(Matrix4f viewMatrix, Matrix4f worldMatrix, float[] store, int offset) { - viewMatrix.mult(worldMatrix, tempMat4); - tempMat4.toRotationMatrix(tempMat3); + private void updateInstance(Matrix4f worldMatrix, float[] store, + int offset, Matrix3f tempMat3, + Quaternion tempQuat) { + worldMatrix.toRotationMatrix(tempMat3); tempMat3.invertLocal(); - + // NOTE: No need to take the transpose in order to encode // into quaternion, the multiplication in the shader is vec * quat // apparently... tempQuat.fromRotationMatrix(tempMat3); - + // Column-major encoding. The "W" field in each of the encoded // vectors represents the quaternion. - store[offset + 0] = tempMat4.m00; - store[offset + 1] = tempMat4.m10; - store[offset + 2] = tempMat4.m20; + store[offset + 0] = worldMatrix.m00; + store[offset + 1] = worldMatrix.m10; + store[offset + 2] = worldMatrix.m20; store[offset + 3] = tempQuat.getX(); - store[offset + 4] = tempMat4.m01; - store[offset + 5] = tempMat4.m11; - store[offset + 6] = tempMat4.m21; + store[offset + 4] = worldMatrix.m01; + store[offset + 5] = worldMatrix.m11; + store[offset + 6] = worldMatrix.m21; store[offset + 7] = tempQuat.getY(); - store[offset + 8] = tempMat4.m02; - store[offset + 9] = tempMat4.m12; - store[offset + 10] = tempMat4.m22; + store[offset + 8] = worldMatrix.m02; + store[offset + 9] = worldMatrix.m12; + store[offset + 10] = worldMatrix.m22; store[offset + 11] = tempQuat.getZ(); - store[offset + 12] = tempMat4.m03; - store[offset + 13] = tempMat4.m13; - store[offset + 14] = tempMat4.m23; + store[offset + 12] = worldMatrix.m03; + store[offset + 13] = worldMatrix.m13; + store[offset + 14] = worldMatrix.m23; store[offset + 15] = tempQuat.getW(); } - private void renderFromControl(Camera cam) { - if (mode != Mode.Auto) { - return; - } - - // Get the instance data VBO for this camera. - VertexBuffer instanceDataVB = instanceDataPerCam.get(cam); - FloatBuffer instanceData; - - if (instanceDataVB == null) { - // This is a new camera, create instance data VBO for it. - instanceData = BufferUtils.createFloatBuffer(worldMatrices.length * INSTANCE_SIZE); - instanceDataVB = new VertexBuffer(Type.InstanceData); - instanceDataVB.setInstanced(true); - instanceDataVB.setupData(Usage.Stream, INSTANCE_SIZE, Format.Float, instanceData); - instanceDataPerCam.put(cam, instanceDataVB); - } else { - // Retrieve the current instance data buffer. - instanceData = (FloatBuffer) instanceDataVB.getData(); - } - - Matrix4f viewMatrix = cam.getViewMatrix(); - - instanceData.limit(instanceData.capacity()); - instanceData.position(0); - - assert currentNumInstances <= worldMatrices.length; - - for (int i = 0; i < currentNumInstances; i++) { - Matrix4f worldMatrix = worldMatrices[i]; - if (worldMatrix == null) { - worldMatrix = Matrix4f.IDENTITY; - } - updateInstance(viewMatrix, worldMatrix, tempFloatArray, 0); - instanceData.put(tempFloatArray); - } - - instanceData.flip(); - - this.lastCamera = cam; - instanceDataVB.updateData(instanceDataVB.getData()); - } - - /** - * Set the current number of instances to be rendered. - * - * @param currentNumInstances the current number of instances to be rendered. - * - * @throws IllegalArgumentException If current number of instances is - * greater than the maximum number of instances. - */ - public void setCurrentNumInstances(int currentNumInstances) { - if (currentNumInstances > worldMatrices.length) { - throw new IllegalArgumentException("currentNumInstances cannot be larger than maxNumInstances"); - } - this.currentNumInstances = currentNumInstances; - } - /** * Set the maximum amount of instances that can be rendered by this * instanced geometry when mode is set to auto. @@ -415,88 +176,164 @@ public class InstancedGeometry extends Geometry { * @throws IllegalStateException If mode is set to manual. * @throws IllegalArgumentException If maxNumInstances is zero or negative */ - public void setMaxNumInstances(int maxNumInstances) { - if (mode == Mode.Manual) { - throw new IllegalStateException("Not allowed in manual mode"); - } + public final void setMaxNumInstances(int maxNumInstances) { if (maxNumInstances < 1) { throw new IllegalArgumentException("maxNumInstances must be 1 or higher"); } - this.worldMatrices = new Matrix4f[maxNumInstances]; + Geometry[] originalGeometries = geometries; + this.geometries = new Geometry[maxNumInstances]; - if (currentNumInstances > maxNumInstances) { - currentNumInstances = maxNumInstances; + if (originalGeometries != null) { + System.arraycopy(originalGeometries, 0, geometries, 0, originalGeometries.length); } - // Resize instance data for each of the cameras. - for (VertexBuffer instanceDataVB : instanceDataPerCam.values()) { - FloatBuffer instanceData = (FloatBuffer) instanceDataVB.getData(); - if (instanceData.capacity() / INSTANCE_SIZE != worldMatrices.length) { - // Delete old data. - BufferUtils.destroyDirectBuffer(instanceData); - - // Resize instance data for this camera. - // Create new data with new length. - instanceData = BufferUtils.createFloatBuffer(worldMatrices.length * INSTANCE_SIZE); - instanceDataVB.updateData(instanceData); - } + // Resize instance data. + if (transformInstanceData != null) { + BufferUtils.destroyDirectBuffer(transformInstanceData.getData()); + transformInstanceData.updateData(BufferUtils.createFloatBuffer(geometries.length * INSTANCE_SIZE)); + } else if (transformInstanceData == null) { + transformInstanceData = new VertexBuffer(Type.InstanceData); + transformInstanceData.setInstanced(true); + transformInstanceData.setupData(Usage.Stream, + INSTANCE_SIZE, + Format.Float, + BufferUtils.createFloatBuffer(geometries.length * INSTANCE_SIZE)); } } public int getMaxNumInstances() { - return worldMatrices.length; + return geometries.length; } - public int getCurrentNumInstances() { - return currentNumInstances; + public int getActualNumInstances() { + return firstUnusedIndex; } - public void setInstanceTransform(int instanceIndex, Matrix4f worldTransform) { - if (mode == Mode.Manual) { - throw new IllegalStateException("Not allowed in manual mode"); + private void swap(int idx1, int idx2) { + Geometry g = geometries[idx1]; + geometries[idx1] = geometries[idx2]; + geometries[idx2] = g; + + if (geometries[idx1] != null) { + InstancedNode.setGeometryStartIndex2(geometries[idx1], idx1); } - if (worldTransform == null) { - throw new IllegalArgumentException("worldTransform cannot be null"); + if (geometries[idx2] != null) { + InstancedNode.setGeometryStartIndex2(geometries[idx2], idx2); } - if (instanceIndex < 0) { - throw new IllegalArgumentException("instanceIndex cannot be smaller than zero"); + } + + private void sanitize(boolean insideEntriesNonNull) { + if (firstUnusedIndex >= geometries.length) { + throw new AssertionError(); } - if (instanceIndex >= currentNumInstances) { - throw new IllegalArgumentException("instanceIndex cannot be larger than currentNumInstances"); + for (int i = 0; i < geometries.length; i++) { + if (i < firstUnusedIndex) { + if (geometries[i] == null) { + if (insideEntriesNonNull) { + throw new AssertionError(); + } + } else if (InstancedNode.getGeometryStartIndex2(geometries[i]) != i) { + throw new AssertionError(); + } + } else { + if (geometries[i] != null) { + throw new AssertionError(); + } + } } - // TODO: Determine if need to make a copy of matrix or just doing this - // is fine. - worldMatrices[instanceIndex] = worldTransform; } - public void setInstanceTransform(int instanceIndex, Transform worldTransform) { - if (worldTransform == null) { - throw new IllegalArgumentException("worldTransform cannot be null"); + public void updateInstances() { + FloatBuffer fb = (FloatBuffer) transformInstanceData.getData(); + fb.limit(fb.capacity()); + fb.position(0); + + TempVars vars = TempVars.get(); + { + float[] temp = vars.matrixWrite; + + for (int i = 0; i < firstUnusedIndex; i++) { + Geometry geom = geometries[i]; + + if (geom == null) { + geom = geometries[firstUnusedIndex - 1]; + + if (geom == null) { + throw new AssertionError(); + } + + swap(i, firstUnusedIndex - 1); + + while (geometries[firstUnusedIndex -1] == null) { + firstUnusedIndex--; + } + } + + Matrix4f worldMatrix = geom.getWorldMatrix(); + updateInstance(worldMatrix, temp, 0, vars.tempMat3, vars.quat1); + fb.put(temp); + } } + vars.release(); - // Compute the world transform matrix. - tempMat4.loadIdentity(); - tempMat4.setRotationQuaternion(worldTransform.getRotation()); - tempMat4.setTranslation(worldTransform.getTranslation()); - tempMat4_2.loadIdentity(); - tempMat4_2.scale(worldTransform.getScale()); - tempMat4.multLocal(tempMat4_2); + fb.flip(); - setInstanceTransform(instanceIndex, tempMat4.clone()); + if (fb.limit() / INSTANCE_SIZE != firstUnusedIndex) { + throw new AssertionError(); + } + + transformInstanceData.updateData(fb); + } + + public void deleteInstance(Geometry geom) { + int idx = InstancedNode.getGeometryStartIndex2(geom); + InstancedNode.setGeometryStartIndex2(geom, -1); + + geometries[idx] = null; + + if (idx == firstUnusedIndex - 1) { + // Deleting the last element. + // Move index back. + firstUnusedIndex--; + while (geometries[firstUnusedIndex] == null) { + firstUnusedIndex--; + if (firstUnusedIndex < 0) { + break; + } + } + firstUnusedIndex++; + } else { + // Deleting element in the middle + } + } + + public void addInstance(Geometry geometry) { + if (geometry == null) { + throw new IllegalArgumentException("geometry cannot be null"); + } + + // Take an index from the end. + if (firstUnusedIndex + 1 >= geometries.length) { + // No more room. + setMaxNumInstances(getMaxNumInstances() * 2); + } + + int freeIndex = firstUnusedIndex; + firstUnusedIndex++; + + geometries[freeIndex] = geometry; + InstancedNode.setGeometryStartIndex2(geometry, freeIndex); } public VertexBuffer[] getAllInstanceData() { - VertexBuffer instanceDataForCam = instanceDataPerCam.get(lastCamera); ArrayList allData = new ArrayList(); - - if (instanceDataForCam != null) { - allData.add(instanceDataForCam); + if (transformInstanceData != null) { + allData.add(transformInstanceData); } if (globalInstanceData != null) { allData.addAll(Arrays.asList(globalInstanceData)); } - return allData.toArray(new VertexBuffer[allData.size()]); } @@ -504,30 +341,20 @@ public class InstancedGeometry extends Geometry { public void write(JmeExporter exporter) throws IOException { super.write(exporter); OutputCapsule capsule = exporter.getCapsule(this); - capsule.write(currentNumInstances, "cur_num_instances", 1); - capsule.write(mode, "instancing_mode", InstancedGeometry.Mode.Auto); - if (mode == Mode.Auto) { - capsule.write(worldMatrices, "world_matrices", null); - } + //capsule.write(currentNumInstances, "cur_num_instances", 1); + capsule.write(geometries, "geometries", null); } @Override public void read(JmeImporter importer) throws IOException { super.read(importer); InputCapsule capsule = importer.getCapsule(this); - currentNumInstances = capsule.readInt("cur_num_instances", 1); - mode = capsule.readEnum("instancing_mode", InstancedGeometry.Mode.class, - InstancedGeometry.Mode.Auto); - - if (mode == Mode.Auto) { - Savable[] matrixSavables = capsule.readSavableArray("world_matrices", null); - worldMatrices = new Matrix4f[matrixSavables.length]; - for (int i = 0; i < worldMatrices.length; i++) { - worldMatrices[i] = (Matrix4f) matrixSavables[i]; - } + //currentNumInstances = capsule.readInt("cur_num_instances", 1); - control = getControl(InstancedGeometryControl.class); - control.geom = this; + Savable[] geometrySavables = capsule.readSavableArray("geometries", null); + geometries = new Geometry[geometrySavables.length]; + for (int i = 0; i < geometrySavables.length; i++) { + geometries[i] = (Geometry) geometrySavables[i]; } } } diff --git a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java new file mode 100644 index 000000000..fa636c87e --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java @@ -0,0 +1,259 @@ +package com.jme3.scene.instancing; + +import com.jme3.material.Material; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.BatchNode; +import com.jme3.scene.Geometry; +import com.jme3.scene.GeometryGroupNode; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.UserData; +import com.jme3.scene.control.AbstractControl; +import com.jme3.scene.control.Control; +import java.util.HashMap; + +public class InstancedNode extends GeometryGroupNode { + + static int getGeometryStartIndex2(Geometry geom) { + return getGeometryStartIndex(geom); + } + + static void setGeometryStartIndex2(Geometry geom, int startIndex) { + setGeometryStartIndex(geom, startIndex); + } + + private static class InstanceTypeKey implements Cloneable { + + Mesh mesh; + Material material; + int lodLevel; + + public InstanceTypeKey(Mesh mesh, Material material, int lodLevel) { + this.mesh = mesh; + this.material = material; + this.lodLevel = lodLevel; + } + + public InstanceTypeKey(){ + } + + @Override + public int hashCode() { + int hash = 3; + hash = 41 * hash + this.mesh.hashCode(); + hash = 41 * hash + this.material.hashCode(); + hash = 41 * hash + this.lodLevel; + return hash; + } + + @Override + public boolean equals(Object obj) { + final InstanceTypeKey other = (InstanceTypeKey) obj; + if (this.mesh != other.mesh) { + return false; + } + if (this.material != other.material) { + return false; + } + if (this.lodLevel != other.lodLevel) { + return false; + } + return true; + } + + @Override + public InstanceTypeKey clone() { + try { + return (InstanceTypeKey) super.clone(); + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + } + + private static class InstancedNodeControl extends AbstractControl { + + private InstancedNode node; + + public InstancedNodeControl() { + } + + public InstancedNodeControl(InstancedNode node) { + this.node = node; + } + + @Override + public Control cloneForSpatial(Spatial spatial) { + return this; + // WARNING: Sets wrong control on spatial. Will be + // fixed automatically by InstancedNode.clone() method. + } + + @Override + protected void controlUpdate(float tpf) { + } + + @Override + protected void controlRender(RenderManager rm, ViewPort vp) { + node.renderFromControl(); + } + } + + protected final HashMap igByGeom + = new HashMap(); + + private final InstanceTypeKey lookUp = new InstanceTypeKey(); + + private final HashMap instancesMap = + new HashMap(); + + public InstancedNode() { + super(); + // NOTE: since we are deserializing, + // the control is going to be added automatically here. + } + + public InstancedNode(String name) { + super(name); + addControl(new InstancedNodeControl(this)); + } + + private void renderFromControl() { + for (InstancedGeometry ig : instancesMap.values()) { + ig.updateInstances(); + } + } + + private static boolean isInstancedGeometry(Geometry geom) { + return geom instanceof InstancedGeometry; + } + + private InstancedGeometry lookUpByGeometry(Geometry geom) { + lookUp.mesh = geom.getMesh(); + lookUp.material = geom.getMaterial(); + lookUp.lodLevel = geom.getLodLevel(); + + InstancedGeometry ig = instancesMap.get(lookUp); + + if (ig == null) { + ig = new InstancedGeometry( + "material-" + lookUp.material.getMaterialDef().getName() + "," + + "lod-" + lookUp.lodLevel); + ig.setMaterial(lookUp.material); + ig.setMesh(lookUp.mesh); + ig.setUserData(UserData.JME_PHYSICSIGNORE, true); + ig.setCullHint(CullHint.Never); + instancesMap.put(lookUp.clone(), ig); + attachChild(ig); + } + + return ig; + } + + private void removeFromInstancedGeometry(Geometry geom) { + InstancedGeometry ig = igByGeom.remove(geom); + if (ig != null) { + ig.deleteInstance(geom); + } + } + + private void ungroupSceneGraph(Spatial s) { + if (s instanceof Node) { + for (Spatial sp : ((Node) s).getChildren()) { + ungroupSceneGraph(sp); + } + } else if (s instanceof Geometry) { + Geometry g = (Geometry) s; + if (g.isGrouped()) { + // Will invoke onGeometryUnassociated automatically. + g.unassociateFromGroupNode(); + if (InstancedNode.getGeometryStartIndex(g) != -1) { + throw new AssertionError(); + } + } + } + } + + @Override + public Spatial detachChildAt(int index) { + Spatial s = super.detachChildAt(index); + if (s instanceof Node) { + ungroupSceneGraph(s); + } + return s; + } + + private void instance(Spatial n) { + if (n instanceof Geometry) { + Geometry g = (Geometry) n; + if (!g.isGrouped() && g.getBatchHint() != BatchHint.Never) { + InstancedGeometry ig = lookUpByGeometry(g); + igByGeom.put(g, ig); + g.associateWithGroupNode(this, 0); + ig.addInstance(g); + } + } else if (n instanceof Node) { + for (Spatial child : ((Node) n).getChildren()) { + if (child instanceof GeometryGroupNode) { + continue; + } + instance(child); + } + } + } + + public void instance() { + instance(this); + } + + @Override + public Node clone(boolean cloneMaterials) { + InstancedNode clone = (InstancedNode)super.clone(cloneMaterials); + if (instancesMap.size() > 0) { + // Remove all instanced geometries from the clone + for (int i = 0; i < clone.children.size(); i++) { + if (clone.children.get(i) instanceof InstancedGeometry) { + clone.children.remove(i); + } + } + + // Clear state (which is incorrect) + clone.igByGeom.clear(); + clone.instancesMap.clear(); + clone.instance(); + } + return clone; + } + + private void majorChange(Geometry geom) { + InstancedGeometry oldIG = igByGeom.get(geom); + InstancedGeometry newIG = lookUpByGeometry(geom); + if (oldIG != newIG) { + oldIG.deleteInstance(geom); + newIG.addInstance(geom); + igByGeom.put(geom, newIG); + } + } + + @Override + public void onTransformChange(Geometry geom) { + // Handled automatically + } + + @Override + public void onMaterialChange(Geometry geom) { + majorChange(geom); + } + + @Override + public void onMeshChange(Geometry geom) { + majorChange(geom); + } + + @Override + public void onGeoemtryUnassociated(Geometry geom) { + removeFromInstancedGeometry(geom); + } +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.j3md index a2e2172c1..63b7470b2 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.j3md @@ -10,6 +10,8 @@ MaterialDef Debug Normals { WorldParameters { WorldViewProjectionMatrix + ViewProjectionMatrix + ViewMatrix ProjectionMatrix } diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md index 5a57426d4..40d64cb7e 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md @@ -59,7 +59,8 @@ MaterialDef Unshaded { WorldParameters { WorldViewProjectionMatrix - ProjectionMatrix + ViewProjectionMatrix + ViewMatrix } Defines { diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib index 4bf3fc1e9..ae34935c5 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib @@ -23,6 +23,7 @@ uniform mat4 g_ViewMatrix; uniform mat4 g_ProjectionMatrix; uniform mat4 g_WorldViewMatrix; uniform mat4 g_WorldViewProjectionMatrix; +uniform mat4 g_ViewProjectionMatrix; uniform mat3 g_NormalMatrix; #if defined INSTANCING @@ -37,29 +38,36 @@ uniform mat3 g_NormalMatrix; // 2 vertex attributes which now can be used for additional per-vertex data. attribute mat4 inInstanceData; -// Extract the world view matrix out of the instance data, leaving out the +// Extract the world matrix out of the instance data, leaving out the // quaternion at the end. -mat4 worldViewMatrix = mat4(vec4(inInstanceData[0].xyz, 0.0), +mat4 worldMatrix = mat4(vec4(inInstanceData[0].xyz, 0.0), vec4(inInstanceData[1].xyz, 0.0), vec4(inInstanceData[2].xyz, 0.0), vec4(inInstanceData[3].xyz, 1.0)); +vec4 TransformWorld(vec4 position) +{ + return (worldMatrix * position); +} vec4 TransformWorldView(vec4 position) { - return worldViewMatrix * position; + return g_ViewMatrix * TransformWorld(position); } vec4 TransformWorldViewProjection(vec4 position) { - return g_ProjectionMatrix * TransformWorldView(position); + return g_ViewProjectionMatrix * TransformWorld(position); } vec3 TransformNormal(vec3 vec) { vec4 quat = vec4(inInstanceData[0].w, inInstanceData[1].w, inInstanceData[2].w, inInstanceData[3].w); - return vec + vec3(2.0) * cross(cross(vec, quat.xyz) + vec3(quat.w) * vec, quat.xyz); + + vec3 worldNormal = vec + vec3(2.0) * cross(cross(vec, quat.xyz) + vec3(quat.w) * vec, quat.xyz); + + return (g_ViewMatrix * vec4(worldNormal, 0.0)).xyz; } // Prevent user from using g_** matrices which will have invalid data in this case. diff --git a/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNode.java b/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNode.java new file mode 100644 index 000000000..c99406122 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNode.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.scene.instancing; + +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Spatial; +import com.jme3.scene.Spatial.CullHint; +import com.jme3.scene.instancing.InstancedGeometry; +import com.jme3.scene.instancing.InstancedNode; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Sphere; +import com.jme3.system.AppSettings; + +public class TestInstanceNode extends SimpleApplication { + + private Mesh mesh1; + private Mesh mesh2; + private final Material[] materials = new Material[6]; + private InstancedNode instancedNode; + private float time = 0; + + public static void main(String[] args){ + TestInstanceNode app = new TestInstanceNode(); + AppSettings settings = new AppSettings(true); + settings.setVSync(false); + app.setSettings(settings); + app.start(); + } + + private Geometry createInstance(float x, float z) { + Mesh mesh; + if (FastMath.nextRandomInt(0, 1) == 1) mesh = mesh2; + else mesh = mesh1; + Geometry geometry = new Geometry("randomGeom", mesh); + geometry.setMaterial(materials[FastMath.nextRandomInt(0, materials.length - 1)]); + geometry.setLocalTranslation(x, 0, z); + return geometry; + } + + @Override + public void simpleInitApp() { + mesh1 = new Sphere(13, 13, 0.4f, true, false); + mesh2 = new Box(0.4f, 0.4f, 0.4f); + + materials[0] = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + materials[0].setBoolean("UseInstancing", true); + materials[0].setColor("Color", ColorRGBA.Red); + + materials[1] = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + materials[1].setBoolean("UseInstancing", true); + materials[1].setColor("Color", ColorRGBA.Green); + + materials[2] = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + materials[2].setBoolean("UseInstancing", true); + materials[2].setColor("Color", ColorRGBA.Blue); + + materials[3] = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + materials[3].setBoolean("UseInstancing", true); + materials[3].setColor("Color", ColorRGBA.Cyan); + + materials[4] = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + materials[4].setBoolean("UseInstancing", true); + materials[4].setColor("Color", ColorRGBA.Magenta); + + materials[5] = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + materials[5].setBoolean("UseInstancing", true); + materials[5].setColor("Color", ColorRGBA.Yellow); + + instancedNode = new InstancedNode("instanced_node"); + instancedNode.setCullHint(CullHint.Never); + + rootNode.attachChild(instancedNode); + + int extent = 30; + + for (int y = -extent; y < extent; y++) { + for (int x = -extent; x < extent; x++) { + Geometry instance = createInstance(x, y); + + float height = (smoothstep(0, 1, FastMath.nextRandomFloat()) * 2.5f) - 1.25f; + instance.setUserData("height", height); + instance.setUserData("dir", 1f); + + instancedNode.attachChild(instance); + } + } + + instancedNode.instance(); + + cam.setLocation(new Vector3f(38.373516f, 6.689055f, 38.482082f)); + cam.setRotation(new Quaternion(-0.04004206f, 0.918326f, -0.096310444f, -0.38183528f)); + flyCam.setMoveSpeed(15); + //flyCam.setEnabled(false); + } + + private float smoothstep(float edge0, float edge1, float x) { + // Scale, bias and saturate x to 0..1 range + x = FastMath.clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f); + // Evaluate polynomial + return x * x * (3 - 2 * x); + } + + + @Override + public void simpleUpdate(float tpf) { + time += tpf; + + if (time > 1f) { + time = 0f; + + for (Spatial instance : instancedNode.getChildren()) { + if (!(instance instanceof InstancedGeometry)) { + Geometry geom = (Geometry) instance; + geom.setMaterial(materials[FastMath.nextRandomInt(0, materials.length - 1)]); + + Mesh mesh; + if (FastMath.nextRandomInt(0, 1) == 1) mesh = mesh2; + else mesh = mesh1; + geom.setMesh(mesh); + } + } + } + + for (Spatial child : instancedNode.getChildren()) { + if (!(child instanceof InstancedGeometry)) { + float val = child.getUserData("height"); + float dir = child.getUserData("dir"); + + val += (dir + ((FastMath.nextRandomFloat() * 0.5f) - 0.25f)) * tpf; + + if (val > 1f) { + val = 1f; + dir = -dir; + } else if (val < 0f) { + val = 0f; + dir = -dir; + } + + Vector3f translation = child.getLocalTranslation(); + translation.y = (smoothstep(0, 1, val) * 2.5f) - 1.25f; + + child.setUserData("height", val); + child.setUserData("dir", dir); + + child.setLocalTranslation(translation); + } + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstancing.java b/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstancing.java deleted file mode 100644 index 2496b4cb2..000000000 --- a/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstancing.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package jme3test.scene.instancing; - -import com.jme3.app.SimpleApplication; -import com.jme3.input.KeyInput; -import com.jme3.input.controls.ActionListener; -import com.jme3.input.controls.AnalogListener; -import com.jme3.input.controls.KeyTrigger; -import com.jme3.material.Material; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.scene.Geometry; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.Spatial.CullHint; -import com.jme3.scene.instancing.InstancedGeometry; -import com.jme3.scene.shape.Sphere; - -public class TestInstancing extends SimpleApplication { - - private InstancedGeometry instancedGeometry; - private Node instancedGeoms; - private Material material; - private boolean enabled = true; - - public static void main(String[] args){ - TestInstancing app = new TestInstancing(); - //app.setShowSettings(false); - //app.setDisplayFps(false); - //app.setDisplayStatView(false); - app.start(); - } - - private Geometry createInstance(float x, float z) { - // Note: it doesn't matter what mesh or material we set here. - Geometry geometry = new Geometry("randomGeom", instancedGeometry.getMesh()); - geometry.setMaterial(instancedGeometry.getMaterial()); - geometry.setLocalTranslation(x, 0, z); - return geometry; - } - - @Override - public void simpleInitApp() { - initInputs(); - - Sphere sphere = new Sphere(10, 10, 0.5f, true, false); - material = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md"); - material.setBoolean("UseInstancing", true); - - instancedGeometry = new InstancedGeometry(InstancedGeometry.Mode.Auto, "instanced_geom"); - instancedGeometry.setMaxNumInstances(60 * 60); - instancedGeometry.setCurrentNumInstances(60 * 60); - instancedGeometry.setCullHint(CullHint.Never); - instancedGeometry.setMesh(sphere); - instancedGeometry.setMaterial(material); - rootNode.attachChild(instancedGeometry); - - instancedGeoms = new Node("instances_node"); - - // Important: Do not render these geometries, only - // use their world transforms to instance them via - // InstancedGeometry. - instancedGeoms.setCullHint(CullHint.Always); - - for (int y = -30; y < 30; y++) { - for (int x = -30; x < 30; x++) { - Geometry instance = createInstance(x, y); - instancedGeoms.attachChild(instance); - } - } - - rootNode.attachChild(instancedGeoms); - rootNode.setCullHint(CullHint.Never); - - int instanceIndex = 0; - for (Spatial child : instancedGeoms.getChildren()) { - if (instanceIndex < instancedGeometry.getMaxNumInstances()) { - instancedGeometry.setInstanceTransform(instanceIndex++, child.getWorldTransform()); - } - } - - instancedGeometry.setCurrentNumInstances(instanceIndex); - - cam.setLocation(new Vector3f(38.373516f, 6.689055f, 38.482082f)); - cam.setRotation(new Quaternion(-0.04004206f, 0.918326f, -0.096310444f, -0.38183528f)); - flyCam.setMoveSpeed(15); - } - - private void initInputs() { - inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE)); - - ActionListener acl = new ActionListener() { - - public void onAction(String name, boolean keyPressed, float tpf) { - if (name.equals("toggle") && keyPressed) { - if (enabled) { - enabled = false; - instancedGeoms.setCullHint(CullHint.Dynamic); - instancedGeometry.setCullHint(CullHint.Always); - material.setBoolean("UseInstancing", false); - System.out.println("Instancing OFF"); - } else { - enabled = true; - instancedGeoms.setCullHint(CullHint.Always); - instancedGeometry.setCullHint(CullHint.Never); - material.setBoolean("UseInstancing", true); - System.out.println("Instancing ON"); - } - } - } - }; - - inputManager.addListener(acl, "toggle"); - } -} From 564a08672f7a31a7748ed536dd23ef27f060596d Mon Sep 17 00:00:00 2001 From: shadowislord Date: Sat, 28 Jun 2014 19:34:53 -0400 Subject: [PATCH 07/23] * Remove InstancedNode.setCullHint(CullHint.Never) from TestInstancedNode example as it is not neccessary --- .../src/main/java/com/jme3/scene/instancing/InstancedNode.java | 1 - .../main/java/jme3test/scene/instancing/TestInstanceNode.java | 2 -- 2 files changed, 3 deletions(-) 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 index fa636c87e..5e5c595ef 100644 --- a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java @@ -3,7 +3,6 @@ package com.jme3.scene.instancing; import com.jme3.material.Material; import com.jme3.renderer.RenderManager; import com.jme3.renderer.ViewPort; -import com.jme3.scene.BatchNode; import com.jme3.scene.Geometry; import com.jme3.scene.GeometryGroupNode; import com.jme3.scene.Mesh; diff --git a/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNode.java b/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNode.java index c99406122..3efcb75d3 100644 --- a/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNode.java +++ b/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNode.java @@ -41,7 +41,6 @@ import com.jme3.math.Vector3f; import com.jme3.scene.Geometry; import com.jme3.scene.Mesh; import com.jme3.scene.Spatial; -import com.jme3.scene.Spatial.CullHint; import com.jme3.scene.instancing.InstancedGeometry; import com.jme3.scene.instancing.InstancedNode; import com.jme3.scene.shape.Box; @@ -104,7 +103,6 @@ public class TestInstanceNode extends SimpleApplication { materials[5].setColor("Color", ColorRGBA.Yellow); instancedNode = new InstancedNode("instanced_node"); - instancedNode.setCullHint(CullHint.Never); rootNode.attachChild(instancedNode); From 4fdce777b5c91cdcbab9ff128c9c602a34cc75f7 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Sat, 28 Jun 2014 20:02:25 -0400 Subject: [PATCH 08/23] * Fix ParticleEmitter.control reference which became incorrect after cloning --- .../src/main/java/com/jme3/effect/ParticleEmitter.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) 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) { From 4ae99f9d5d599cca274a543d93425951c66edaa2 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Sat, 28 Jun 2014 20:49:21 -0400 Subject: [PATCH 09/23] * Fix incorrect reset of Geometry members after cloning --- jme3-core/src/main/java/com/jme3/scene/Geometry.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 b33152c07..50ca01912 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Geometry.java +++ b/jme3-core/src/main/java/com/jme3/scene/Geometry.java @@ -486,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 = -1; + if (geomClone.isGrouped()) { + geomClone.groupNode = null; + geomClone.startIndex = -1; } geomClone.cachedWorldMat = cachedWorldMat.clone(); From 6ddc68278ba84e2b43854c79c448ae37f0e8e469 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Sat, 28 Jun 2014 20:50:10 -0400 Subject: [PATCH 10/23] * Make sure InstancedGeometry is non-batchable spatial --- .../java/com/jme3/scene/instancing/InstancedGeometry.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 d9259ab99..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 @@ -68,6 +68,7 @@ public class InstancedGeometry extends Geometry { public InstancedGeometry() { super(); setIgnoreTransform(true); + setBatchHint(BatchHint.Never); setMaxNumInstances(1); } @@ -81,6 +82,7 @@ public class InstancedGeometry extends Geometry { public InstancedGeometry(String name) { super(name); setIgnoreTransform(true); + setBatchHint(BatchHint.Never); setMaxNumInstances(1); } @@ -326,6 +328,10 @@ public class InstancedGeometry extends Geometry { InstancedNode.setGeometryStartIndex2(geometry, freeIndex); } + public Geometry[] getGeometries() { + return geometries; + } + public VertexBuffer[] getAllInstanceData() { ArrayList allData = new ArrayList(); if (transformInstanceData != null) { From fa41da59a4a43d99294cb35c1c0a95aa0c862bbf Mon Sep 17 00:00:00 2001 From: shadowislord Date: Sat, 28 Jun 2014 20:52:30 -0400 Subject: [PATCH 11/23] * Fix outstanding cloning related issues in InstancedNode * Throw exception if the geometry's material does not support instancing * Test the cloning function in TestInstanceNode --- .../jme3/scene/instancing/InstancedNode.java | 120 ++++++++++++------ .../scene/instancing/TestInstanceNode.java | 26 ++-- 2 files changed, 100 insertions(+), 46 deletions(-) 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 index 5e5c595ef..61222cc2f 100644 --- a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java @@ -9,8 +9,11 @@ import com.jme3.scene.Mesh; import com.jme3.scene.Node; import com.jme3.scene.Spatial; import com.jme3.scene.UserData; -import com.jme3.scene.control.AbstractControl; import com.jme3.scene.control.Control; +import 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 { @@ -72,7 +75,7 @@ public class InstancedNode extends GeometryGroupNode { } } - private static class InstancedNodeControl extends AbstractControl { + private static class InstancedNodeControl implements Control { private InstancedNode node; @@ -90,22 +93,31 @@ public class InstancedNode extends GeometryGroupNode { // fixed automatically by InstancedNode.clone() method. } - @Override - protected void controlUpdate(float tpf) { + public void setSpatial(Spatial spatial){ } - - @Override - protected void controlRender(RenderManager rm, ViewPort vp) { + + 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 final HashMap igByGeom + protected InstancedNodeControl control; + + protected HashMap igByGeom = new HashMap(); - private final InstanceTypeKey lookUp = new InstanceTypeKey(); + private InstanceTypeKey lookUp = new InstanceTypeKey(); - private final HashMap instancesMap = + private HashMap instancesMap = new HashMap(); public InstancedNode() { @@ -116,7 +128,8 @@ public class InstancedNode extends GeometryGroupNode { public InstancedNode(String name) { super(name); - addControl(new InstancedNodeControl(this)); + control = new InstancedNodeControl(this); + addControl(control); } private void renderFromControl() { @@ -124,11 +137,7 @@ public class InstancedNode extends GeometryGroupNode { ig.updateInstances(); } } - - private static boolean isInstancedGeometry(Geometry geom) { - return geom instanceof InstancedGeometry; - } - + private InstancedGeometry lookUpByGeometry(Geometry geom) { lookUp.mesh = geom.getMesh(); lookUp.material = geom.getMaterial(); @@ -138,6 +147,7 @@ public class InstancedNode extends GeometryGroupNode { if (ig == null) { ig = new InstancedGeometry( + "mesh-" + System.identityHashCode(lookUp.mesh) + "," + "material-" + lookUp.material.getMaterialDef().getName() + "," + "lod-" + lookUp.lodLevel); ig.setMaterial(lookUp.material); @@ -151,6 +161,21 @@ public class InstancedNode extends GeometryGroupNode { 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) { @@ -158,6 +183,19 @@ public class InstancedNode extends GeometryGroupNode { } } + 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()) { @@ -168,6 +206,7 @@ public class InstancedNode extends GeometryGroupNode { if (g.isGrouped()) { // Will invoke onGeometryUnassociated automatically. g.unassociateFromGroupNode(); + if (InstancedNode.getGeometryStartIndex(g) != -1) { throw new AssertionError(); } @@ -188,10 +227,7 @@ public class InstancedNode extends GeometryGroupNode { if (n instanceof Geometry) { Geometry g = (Geometry) n; if (!g.isGrouped() && g.getBatchHint() != BatchHint.Never) { - InstancedGeometry ig = lookUpByGeometry(g); - igByGeom.put(g, ig); - g.associateWithGroupNode(this, 0); - ig.addInstance(g); + addToInstancedGeometry(g); } } else if (n instanceof Node) { for (Spatial child : ((Node) n).getChildren()) { @@ -207,35 +243,45 @@ public class InstancedNode extends GeometryGroupNode { 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(); + } } } - - // Clear state (which is incorrect) - clone.igByGeom.clear(); - clone.instancesMap.clear(); - clone.instance(); } + + // 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; } - private void majorChange(Geometry geom) { - InstancedGeometry oldIG = igByGeom.get(geom); - InstancedGeometry newIG = lookUpByGeometry(geom); - if (oldIG != newIG) { - oldIG.deleteInstance(geom); - newIG.addInstance(geom); - igByGeom.put(geom, newIG); - } - } - @Override public void onTransformChange(Geometry geom) { // Handled automatically @@ -243,12 +289,12 @@ public class InstancedNode extends GeometryGroupNode { @Override public void onMaterialChange(Geometry geom) { - majorChange(geom); + relocateInInstancedGeometry(geom); } @Override public void onMeshChange(Geometry geom) { - majorChange(geom); + relocateInInstancedGeometry(geom); } @Override diff --git a/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNode.java b/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNode.java index 3efcb75d3..41e711c7f 100644 --- a/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNode.java +++ b/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNode.java @@ -41,6 +41,7 @@ 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; @@ -52,8 +53,9 @@ public class TestInstanceNode extends SimpleApplication { private Mesh mesh1; private Mesh mesh2; private final Material[] materials = new Material[6]; - private InstancedNode instancedNode; + private Node instancedNode; private float time = 0; + private boolean INSTANCING = false; public static void main(String[] args){ TestInstanceNode app = new TestInstanceNode(); @@ -79,27 +81,27 @@ public class TestInstanceNode extends SimpleApplication { mesh2 = new Box(0.4f, 0.4f, 0.4f); materials[0] = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - materials[0].setBoolean("UseInstancing", true); + materials[0].setBoolean("UseInstancing", INSTANCING); materials[0].setColor("Color", ColorRGBA.Red); materials[1] = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - materials[1].setBoolean("UseInstancing", true); + 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", true); + 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", true); + 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", true); + 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", true); + materials[5].setBoolean("UseInstancing", INSTANCING); materials[5].setColor("Color", ColorRGBA.Yellow); instancedNode = new InstancedNode("instanced_node"); @@ -120,12 +122,18 @@ public class TestInstanceNode extends SimpleApplication { } } - instancedNode.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); + flyCam.setEnabled(false); } private float smoothstep(float edge0, float edge1, float x) { From a11bfa5e63449989da13e4b4858aad51288adf18 Mon Sep 17 00:00:00 2001 From: Nehon Date: Sun, 29 Jun 2014 18:40:31 +0200 Subject: [PATCH 12/23] Fixed a bug where grouped geom were rendered in shadow pre-pass because they were still in the shadow cast queue --- jme3-core/src/main/java/com/jme3/renderer/RenderManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); } From 7356b727c33ebf2f6923b6c790eb7faa176ced31 Mon Sep 17 00:00:00 2001 From: Nehon Date: Sun, 29 Jun 2014 18:41:32 +0200 Subject: [PATCH 13/23] Instancing now works with Lighting material, and shadows are supported with instanced geometries --- .../Common/MatDefs/Light/Lighting.j3md | 25 ++++++++++++++++++- .../Common/MatDefs/Light/Lighting.vert | 22 +++++++++------- .../Common/MatDefs/Misc/Unshaded.j3md | 15 +++++++++++ .../Common/MatDefs/Shadow/PostShadow.j3md | 1 + .../Common/MatDefs/Shadow/PostShadow.vert | 6 ++--- .../Common/MatDefs/Shadow/PostShadow15.vert | 7 +++--- .../Common/MatDefs/Shadow/PreShadow.vert | 6 ++--- .../Common/ShaderLib/Instancing.glsllib | 5 ++++ .../resources/Common/MatDefs/SSAO/normal.vert | 1 + 9 files changed, 66 insertions(+), 22 deletions(-) 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..bf28391eb 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/Unshaded.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md index 40d64cb7e..aee284705 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md @@ -87,10 +87,13 @@ MaterialDef Unshaded { WorldViewProjectionMatrix WorldViewMatrix NormalMatrix + ViewProjectionMatrix + ViewMatrix } Defines { NUM_BONES : NumberOfBones + INSTANCING : UseInstancing } } @@ -102,12 +105,15 @@ MaterialDef Unshaded { WorldParameters { WorldViewProjectionMatrix WorldViewMatrix + ViewProjectionMatrix + ViewMatrix } Defines { COLOR_MAP : ColorMap DISCARD_ALPHA : AlphaDiscardThreshold NUM_BONES : NumberOfBones + INSTANCING : UseInstancing } ForcedRenderState { @@ -128,6 +134,8 @@ MaterialDef Unshaded { WorldParameters { WorldViewProjectionMatrix WorldMatrix + ViewProjectionMatrix + ViewMatrix } Defines { @@ -141,6 +149,7 @@ MaterialDef Unshaded { PSSM : Splits POINTLIGHT : LightViewProjectionMatrix5 NUM_BONES : NumberOfBones + INSTANCING : UseInstancing } ForcedRenderState { @@ -157,6 +166,8 @@ MaterialDef Unshaded { WorldParameters { WorldViewProjectionMatrix WorldMatrix + ViewProjectionMatrix + ViewMatrix } Defines { @@ -170,6 +181,7 @@ MaterialDef Unshaded { PSSM : Splits POINTLIGHT : LightViewProjectionMatrix5 NUM_BONES : NumberOfBones + INSTANCING : UseInstancing } ForcedRenderState { @@ -186,6 +198,8 @@ MaterialDef Unshaded { WorldParameters { WorldViewProjectionMatrix + ViewProjectionMatrix + ViewMatrix } Defines { @@ -193,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 ae34935c5..7ed91181d 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib @@ -78,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..0d812a8cb 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.vert +++ b/jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.vert @@ -1,3 +1,4 @@ +#import "Common/ShaderLib/Instancing.glsllib" #import "Common/ShaderLib/Skinning.glsllib" uniform mat4 g_WorldViewProjectionMatrix; uniform mat3 g_NormalMatrix; From 1b7421a1533cd47e97fdb408c0735da90385418c Mon Sep 17 00:00:00 2001 From: Nehon Date: Wed, 2 Jul 2014 20:55:02 +0200 Subject: [PATCH 14/23] Used correct colorSpace in android Image loaders --- .../src/main/java/com/jme3/renderer/android/TextureUtil.java | 1 - .../java/com/jme3/texture/plugins/AndroidImageLoader.java | 4 +++- .../com/jme3/texture/plugins/AndroidNativeImageLoader.java | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) 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; } From 8700871e76750e4dd3b66aefc49f72717879e87b Mon Sep 17 00:00:00 2001 From: neph1 Date: Fri, 4 Jul 2014 17:59:09 +0200 Subject: [PATCH 15/23] Fixed some merge issues (now it's working properly) Added test case for IK --- .../control/KinematicRagdollControl.java | 27 +++--- .../src/main/java/jme3test/bullet/TestIK.java | 93 +++++++++++++++++++ 2 files changed, 109 insertions(+), 11 deletions(-) create mode 100644 jme3-examples/src/main/java/jme3test/bullet/TestIK.java 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 e0ae9855b..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 @@ -196,11 +196,10 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P if (!enabled) { return; } - if(mode == Mode.IK){ - ikUpdate(tpf); - } - //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); @@ -325,15 +324,14 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P while (it.hasNext()) { boneName = it.next(); - Logger.getLogger(KinematicRagdollControl.class.getSimpleName()).log(Level.INFO, "updationg {0}", boneName); bone = (Bone) boneLinks.get(boneName).bone; if (!bone.hasUserControl()) { - Logger.getLogger(KinematicRagdollControl.class.getSimpleName()).log(Level.INFO, "{0} doesn't have user control", boneName); + 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.INFO, "Distance is close enough"); + Logger.getLogger(KinematicRagdollControl.class.getSimpleName()).log(Level.FINE, "Distance is close enough"); continue; } int depth = 0; @@ -735,9 +733,12 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P vars.release(); } - for (Bone bone : skeleton.getRoots()) { - RagdollUtils.setUserControl(bone, mode == Mode.Ragdoll); + if(mode != Mode.IK){ + for (Bone bone : skeleton.getRoots()) { + RagdollUtils.setUserControl(bone, mode == Mode.Ragdoll); + } } + } /** @@ -940,7 +941,7 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P int depth = ikChainDepth.remove(bone.getName()); int i = 0; while (i < depth+2 && bone.getParent() != null) { - if (!bone.hasUserControl()) { + if (bone.hasUserControl()) { // matchPhysicObjectToBone(boneLinks.get(bone.getName()), position, tmpRot1); bone.setUserControl(false); } @@ -1004,6 +1005,10 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P public void setLimbDampening(float limbDampening) { this.limbDampening = limbDampening; } + + public Bone getBone(String name){ + return skeleton.getBone(name); + } /** * serialize this control * 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)); + } + +} From 1a4f07b36a9eb655630c1546d161cea7b1ad9d7f Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Thu, 10 Jul 2014 01:38:19 -0400 Subject: [PATCH 16/23] Fixing tabs to spaces. --- .../java/com/jme3/scene/VertexBuffer.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) 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..203c85a3f 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 } /** From 9b8b730c44de69c56fca2951a9cecec8210997a9 Mon Sep 17 00:00:00 2001 From: pspeed42 Date: Thu, 10 Jul 2014 01:55:29 -0400 Subject: [PATCH 17/23] Added a license header. --- .../jme3/scene/instancing/InstancedNode.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) 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 index 61222cc2f..b3cb26da0 100644 --- a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java @@ -1,3 +1,34 @@ +/* + * 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; From cce7b0f6def1b32051c0037823a286bcd15d2ac2 Mon Sep 17 00:00:00 2001 From: pspeed42 Date: Thu, 10 Jul 2014 02:49:21 -0400 Subject: [PATCH 18/23] More tab eradication. --- .../src/main/resources/Common/MatDefs/Light/Lighting.j3md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 bf28391eb..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,8 +132,8 @@ MaterialDef Phong Lighting { // For hardware skinning Int NumberOfBones Matrix4Array BoneMatrices - - Boolean UseInstancing + + Boolean UseInstancing } Technique { @@ -180,7 +180,7 @@ MaterialDef Phong Lighting { SPHERE_MAP : SphereMap NUM_BONES : NumberOfBones - + INSTANCING : UseInstancing } } @@ -309,7 +309,7 @@ MaterialDef Phong Lighting { WorldParameters { WorldViewProjectionMatrix WorldViewMatrix - NormalMatrix + NormalMatrix ViewProjectionMatrix ViewMatrix } From 61cbf16ebcb0867bf881d81a7d22aceecb7d5dc4 Mon Sep 17 00:00:00 2001 From: pspeed42 Date: Thu, 10 Jul 2014 03:57:00 -0400 Subject: [PATCH 19/23] Fixed the duplicate definition errors in normal.vert ...I left them commented out for reference and because maybe this needs some rethinking. --- .../src/main/resources/Common/MatDefs/SSAO/normal.vert | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 0d812a8cb..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,7 +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; From 7ca599dfd935e44095b81b060043cadb93d73d61 Mon Sep 17 00:00:00 2001 From: pspeed42 Date: Fri, 11 Jul 2014 00:03:04 -0400 Subject: [PATCH 20/23] More tabs to spaces. --- .../src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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..23cd63ce8 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 @@ -2273,7 +2273,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________ From 216f874175802d959cf2acd03f542fe30ff381c3 Mon Sep 17 00:00:00 2001 From: pspeed42 Date: Fri, 11 Jul 2014 01:14:47 -0400 Subject: [PATCH 21/23] Added support for instancing at the mesh level. (so far only for lwjgl because I'm lazy) Went ahead and added in divisor support while I was at it because it doesn't cost anything and there might be use-cases for it. Mesh now knows about instances and you can directly ask it for an instance count. I haven't added support for proper bounding box calculation but it should at least be possible now. --- .../src/main/java/com/jme3/scene/Mesh.java | 26 ++++++++- .../java/com/jme3/scene/VertexBuffer.java | 54 ++++++++++++++++--- .../jme3/renderer/lwjgl/LwjglRenderer.java | 7 ++- 3 files changed, 77 insertions(+), 10 deletions(-) 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 203c85a3f..c67435b78 100644 --- a/jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java +++ b/jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java @@ -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-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java index 23cd63ce8..e3e9393f3 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 @@ -2294,7 +2294,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 +2491,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); From e76e770d17df382d7014a1a467fc9ba18c7ff7b7 Mon Sep 17 00:00:00 2001 From: pspeed42 Date: Fri, 11 Jul 2014 01:38:14 -0400 Subject: [PATCH 22/23] Modified the multi-slot buffer support to work even if the buffer is not instanced. Saw no real reason not to and it makes buffer support more flexible. --- .../main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 e3e9393f3..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"); } From dacaaa547704560487966bf9fea1e59ef283e5bf Mon Sep 17 00:00:00 2001 From: Jan Ivenz Date: Thu, 10 Jul 2014 13:17:05 +0200 Subject: [PATCH 23/23] Fixed clamp method in ColorRGBA. --- jme3-core/src/main/java/com/jme3/math/ColorRGBA.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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); } /**