blob: d75728feeeec783bc37eb67d9585bbfe98445ca0 [file] [log] [blame]
package com.jme3.scene.plugins.blender.constraints;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.animations.Ipo;
import com.jme3.scene.plugins.blender.animations.IpoHelper;
import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure;
/**
* 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());
private static final Map<String, Class<? extends Constraint>> constraintClasses = new HashMap<String, Class<? extends Constraint>>(22);
static {
constraintClasses.put("bActionConstraint", ConstraintAction.class);
constraintClasses.put("bChildOfConstraint", ConstraintChildOf.class);
constraintClasses.put("bClampToConstraint", ConstraintClampTo.class);
constraintClasses.put("bDistLimitConstraint", ConstraintDistLimit.class);
constraintClasses.put("bFollowPathConstraint", ConstraintFollowPath.class);
constraintClasses.put("bKinematicConstraint", ConstraintInverseKinematics.class);
constraintClasses.put("bLockTrackConstraint", ConstraintLockTrack.class);
constraintClasses.put("bLocateLikeConstraint", ConstraintLocLike.class);
constraintClasses.put("bLocLimitConstraint", ConstraintLocLimit.class);
constraintClasses.put("bMinMaxConstraint", ConstraintMinMax.class);
constraintClasses.put("bNullConstraint", ConstraintNull.class);
constraintClasses.put("bPythonConstraint", ConstraintPython.class);
constraintClasses.put("bRigidBodyJointConstraint", ConstraintRigidBodyJoint.class);
constraintClasses.put("bRotateLikeConstraint", ConstraintRotLike.class);
constraintClasses.put("bShrinkWrapConstraint", ConstraintShrinkWrap.class);
constraintClasses.put("bSizeLikeConstraint", ConstraintSizeLike.class);
constraintClasses.put("bSizeLimitConstraint", ConstraintSizeLimit.class);
constraintClasses.put("bStretchToConstraint", ConstraintStretchTo.class);
constraintClasses.put("bTransformConstraint", ConstraintTransform.class);
constraintClasses.put("bRotLimitConstraint", ConstraintRotLimit.class);
//Blender 2.50+
constraintClasses.put("bSplineIKConstraint", ConstraintSplineInverseKinematic.class);
constraintClasses.put("bDampTrackConstraint", ConstraintDampTrack.class);
constraintClasses.put("bPivotConstraint", ConstraintDampTrack.class);
}
/**
* Helper constructor. It's main task is to generate the affection functions. These functions are common to all
* ConstraintHelper instances. Unfortunately this constructor might grow large. If it becomes too large - I shall
* consider refactoring. The constructor parses the given blender version and stores the result. Some
* functionalities may differ in different blender versions.
* @param blenderVersion
* the version read from the blend file
* @param fixUpAxis
* a variable that indicates if the Y asxis is the UP axis or not
*/
public ConstraintHelper(String blenderVersion, BlenderContext blenderContext, boolean fixUpAxis) {
super(blenderVersion, fixUpAxis);
}
/**
* 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
IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class);
Map<String, Map<String, Ipo>> constraintsIpos = new HashMap<String, Map<String, Ipo>>();
Pointer pActions = (Pointer) objectStructure.getFieldValue("action");
if (pActions.isNotNull()) {
List<Structure> actions = pActions.fetchData(blenderContext.getInputStream());
for (Structure action : actions) {
Structure chanbase = (Structure) action.getFieldValue("chanbase");
List<Structure> actionChannels = chanbase.evaluateListBase(blenderContext);
for (Structure actionChannel : actionChannels) {
Map<String, Ipo> ipos = new HashMap<String, Ipo>();
Structure constChannels = (Structure) actionChannel.getFieldValue("constraintChannels");
List<Structure> constraintChannels = constChannels.evaluateListBase(blenderContext);
for (Structure constraintChannel : constraintChannels) {
Pointer pIpo = (Pointer) constraintChannel.getFieldValue("ipo");
if (pIpo.isNotNull()) {
String constraintName = constraintChannel.getFieldValue("name").toString();
Ipo ipo = ipoHelper.fromIpoStructure(pIpo.fetchData(blenderContext.getInputStream()).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<Structure> poseChannels = ((Structure) pPose.fetchData(blenderContext.getInputStream()).get(0).getFieldValue("chanbase")).evaluateListBase(blenderContext);
for (Structure poseChannel : poseChannels) {
List<Constraint> constraintsList = new ArrayList<Constraint>();
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<Structure> constraints = ((Structure) poseChannel.getFieldValue("constraints")).evaluateListBase(blenderContext);
for (Structure constraint : constraints) {
String constraintName = constraint.getFieldValue("name").toString();
Map<String, Ipo> ipoMap = constraintsIpos.get(name);
Ipo ipo = ipoMap==null ? null : ipoMap.get(constraintName);
if (ipo == null) {
float enforce = ((Number) constraint.getFieldValue("enforce")).floatValue();
ipo = ipoHelper.fromValue(enforce);
}
constraintsList.add(this.createConstraint(constraint, boneOMA, ipo, blenderContext));
}
blenderContext.addConstraints(boneOMA, constraintsList);
}
}
//loading constraints connected with the object itself
List<Structure> constraints = ((Structure)objectStructure.getFieldValue("constraints")).evaluateListBase(blenderContext);
List<Constraint> constraintsList = new ArrayList<Constraint>(constraints.size());
for(Structure constraint : constraints) {
String constraintName = constraint.getFieldValue("name").toString();
String objectName = objectStructure.getName();
Map<String, Ipo> objectConstraintsIpos = constraintsIpos.get(objectName);
Ipo ipo = objectConstraintsIpos!=null ? objectConstraintsIpos.get(constraintName) : null;
if (ipo == null) {
float enforce = ((Number) constraint.getFieldValue("enforce")).floatValue();
ipo = ipoHelper.fromValue(enforce);
}
constraintsList.add(this.createConstraint(constraint, objectStructure.getOldMemoryAddress(), ipo, blenderContext));
}
blenderContext.addConstraints(objectStructure.getOldMemoryAddress(), constraintsList);
}
/**
* This method creates the constraint instance.
*
* @param constraintStructure
* the constraint's structure (bConstraint clss in blender 2.49).
* @param ownerOMA
* the old memory address of the constraint's owner
* @param influenceIpo
* the ipo curve of the influence factor
* @param blenderContext
* the blender context
* @throws BlenderFileException
* this exception is thrown when the blender file is somehow
* corrupted
*/
protected Constraint createConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo,
BlenderContext blenderContext) throws BlenderFileException {
String constraintClassName = this.getConstraintClassName(constraintStructure, blenderContext);
Class<? extends Constraint> constraintClass = constraintClasses.get(constraintClassName);
if(constraintClass != null) {
try {
return (Constraint) constraintClass.getDeclaredConstructors()[0].newInstance(constraintStructure, ownerOMA, influenceIpo,
blenderContext);
} catch (IllegalArgumentException e) {
throw new BlenderFileException(e.getLocalizedMessage(), e);
} catch (SecurityException e) {
throw new BlenderFileException(e.getLocalizedMessage(), e);
} catch (InstantiationException e) {
throw new BlenderFileException(e.getLocalizedMessage(), e);
} catch (IllegalAccessException e) {
throw new BlenderFileException(e.getLocalizedMessage(), e);
} catch (InvocationTargetException e) {
throw new BlenderFileException(e.getLocalizedMessage(), e);
}
} else {
throw new BlenderFileException("Unknown constraint type: " + constraintClassName);
}
}
protected String getConstraintClassName(Structure constraintStructure, BlenderContext blenderContext) throws BlenderFileException {
Pointer pData = (Pointer)constraintStructure.getFieldValue("data");
if(pData.isNotNull()) {
Structure data = pData.fetchData(blenderContext.getInputStream()).get(0);
return data.getType();
}
return constraintStructure.getType();
}
@Override
public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
return true;
}
}