1 package com.jme3.scene.plugins.blender.constraints; 2 3 import java.lang.reflect.InvocationTargetException; 4 import java.util.ArrayList; 5 import java.util.HashMap; 6 import java.util.List; 7 import java.util.Map; 8 import java.util.logging.Logger; 9 10 import com.jme3.scene.plugins.blender.AbstractBlenderHelper; 11 import com.jme3.scene.plugins.blender.BlenderContext; 12 import com.jme3.scene.plugins.blender.animations.Ipo; 13 import com.jme3.scene.plugins.blender.animations.IpoHelper; 14 import com.jme3.scene.plugins.blender.exceptions.BlenderFileException; 15 import com.jme3.scene.plugins.blender.file.Pointer; 16 import com.jme3.scene.plugins.blender.file.Structure; 17 18 /** 19 * This class should be used for constraint calculations. 20 * @author Marcin Roguski (Kaelthas) 21 */ 22 public class ConstraintHelper extends AbstractBlenderHelper { 23 private static final Logger LOGGER = Logger.getLogger(ConstraintHelper.class.getName()); 24 25 private static final Map<String, Class<? extends Constraint>> constraintClasses = new HashMap<String, Class<? extends Constraint>>(22); 26 static { 27 constraintClasses.put("bActionConstraint", ConstraintAction.class); 28 constraintClasses.put("bChildOfConstraint", ConstraintChildOf.class); 29 constraintClasses.put("bClampToConstraint", ConstraintClampTo.class); 30 constraintClasses.put("bDistLimitConstraint", ConstraintDistLimit.class); 31 constraintClasses.put("bFollowPathConstraint", ConstraintFollowPath.class); 32 constraintClasses.put("bKinematicConstraint", ConstraintInverseKinematics.class); 33 constraintClasses.put("bLockTrackConstraint", ConstraintLockTrack.class); 34 constraintClasses.put("bLocateLikeConstraint", ConstraintLocLike.class); 35 constraintClasses.put("bLocLimitConstraint", ConstraintLocLimit.class); 36 constraintClasses.put("bMinMaxConstraint", ConstraintMinMax.class); 37 constraintClasses.put("bNullConstraint", ConstraintNull.class); 38 constraintClasses.put("bPythonConstraint", ConstraintPython.class); 39 constraintClasses.put("bRigidBodyJointConstraint", ConstraintRigidBodyJoint.class); 40 constraintClasses.put("bRotateLikeConstraint", ConstraintRotLike.class); 41 constraintClasses.put("bShrinkWrapConstraint", ConstraintShrinkWrap.class); 42 constraintClasses.put("bSizeLikeConstraint", ConstraintSizeLike.class); 43 constraintClasses.put("bSizeLimitConstraint", ConstraintSizeLimit.class); 44 constraintClasses.put("bStretchToConstraint", ConstraintStretchTo.class); 45 constraintClasses.put("bTransformConstraint", ConstraintTransform.class); 46 constraintClasses.put("bRotLimitConstraint", ConstraintRotLimit.class); 47 //Blender 2.50+ 48 constraintClasses.put("bSplineIKConstraint", ConstraintSplineInverseKinematic.class); 49 constraintClasses.put("bDampTrackConstraint", ConstraintDampTrack.class); 50 constraintClasses.put("bPivotConstraint", ConstraintDampTrack.class); 51 } 52 53 /** 54 * Helper constructor. It's main task is to generate the affection functions. These functions are common to all 55 * ConstraintHelper instances. Unfortunately this constructor might grow large. If it becomes too large - I shall 56 * consider refactoring. The constructor parses the given blender version and stores the result. Some 57 * functionalities may differ in different blender versions. 58 * @param blenderVersion 59 * the version read from the blend file 60 * @param fixUpAxis 61 * a variable that indicates if the Y asxis is the UP axis or not 62 */ 63 public ConstraintHelper(String blenderVersion, BlenderContext blenderContext, boolean fixUpAxis) { 64 super(blenderVersion, fixUpAxis); 65 } 66 67 /** 68 * This method reads constraints for for the given structure. The 69 * constraints are loaded only once for object/bone. 70 * 71 * @param objectStructure 72 * the structure we read constraint's for 73 * @param blenderContext 74 * the blender context 75 * @throws BlenderFileException 76 */ 77 public void loadConstraints(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException { 78 LOGGER.fine("Loading constraints."); 79 // reading influence ipos for the constraints 80 IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class); 81 Map<String, Map<String, Ipo>> constraintsIpos = new HashMap<String, Map<String, Ipo>>(); 82 Pointer pActions = (Pointer) objectStructure.getFieldValue("action"); 83 if (pActions.isNotNull()) { 84 List<Structure> actions = pActions.fetchData(blenderContext.getInputStream()); 85 for (Structure action : actions) { 86 Structure chanbase = (Structure) action.getFieldValue("chanbase"); 87 List<Structure> actionChannels = chanbase.evaluateListBase(blenderContext); 88 for (Structure actionChannel : actionChannels) { 89 Map<String, Ipo> ipos = new HashMap<String, Ipo>(); 90 Structure constChannels = (Structure) actionChannel.getFieldValue("constraintChannels"); 91 List<Structure> constraintChannels = constChannels.evaluateListBase(blenderContext); 92 for (Structure constraintChannel : constraintChannels) { 93 Pointer pIpo = (Pointer) constraintChannel.getFieldValue("ipo"); 94 if (pIpo.isNotNull()) { 95 String constraintName = constraintChannel.getFieldValue("name").toString(); 96 Ipo ipo = ipoHelper.fromIpoStructure(pIpo.fetchData(blenderContext.getInputStream()).get(0), blenderContext); 97 ipos.put(constraintName, ipo); 98 } 99 } 100 String actionName = actionChannel.getFieldValue("name").toString(); 101 constraintsIpos.put(actionName, ipos); 102 } 103 } 104 } 105 106 //loading constraints connected with the object's bones 107 Pointer pPose = (Pointer) objectStructure.getFieldValue("pose"); 108 if (pPose.isNotNull()) { 109 List<Structure> poseChannels = ((Structure) pPose.fetchData(blenderContext.getInputStream()).get(0).getFieldValue("chanbase")).evaluateListBase(blenderContext); 110 for (Structure poseChannel : poseChannels) { 111 List<Constraint> constraintsList = new ArrayList<Constraint>(); 112 Long boneOMA = Long.valueOf(((Pointer) poseChannel.getFieldValue("bone")).getOldMemoryAddress()); 113 114 //the name is read directly from structure because bone might not yet be loaded 115 String name = blenderContext.getFileBlock(boneOMA).getStructure(blenderContext).getFieldValue("name").toString(); 116 List<Structure> constraints = ((Structure) poseChannel.getFieldValue("constraints")).evaluateListBase(blenderContext); 117 for (Structure constraint : constraints) { 118 String constraintName = constraint.getFieldValue("name").toString(); 119 Map<String, Ipo> ipoMap = constraintsIpos.get(name); 120 Ipo ipo = ipoMap==null ? null : ipoMap.get(constraintName); 121 if (ipo == null) { 122 float enforce = ((Number) constraint.getFieldValue("enforce")).floatValue(); 123 ipo = ipoHelper.fromValue(enforce); 124 } 125 constraintsList.add(this.createConstraint(constraint, boneOMA, ipo, blenderContext)); 126 } 127 blenderContext.addConstraints(boneOMA, constraintsList); 128 } 129 } 130 131 //loading constraints connected with the object itself 132 List<Structure> constraints = ((Structure)objectStructure.getFieldValue("constraints")).evaluateListBase(blenderContext); 133 List<Constraint> constraintsList = new ArrayList<Constraint>(constraints.size()); 134 135 for(Structure constraint : constraints) { 136 String constraintName = constraint.getFieldValue("name").toString(); 137 String objectName = objectStructure.getName(); 138 139 Map<String, Ipo> objectConstraintsIpos = constraintsIpos.get(objectName); 140 Ipo ipo = objectConstraintsIpos!=null ? objectConstraintsIpos.get(constraintName) : null; 141 if (ipo == null) { 142 float enforce = ((Number) constraint.getFieldValue("enforce")).floatValue(); 143 ipo = ipoHelper.fromValue(enforce); 144 } 145 constraintsList.add(this.createConstraint(constraint, objectStructure.getOldMemoryAddress(), ipo, blenderContext)); 146 } 147 blenderContext.addConstraints(objectStructure.getOldMemoryAddress(), constraintsList); 148 } 149 150 /** 151 * This method creates the constraint instance. 152 * 153 * @param constraintStructure 154 * the constraint's structure (bConstraint clss in blender 2.49). 155 * @param ownerOMA 156 * the old memory address of the constraint's owner 157 * @param influenceIpo 158 * the ipo curve of the influence factor 159 * @param blenderContext 160 * the blender context 161 * @throws BlenderFileException 162 * this exception is thrown when the blender file is somehow 163 * corrupted 164 */ 165 protected Constraint createConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, 166 BlenderContext blenderContext) throws BlenderFileException { 167 String constraintClassName = this.getConstraintClassName(constraintStructure, blenderContext); 168 Class<? extends Constraint> constraintClass = constraintClasses.get(constraintClassName); 169 if(constraintClass != null) { 170 try { 171 return (Constraint) constraintClass.getDeclaredConstructors()[0].newInstance(constraintStructure, ownerOMA, influenceIpo, 172 blenderContext); 173 } catch (IllegalArgumentException e) { 174 throw new BlenderFileException(e.getLocalizedMessage(), e); 175 } catch (SecurityException e) { 176 throw new BlenderFileException(e.getLocalizedMessage(), e); 177 } catch (InstantiationException e) { 178 throw new BlenderFileException(e.getLocalizedMessage(), e); 179 } catch (IllegalAccessException e) { 180 throw new BlenderFileException(e.getLocalizedMessage(), e); 181 } catch (InvocationTargetException e) { 182 throw new BlenderFileException(e.getLocalizedMessage(), e); 183 } 184 } else { 185 throw new BlenderFileException("Unknown constraint type: " + constraintClassName); 186 } 187 } 188 189 protected String getConstraintClassName(Structure constraintStructure, BlenderContext blenderContext) throws BlenderFileException { 190 Pointer pData = (Pointer)constraintStructure.getFieldValue("data"); 191 if(pData.isNotNull()) { 192 Structure data = pData.fetchData(blenderContext.getInputStream()).get(0); 193 return data.getType(); 194 195 } 196 return constraintStructure.getType(); 197 } 198 199 @Override 200 public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { 201 return true; 202 } 203 } 204