private void interpolate( Quaternion[] targetQuaternions, float[] targetTimes, float currentTime, Quaternion result) { int index = 0; for (int i = 1; i < targetTimes.length; ++i) { if (targetTimes[i] < currentTime) { ++index; } else { break; } } if (index >= targetTimes.length - 1) { result.set(targetQuaternions[targetTimes.length - 1]); } else { float delta = targetTimes[index + 1] - targetTimes[index]; if (delta == 0.0f) { result.set(targetQuaternions[index + 1]); } else { float scale = (currentTime - targetTimes[index]) / (targetTimes[index + 1] - targetTimes[index]); result.slerp(targetQuaternions[index], targetQuaternions[index + 1], scale); } } }
/** * Smoothly blend from Ragdoll mode to Kinematic mode This is useful to blend ragdoll actual * position to a keyframe animation for example * * @param blendTime the blending time between ragdoll to anim. */ public void blendToKinematicMode(float blendTime) { if (mode == Mode.Kinematic) { return; } blendedControl = true; this.blendTime = blendTime; mode = Mode.Kinematic; AnimControl animControl = targetModel.getControl(AnimControl.class); animControl.setEnabled(true); TempVars vars = TempVars.get(); for (PhysicsBoneLink link : boneLinks.values()) { Vector3f p = link.rigidBody.getMotionState().getWorldLocation(); Vector3f position = vars.vect1; targetModel.getWorldTransform().transformInverseVector(p, position); Quaternion q = link.rigidBody.getMotionState().getWorldRotationQuat(); Quaternion q2 = vars.quat1; Quaternion q3 = vars.quat2; q2.set(q).multLocal(link.initalWorldRotation).normalizeLocal(); q3.set(targetModel.getWorldRotation()).inverseLocal().mult(q2, q2); q2.normalizeLocal(); link.startBlendingPos.set(position); link.startBlendingRot.set(q2); link.rigidBody.setKinematic(true); } vars.release(); for (Bone bone : skeleton.getRoots()) { RagdollUtils.setUserControl(bone, false); } blendStart = 0; }
/** * Set the transforms of a rigidBody to match the transforms of a bone. this is used to make the * ragdoll follow the skeleton motion while in Kinematic mode * * @param link the link containing the bone and the rigidBody * @param position just a temp vector for position * @param tmpRot1 just a temp quaternion for rotation */ private void matchPhysicObjectToBone( PhysicsBoneLink link, Vector3f position, Quaternion tmpRot1) { // computing position from rotation and scale targetModel.getWorldTransform().transformVector(link.bone.getModelSpacePosition(), position); // computing rotation tmpRot1 .set(link.bone.getModelSpaceRotation()) .multLocal(link.bone.getWorldBindInverseRotation()); targetModel.getWorldRotation().mult(tmpRot1, tmpRot1); tmpRot1.normalizeLocal(); // updating physic location/rotation of the physic bone link.rigidBody.setPhysicsLocation(position); link.rigidBody.setPhysicsRotation(tmpRot1); }
/** * <code>lookAt</code> is a convenience method for auto-setting the local rotation based on a * position in world space and an up vector. It computes the rotation to transform the z-axis to * point onto 'position' and the y-axis to 'up'. Unlike {@link * Quaternion#lookAt(com.jme3.math.Vector3f, com.jme3.math.Vector3f) } this method takes a world * position to look at and not a relative direction. * * <p>Note : 28/01/2013 this method has been fixed as it was not taking into account the parent * rotation. This was resulting in improper rotation when the spatial had rotated parent nodes. * This method is intended to work in world space, so no matter what parent graph the spatial has, * it will look at the given position in world space. * * @param position where to look at in terms of world coordinates * @param upVector a vector indicating the (local) up direction. (typically {0, 1, 0} in jME.) */ public void lookAt(Vector3f position, Vector3f upVector) { Vector3f worldTranslation = getWorldTranslation(); TempVars vars = TempVars.get(); Vector3f compVecA = vars.vect4; compVecA.set(position).subtractLocal(worldTranslation); getLocalRotation().lookAt(compVecA, upVector); if (getParent() != null) { Quaternion rot = vars.quat1; rot = rot.set(parent.getWorldRotation()).inverseLocal().multLocal(getLocalRotation()); rot.normalizeLocal(); setLocalRotation(rot); } vars.release(); setTransformRefresh(); }
/** Loads the identity. Equal to translation=0,0,0 scale=1,1,1 rot=0,0,0,1. */ public void loadIdentity() { translation.set(0, 0, 0); scale.set(1, 1, 1); rot.set(0, 0, 0, 1); }
/** * Stores this rotation value into the given Quaternion. If quat is null, a new Quaternion is * created to hold the value. The value, once stored, is returned. * * @param quat The store location for this matrix's rotation. * @return The value of this matrix's rotation. */ public Quaternion getRotation(Quaternion quat) { if (quat == null) quat = new Quaternion(); quat.set(rot); return quat; }
public void update(float tpf) { if (!enabled) { return; } TempVars vars = TempVars.get(); Quaternion tmpRot1 = vars.quat1; Quaternion tmpRot2 = vars.quat2; // 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)) { for (PhysicsBoneLink link : boneLinks.values()) { Vector3f position = vars.vect1; // retrieving bone position in physic world space Vector3f p = link.rigidBody.getMotionState().getWorldLocation(); // transforming this position with inverse transforms of the model targetModel.getWorldTransform().transformInverseVector(p, position); // retrieving bone rotation in physic world space Quaternion q = link.rigidBody.getMotionState().getWorldRotationQuat(); // multiplying this rotation by the initialWorld rotation of the bone, // then transforming it with the inverse world rotation of the model tmpRot1.set(q).multLocal(link.initalWorldRotation); tmpRot2.set(targetModel.getWorldRotation()).inverseLocal().mult(tmpRot1, tmpRot1); tmpRot1.normalizeLocal(); // if the bone is the root bone, we apply the physic's transform to the model, so its // position and rotation are correctly updated if (link.bone.getParent() == null) { // offsetting the physic's position/rotation by the root bone inverse model space // position/rotaion modelPosition.set(p).subtractLocal(link.bone.getWorldBindPosition()); targetModel .getParent() .getWorldTransform() .transformInverseVector(modelPosition, modelPosition); modelRotation .set(q) .multLocal(tmpRot2.set(link.bone.getWorldBindRotation()).inverseLocal()); // applying transforms to the model targetModel.setLocalTranslation(modelPosition); targetModel.setLocalRotation(modelRotation); // Applying computed transforms to the bone link.bone.setUserTransformsWorld(position, tmpRot1); } else { // if boneList is empty, this means that every bone in the ragdoll has a collision shape, // so we just update the bone position if (boneList.isEmpty()) { link.bone.setUserTransformsWorld(position, tmpRot1); } else { // boneList is not empty, this means some bones of the skeleton might not be associated // with a collision shape. // So we update them recusively RagdollUtils.setTransform(link.bone, position, tmpRot1, false, boneList); } } } } else { // the ragdoll does not have the controll, so the keyframed animation updates the physic // position of the physic bonces for (PhysicsBoneLink link : boneLinks.values()) { Vector3f position = vars.vect1; // 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) { Vector3f position2 = vars.vect2; // initializing tmp vars with the start position/rotation of the ragdoll position.set(link.startBlendingPos); tmpRot1.set(link.startBlendingRot); // interpolating between ragdoll position/rotation and keyframed position/rotation tmpRot2.set(tmpRot1).nlerp(link.bone.getModelSpaceRotation(), blendStart / blendTime); position2 .set(position) .interpolate(link.bone.getModelSpacePosition(), blendStart / blendTime); tmpRot1.set(tmpRot2); position.set(position2); // updating bones transforms if (boneList.isEmpty()) { // we ensure we have the control to update the bone link.bone.setUserControl(true); link.bone.setUserTransformsWorld(position, tmpRot1); // we give control back to the key framed animation. link.bone.setUserControl(false); } else { RagdollUtils.setTransform(link.bone, position, tmpRot1, true, boneList); } } // setting skeleton transforms to the ragdoll matchPhysicObjectToBone(link, position, tmpRot1); modelPosition.set(targetModel.getLocalTranslation()); } // time control for blending if (blendedControl) { blendStart += tpf; if (blendStart > blendTime) { blendedControl = false; } } } vars.release(); }
protected void renderMultipassLighting(Shader shader, Geometry g, RenderManager rm) { Renderer r = rm.getRenderer(); LightList lightList = g.getWorldLightList(); Uniform lightDir = shader.getUniform("g_LightDirection"); Uniform lightColor = shader.getUniform("g_LightColor"); Uniform lightPos = shader.getUniform("g_LightPosition"); Uniform ambientColor = shader.getUniform("g_AmbientLightColor"); boolean isFirstLight = true; boolean isSecondLight = false; for (int i = 0; i < lightList.size(); i++) { Light l = lightList.get(i); if (l instanceof AmbientLight) { continue; } if (isFirstLight) { // set ambient color for first light only ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList)); isFirstLight = false; isSecondLight = true; } else if (isSecondLight) { ambientColor.setValue(VarType.Vector4, ColorRGBA.Black); // apply additive blending for 2nd and future lights r.applyRenderState(additiveLight); isSecondLight = false; } TempVars vars = TempVars.get(); Quaternion tmpLightDirection = vars.quat1; Quaternion tmpLightPosition = vars.quat2; ColorRGBA tmpLightColor = vars.color; Vector4f tmpVec = vars.vect4f; ColorRGBA color = l.getColor(); tmpLightColor.set(color); tmpLightColor.a = l.getType().getId(); lightColor.setValue(VarType.Vector4, tmpLightColor); switch (l.getType()) { case Directional: DirectionalLight dl = (DirectionalLight) l; Vector3f dir = dl.getDirection(); tmpLightPosition.set(dir.getX(), dir.getY(), dir.getZ(), -1); lightPos.setValue(VarType.Vector4, tmpLightPosition); tmpLightDirection.set(0, 0, 0, 0); lightDir.setValue(VarType.Vector4, tmpLightDirection); break; case Point: PointLight pl = (PointLight) l; Vector3f pos = pl.getPosition(); float invRadius = pl.getInvRadius(); tmpLightPosition.set(pos.getX(), pos.getY(), pos.getZ(), invRadius); lightPos.setValue(VarType.Vector4, tmpLightPosition); tmpLightDirection.set(0, 0, 0, 0); lightDir.setValue(VarType.Vector4, tmpLightDirection); break; case Spot: SpotLight sl = (SpotLight) l; Vector3f pos2 = sl.getPosition(); Vector3f dir2 = sl.getDirection(); float invRange = sl.getInvSpotRange(); float spotAngleCos = sl.getPackedAngleCos(); tmpLightPosition.set(pos2.getX(), pos2.getY(), pos2.getZ(), invRange); lightPos.setValue(VarType.Vector4, tmpLightPosition); // We transform the spot directoin in view space here to save 5 varying later in the // lighting shader // one vec4 less and a vec4 that becomes a vec3 // the downside is that spotAngleCos decoding happen now in the frag shader. tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(), 0); rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec); tmpLightDirection.set(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos); lightDir.setValue(VarType.Vector4, tmpLightDirection); break; default: throw new UnsupportedOperationException("Unknown type of light: " + l.getType()); } vars.release(); r.setShader(shader); r.renderMesh(g.getMesh(), g.getLodLevel(), 1); } if (isFirstLight && lightList.size() > 0) { // There are only ambient lights in the scene. Render // a dummy "normal light" so we can see the ambient ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList)); lightColor.setValue(VarType.Vector4, ColorRGBA.BlackNoAlpha); lightPos.setValue(VarType.Vector4, nullDirLight); r.setShader(shader); r.renderMesh(g.getMesh(), g.getLodLevel(), 1); } }