1package com.jme3.scene.plugins.blender.constraints;
2
3import java.lang.reflect.InvocationTargetException;
4import java.util.ArrayList;
5import java.util.HashMap;
6import java.util.List;
7import java.util.Map;
8import java.util.logging.Logger;
9
10import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
11import com.jme3.scene.plugins.blender.BlenderContext;
12import com.jme3.scene.plugins.blender.animations.Ipo;
13import com.jme3.scene.plugins.blender.animations.IpoHelper;
14import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
15import com.jme3.scene.plugins.blender.file.Pointer;
16import com.jme3.scene.plugins.blender.file.Structure;
17
18/**
19 * This class should be used for constraint calculations.
20 * @author Marcin Roguski (Kaelthas)
21 */
22public 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