com.jme3.scene.plugins.blender.constraints.ConstraintHelper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jme3-blender Show documentation
Show all versions of jme3-blender Show documentation
jMonkeyEngine is a 3D game engine for adventurous Java developers
The newest version!
package com.jme3.scene.plugins.blender.constraints;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import com.jme3.animation.Bone;
import com.jme3.animation.Skeleton;
import com.jme3.math.Matrix4f;
import com.jme3.math.Quaternion;
import com.jme3.math.Transform;
import com.jme3.math.Vector3f;
import com.jme3.scene.Spatial;
import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
import com.jme3.scene.plugins.blender.animations.AnimationHelper;
import com.jme3.scene.plugins.blender.animations.BoneContext;
import com.jme3.scene.plugins.blender.animations.Ipo;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.objects.ObjectHelper;
import com.jme3.util.TempVars;
/**
* This class should be used for constraint calculations.
*
* @author Marcin Roguski (Kaelthas)
*/
public class ConstraintHelper extends AbstractBlenderHelper {
private static final Logger LOGGER = Logger.getLogger(ConstraintHelper.class.getName());
/**
* Helper constructor.
*
* @param blenderVersion
* the version read from the blend file
* @param blenderContext
* the blender context
*/
public ConstraintHelper(String blenderVersion, BlenderContext blenderContext) {
super(blenderVersion, blenderContext);
}
/**
* This method reads constraints for for the given structure. The
* constraints are loaded only once for object/bone.
*
* @param objectStructure
* the structure we read constraint's for
* @param blenderContext
* the blender context
* @throws BlenderFileException
*/
public void loadConstraints(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException {
LOGGER.fine("Loading constraints.");
// reading influence ipos for the constraints
AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
Map> constraintsIpos = new HashMap>();
Pointer pActions = (Pointer) objectStructure.getFieldValue("action");
if (pActions.isNotNull()) {
List actions = pActions.fetchData();
for (Structure action : actions) {
Structure chanbase = (Structure) action.getFieldValue("chanbase");
List actionChannels = chanbase.evaluateListBase();
for (Structure actionChannel : actionChannels) {
Map ipos = new HashMap();
Structure constChannels = (Structure) actionChannel.getFieldValue("constraintChannels");
List constraintChannels = constChannels.evaluateListBase();
for (Structure constraintChannel : constraintChannels) {
Pointer pIpo = (Pointer) constraintChannel.getFieldValue("ipo");
if (pIpo.isNotNull()) {
String constraintName = constraintChannel.getFieldValue("name").toString();
Ipo ipo = animationHelper.fromIpoStructure(pIpo.fetchData().get(0), blenderContext);
ipos.put(constraintName, ipo);
}
}
String actionName = actionChannel.getFieldValue("name").toString();
constraintsIpos.put(actionName, ipos);
}
}
}
// loading constraints connected with the object's bones
Pointer pPose = (Pointer) objectStructure.getFieldValue("pose");
if (pPose.isNotNull()) {
List poseChannels = ((Structure) pPose.fetchData().get(0).getFieldValue("chanbase")).evaluateListBase();
for (Structure poseChannel : poseChannels) {
List constraintsList = new ArrayList();
Long boneOMA = Long.valueOf(((Pointer) poseChannel.getFieldValue("bone")).getOldMemoryAddress());
// the name is read directly from structure because bone might
// not yet be loaded
String name = blenderContext.getFileBlock(boneOMA).getStructure(blenderContext).getFieldValue("name").toString();
List constraints = ((Structure) poseChannel.getFieldValue("constraints")).evaluateListBase();
for (Structure constraint : constraints) {
String constraintName = constraint.getFieldValue("name").toString();
Map ipoMap = constraintsIpos.get(name);
Ipo ipo = ipoMap == null ? null : ipoMap.get(constraintName);
if (ipo == null) {
float enforce = ((Number) constraint.getFieldValue("enforce")).floatValue();
ipo = animationHelper.fromValue(enforce);
}
constraintsList.add(new BoneConstraint(constraint, boneOMA, ipo, blenderContext));
}
blenderContext.addConstraints(boneOMA, constraintsList);
}
}
// loading constraints connected with the object itself
List constraints = ((Structure) objectStructure.getFieldValue("constraints")).evaluateListBase();
if (constraints != null && constraints.size() > 0) {
Pointer pData = (Pointer) objectStructure.getFieldValue("data");
String dataType = pData.isNotNull() ? pData.fetchData().get(0).getType() : null;
List constraintsList = new ArrayList(constraints.size());
for (Structure constraint : constraints) {
String constraintName = constraint.getFieldValue("name").toString();
String objectName = objectStructure.getName();
Map objectConstraintsIpos = constraintsIpos.get(objectName);
Ipo ipo = objectConstraintsIpos != null ? objectConstraintsIpos.get(constraintName) : null;
if (ipo == null) {
float enforce = ((Number) constraint.getFieldValue("enforce")).floatValue();
ipo = animationHelper.fromValue(enforce);
}
constraintsList.add(this.createConstraint(dataType, constraint, objectStructure.getOldMemoryAddress(), ipo, blenderContext));
}
blenderContext.addConstraints(objectStructure.getOldMemoryAddress(), constraintsList);
}
}
/**
* This method creates a proper constraint object depending on the object's
* data type. Supported data types: Mesh Armature Camera
* Lamp Bone constraints are created in a different place.
*
* @param dataType
* the type of the object's data
* @param constraintStructure
* the constraint structure
* @param ownerOMA
* the owner OMA
* @param influenceIpo
* the influence interpolation curve
* @param blenderContext
* the blender context
* @return constraint object for the required type
* @throws BlenderFileException
* thrown when problems with blender file occurred
*/
private Constraint createConstraint(String dataType, Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {
if (dataType == null || "Mesh".equalsIgnoreCase(dataType) || "Camera".equalsIgnoreCase(dataType) || "Lamp".equalsIgnoreCase(dataType)) {
return new SpatialConstraint(constraintStructure, ownerOMA, influenceIpo, blenderContext);
} else if ("Armature".equalsIgnoreCase(dataType)) {
return new SkeletonConstraint(constraintStructure, ownerOMA, influenceIpo, blenderContext);
} else {
throw new IllegalArgumentException("Unsupported data type for applying constraints: " + dataType);
}
}
/**
* The method bakes all available and valid constraints.
*
* @param blenderContext
* the blender context
*/
public void bakeConstraints(BlenderContext blenderContext) {
Set owners = new HashSet();
for (Constraint constraint : blenderContext.getAllConstraints()) {
if(constraint instanceof BoneConstraint) {
BoneContext boneContext = blenderContext.getBoneContext(constraint.ownerOMA);
owners.add(boneContext.getArmatureObjectOMA());
} else {
Spatial spatial = (Spatial) blenderContext.getLoadedFeature(constraint.ownerOMA, LoadedDataType.FEATURE);
while (spatial.getParent() != null) {
spatial = spatial.getParent();
}
owners.add((Long)blenderContext.getMarkerValue(ObjectHelper.OMA_MARKER, spatial));
}
}
List simulationRootNodes = new ArrayList(owners.size());
for(Long ownerOMA : owners) {
simulationRootNodes.add(new SimulationNode(ownerOMA, blenderContext));
}
for (SimulationNode node : simulationRootNodes) {
node.simulate();
}
}
/**
* The method retrieves the transform from a feature in a given space.
*
* @param oma
* the OMA of the feature (spatial or armature node)
* @param subtargetName
* the feature's subtarget (bone in a case of armature's node)
* @param space
* the space the transform is evaluated to
* @return the transform of a feature in a given space
*/
public Transform getTransform(Long oma, String subtargetName, Space space) {
Spatial feature = (Spatial) blenderContext.getLoadedFeature(oma, LoadedDataType.FEATURE);
boolean isArmature = blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, feature) != null;
if (isArmature) {
blenderContext.getSkeleton(oma).updateWorldVectors();
BoneContext targetBoneContext = blenderContext.getBoneByName(oma, subtargetName);
Bone bone = targetBoneContext.getBone();
if (bone.getParent() == null && (space == Space.CONSTRAINT_SPACE_LOCAL || space == Space.CONSTRAINT_SPACE_PARLOCAL)) {
space = Space.CONSTRAINT_SPACE_POSE;
}
TempVars tempVars = TempVars.get();// use readable names of the matrices so that the code is more clear
Transform result;
switch (space) {
case CONSTRAINT_SPACE_WORLD:
Spatial model = (Spatial) blenderContext.getLoadedFeature(targetBoneContext.getSkeletonOwnerOma(), LoadedDataType.FEATURE);
Matrix4f boneModelMatrix = this.toMatrix(bone.getModelSpacePosition(), bone.getModelSpaceRotation(), bone.getModelSpaceScale(), tempVars.tempMat4);
Matrix4f modelWorldMatrix = this.toMatrix(model.getWorldTransform(), tempVars.tempMat42);
Matrix4f boneMatrixInWorldSpace = modelWorldMatrix.multLocal(boneModelMatrix);
result = new Transform(boneMatrixInWorldSpace.toTranslationVector(), boneMatrixInWorldSpace.toRotationQuat(), boneMatrixInWorldSpace.toScaleVector());
break;
case CONSTRAINT_SPACE_LOCAL:
assert bone.getParent() != null : "CONSTRAINT_SPACE_LOCAL should be evaluated as CONSTRAINT_SPACE_POSE if the bone has no parent!";
result = new Transform(bone.getLocalPosition(), bone.getLocalRotation(), bone.getLocalScale());
break;
case CONSTRAINT_SPACE_POSE: {
Matrix4f boneWorldMatrix = this.toMatrix(this.getTransform(oma, subtargetName, Space.CONSTRAINT_SPACE_WORLD), tempVars.tempMat4);
Matrix4f armatureInvertedWorldMatrix = this.toMatrix(feature.getWorldTransform(), tempVars.tempMat42).invertLocal();
Matrix4f bonePoseMatrix = armatureInvertedWorldMatrix.multLocal(boneWorldMatrix);
result = new Transform(bonePoseMatrix.toTranslationVector(), bonePoseMatrix.toRotationQuat(), bonePoseMatrix.toScaleVector());
break;
}
case CONSTRAINT_SPACE_PARLOCAL: {
Matrix4f boneWorldMatrix = this.toMatrix(this.getTransform(oma, subtargetName, Space.CONSTRAINT_SPACE_WORLD), tempVars.tempMat4);
Matrix4f armatureInvertedWorldMatrix = this.toMatrix(feature.getWorldTransform(), tempVars.tempMat42).invertLocal();
Matrix4f bonePoseMatrix = armatureInvertedWorldMatrix.multLocal(boneWorldMatrix);
result = new Transform(bonePoseMatrix.toTranslationVector(), bonePoseMatrix.toRotationQuat(), bonePoseMatrix.toScaleVector());
Bone parent = bone.getParent();
if(parent != null) {
BoneContext parentContext = blenderContext.getBoneContext(parent);
Vector3f head = parent.getModelSpacePosition();
Vector3f tail = head.add(bone.getModelSpaceRotation().mult(Vector3f.UNIT_Y.mult(parentContext.getLength())));
result.getTranslation().subtractLocal(tail);
}
break;
}
default:
throw new IllegalStateException("Unknown space type: " + space);
}
tempVars.release();
return result;
} else {
switch (space) {
case CONSTRAINT_SPACE_LOCAL:
return feature.getLocalTransform();
case CONSTRAINT_SPACE_WORLD:
return feature.getWorldTransform();
case CONSTRAINT_SPACE_PARLOCAL:
case CONSTRAINT_SPACE_POSE:
throw new IllegalStateException("Nodes can have only Local and World spaces applied!");
default:
throw new IllegalStateException("Unknown space type: " + space);
}
}
}
/**
* Applies transform to a feature (bone or spatial). Computations transform
* the given transformation from the given space to the feature's local
* space.
*
* @param oma
* the OMA of the feature we apply transformation to
* @param subtargetName
* the name of the feature's subtarget (bone in case of armature)
* @param space
* the space in which the given transform is to be applied
* @param transform
* the transform we apply
*/
public void applyTransform(Long oma, String subtargetName, Space space, Transform transform) {
Spatial feature = (Spatial) blenderContext.getLoadedFeature(oma, LoadedDataType.FEATURE);
boolean isArmature = blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, feature) != null;
if (isArmature) {
Skeleton skeleton = blenderContext.getSkeleton(oma);
BoneContext targetBoneContext = blenderContext.getBoneByName(oma, subtargetName);
Bone bone = targetBoneContext.getBone();
if (bone.getParent() == null && (space == Space.CONSTRAINT_SPACE_LOCAL || space == Space.CONSTRAINT_SPACE_PARLOCAL)) {
space = Space.CONSTRAINT_SPACE_POSE;
}
TempVars tempVars = TempVars.get();
switch (space) {
case CONSTRAINT_SPACE_LOCAL:
assert bone.getParent() != null : "CONSTRAINT_SPACE_LOCAL should be evaluated as CONSTRAINT_SPACE_POSE if the bone has no parent!";
bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale());
break;
case CONSTRAINT_SPACE_WORLD: {
Matrix4f boneMatrixInWorldSpace = this.toMatrix(transform, tempVars.tempMat4);
Matrix4f modelWorldMatrix = this.toMatrix(this.getTransform(targetBoneContext.getSkeletonOwnerOma(), null, Space.CONSTRAINT_SPACE_WORLD), tempVars.tempMat42);
Matrix4f boneMatrixInModelSpace = modelWorldMatrix.invertLocal().multLocal(boneMatrixInWorldSpace);
Bone parent = bone.getParent();
if (parent != null) {
Matrix4f parentMatrixInModelSpace = this.toMatrix(parent.getModelSpacePosition(), parent.getModelSpaceRotation(), parent.getModelSpaceScale(), tempVars.tempMat4);
boneMatrixInModelSpace = parentMatrixInModelSpace.invertLocal().multLocal(boneMatrixInModelSpace);
}
bone.setBindTransforms(boneMatrixInModelSpace.toTranslationVector(), boneMatrixInModelSpace.toRotationQuat(), boneMatrixInModelSpace.toScaleVector());
break;
}
case CONSTRAINT_SPACE_POSE: {
Matrix4f armatureWorldMatrix = this.toMatrix(feature.getWorldTransform(), tempVars.tempMat4);
Matrix4f boneMatrixInWorldSpace = armatureWorldMatrix.multLocal(this.toMatrix(transform, tempVars.tempMat42));
Matrix4f invertedModelMatrix = this.toMatrix(this.getTransform(targetBoneContext.getSkeletonOwnerOma(), null, Space.CONSTRAINT_SPACE_WORLD), tempVars.tempMat42).invertLocal();
Matrix4f boneMatrixInModelSpace = invertedModelMatrix.multLocal(boneMatrixInWorldSpace);
Bone parent = bone.getParent();
if (parent != null) {
Matrix4f parentMatrixInModelSpace = this.toMatrix(parent.getModelSpacePosition(), parent.getModelSpaceRotation(), parent.getModelSpaceScale(), tempVars.tempMat4);
boneMatrixInModelSpace = parentMatrixInModelSpace.invertLocal().multLocal(boneMatrixInModelSpace);
}
bone.setBindTransforms(boneMatrixInModelSpace.toTranslationVector(), boneMatrixInModelSpace.toRotationQuat(), boneMatrixInModelSpace.toScaleVector());
break;
}
case CONSTRAINT_SPACE_PARLOCAL:
Matrix4f armatureWorldMatrix = this.toMatrix(feature.getWorldTransform(), tempVars.tempMat4);
Matrix4f boneMatrixInWorldSpace = armatureWorldMatrix.multLocal(this.toMatrix(transform, tempVars.tempMat42));
Matrix4f invertedModelMatrix = this.toMatrix(this.getTransform(targetBoneContext.getSkeletonOwnerOma(), null, Space.CONSTRAINT_SPACE_WORLD), tempVars.tempMat42).invertLocal();
Matrix4f boneMatrixInModelSpace = invertedModelMatrix.multLocal(boneMatrixInWorldSpace);
Bone parent = bone.getParent();
if (parent != null) {
//first add the initial parent matrix to the bone's model matrix
BoneContext parentContext = blenderContext.getBoneContext(parent);
Matrix4f initialParentMatrixInModelSpace = parentContext.getBoneMatrixInModelSpace();
Matrix4f currentParentMatrixInModelSpace = this.toMatrix(parent.getModelSpacePosition(), parent.getModelSpaceRotation(), parent.getModelSpaceScale(), tempVars.tempMat4);
//the bone will now move with its parent in model space
//now we need to subtract the difference between current parent's model matrix and its initial model matrix
boneMatrixInModelSpace = initialParentMatrixInModelSpace.mult(boneMatrixInModelSpace);
Matrix4f diffMatrix = initialParentMatrixInModelSpace.mult(currentParentMatrixInModelSpace.invert());
boneMatrixInModelSpace.multLocal(diffMatrix);
//now the bone will have its position in model space with initial parent's model matrix added
}
bone.setBindTransforms(boneMatrixInModelSpace.toTranslationVector(), boneMatrixInModelSpace.toRotationQuat(), boneMatrixInModelSpace.toScaleVector());
break;
default:
tempVars.release();
throw new IllegalStateException("Invalid space type for target object: " + space.toString());
}
tempVars.release();
skeleton.updateWorldVectors();
} else {
switch (space) {
case CONSTRAINT_SPACE_LOCAL:
feature.getLocalTransform().set(transform);
break;
case CONSTRAINT_SPACE_WORLD:
if (feature.getParent() == null) {
feature.setLocalTransform(transform);
} else {
Transform parentWorldTransform = feature.getParent().getWorldTransform();
TempVars tempVars = TempVars.get();
Matrix4f parentInverseMatrix = this.toMatrix(parentWorldTransform, tempVars.tempMat4).invertLocal();
Matrix4f m = this.toMatrix(transform, tempVars.tempMat42);
m = m.multLocal(parentInverseMatrix);
tempVars.release();
transform.setTranslation(m.toTranslationVector());
transform.setRotation(m.toRotationQuat());
transform.setScale(m.toScaleVector());
feature.setLocalTransform(transform);
}
break;
default:
throw new IllegalStateException("Invalid space type for spatial object: " + space.toString());
}
}
}
/**
* Converts given transform to the matrix.
*
* @param transform
* the transform to be converted
* @param store
* the matrix where the result will be stored
* @return the store matrix
*/
public Matrix4f toMatrix(Transform transform, Matrix4f store) {
if (transform != null) {
return this.toMatrix(transform.getTranslation(), transform.getRotation(), transform.getScale(), store);
}
store.loadIdentity();
return store;
}
/**
* Converts given transformation parameters into the matrix.
*
* @param position
* the position of the feature
* @param rotation
* the rotation of the feature
* @param scale
* the scale of the feature
* @param store
* the matrix where the result will be stored
* @return the store matrix
*/
private Matrix4f toMatrix(Vector3f position, Quaternion rotation, Vector3f scale, Matrix4f store) {
store.loadIdentity();
store.setTranslation(position);
store.setRotationQuaternion(rotation);
store.setScale(scale);
return store;
}
/**
* The space of target or owner transformation.
*
* @author Marcin Roguski (Kaelthas)
*/
public static enum Space {
/** A transformation of the bone or spatial in the world space. */
CONSTRAINT_SPACE_WORLD,
/**
* For spatial it is the transformation in its parent space or in WORLD space if it has no parent.
* For bone it is a transformation in its bone parent space or in armature space if it has no parent.
*/
CONSTRAINT_SPACE_LOCAL,
/**
* This space IS NOT applicable for spatials.
* For bone it is a transformation in the blender's armature object space.
*/
CONSTRAINT_SPACE_POSE,
CONSTRAINT_SPACE_PARLOCAL;
/**
* This method returns the enum instance when given the appropriate
* value from the blend file.
*
* @param c
* the blender's value of the space modifier
* @return the scape enum instance
*/
public static Space valueOf(byte c) {
switch (c) {
case 0:
return CONSTRAINT_SPACE_WORLD;
case 1:
return CONSTRAINT_SPACE_LOCAL;
case 2:
return CONSTRAINT_SPACE_POSE;
case 3:
return CONSTRAINT_SPACE_PARLOCAL;
default:
throw new IllegalArgumentException("Value: " + c + " cannot be converted to Space enum instance!");
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy