1/*
2 * Copyright (c) 2009-2010 jMonkeyEngine
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 *   notice, this list of conditions and the following disclaimer.
11 *
12 * * Redistributions in binary form must reproduce the above copyright
13 *   notice, this list of conditions and the following disclaimer in the
14 *   documentation and/or other materials provided with the distribution.
15 *
16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17 *   may be used to endorse or promote products derived from this software
18 *   without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32package com.jme3.scene.plugins.blender.animations;
33
34import java.util.ArrayList;
35import java.util.HashMap;
36import java.util.List;
37import java.util.Map;
38import java.util.logging.Level;
39import java.util.logging.Logger;
40
41import com.jme3.animation.Bone;
42import com.jme3.animation.BoneTrack;
43import com.jme3.animation.Skeleton;
44import com.jme3.math.Matrix4f;
45import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
46import com.jme3.scene.plugins.blender.BlenderContext;
47import com.jme3.scene.plugins.blender.curves.BezierCurve;
48import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
49import com.jme3.scene.plugins.blender.file.Pointer;
50import com.jme3.scene.plugins.blender.file.Structure;
51
52/**
53 * This class defines the methods to calculate certain aspects of animation and
54 * armature functionalities.
55 *
56 * @author Marcin Roguski (Kaelthas)
57 */
58public class ArmatureHelper extends AbstractBlenderHelper {
59	private static final Logger	LOGGER		= Logger.getLogger(ArmatureHelper.class.getName());
60
61	/** A map of bones and their old memory addresses. */
62	private Map<Bone, Long>		bonesOMAs	= new HashMap<Bone, Long>();
63
64	/**
65	 * This constructor parses the given blender version and stores the result.
66	 * Some functionalities may differ in different blender versions.
67	 *
68	 * @param blenderVersion
69	 *            the version read from the blend file
70	 * @param fixUpAxis
71	 *            a variable that indicates if the Y asxis is the UP axis or not
72	 */
73	public ArmatureHelper(String blenderVersion, boolean fixUpAxis) {
74		super(blenderVersion, fixUpAxis);
75	}
76
77	/**
78	 * This method builds the object's bones structure.
79	 *
80	 * @param boneStructure
81	 *            the structure containing the bones' data
82	 * @param parent
83	 *            the parent bone
84	 * @param result
85	 *            the list where the newly created bone will be added
86	 * @param bonesPoseChannels
87	 *            a map of bones poses channels
88	 * @param blenderContext
89	 *            the blender context
90	 * @throws BlenderFileException
91	 *             an exception is thrown when there is problem with the blender
92	 *             file
93	 */
94	public void buildBones(Structure boneStructure, Bone parent, List<Bone> result, Matrix4f arbt, final Map<Long, Structure> bonesPoseChannels, BlenderContext blenderContext) throws BlenderFileException {
95		BoneContext bc = new BoneContext(boneStructure, arbt, bonesPoseChannels, blenderContext);
96		bc.buildBone(result, bonesOMAs, blenderContext);
97	}
98
99	/**
100	 * This method returns the old memory address of a bone. If the bone does
101	 * not exist in the blend file - zero is returned.
102	 *
103	 * @param bone
104	 *            the bone whose old memory address we seek
105	 * @return the old memory address of the given bone
106	 */
107	public Long getBoneOMA(Bone bone) {
108		Long result = bonesOMAs.get(bone);
109		if (result == null) {
110			result = Long.valueOf(0);
111		}
112		return result;
113	}
114
115	/**
116	 * This method returns a map where the key is the object's group index that
117	 * is used by a bone and the key is the bone index in the armature.
118	 *
119	 * @param defBaseStructure
120	 *            a bPose structure of the object
121	 * @return bone group-to-index map
122	 * @throws BlenderFileException
123	 *             this exception is thrown when the blender file is somehow
124	 *             corrupted
125	 */
126	public Map<Integer, Integer> getGroupToBoneIndexMap(Structure defBaseStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException {
127		Map<Integer, Integer> result = null;
128		if (skeleton.getBoneCount() != 0) {
129			result = new HashMap<Integer, Integer>();
130			List<Structure> deformGroups = defBaseStructure.evaluateListBase(blenderContext);// bDeformGroup
131			int groupIndex = 0;
132			for (Structure deformGroup : deformGroups) {
133				String deformGroupName = deformGroup.getFieldValue("name").toString();
134				Integer boneIndex = this.getBoneIndex(skeleton, deformGroupName);
135				if (boneIndex != null) {
136					result.put(Integer.valueOf(groupIndex), boneIndex);
137				}
138				++groupIndex;
139			}
140		}
141		return result;
142	}
143
144	@Override
145	public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
146		return true;
147	}
148
149	/**
150	 * This method retuns the bone tracks for animation.
151	 *
152	 * @param actionStructure
153	 *            the structure containing the tracks
154	 * @param blenderContext
155	 *            the blender context
156	 * @return a list of tracks for the specified animation
157	 * @throws BlenderFileException
158	 *             an exception is thrown when there are problems with the blend
159	 *             file
160	 */
161	public BoneTrack[] getTracks(Structure actionStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException {
162		if (blenderVersion < 250) {
163			return this.getTracks249(actionStructure, skeleton, blenderContext);
164		} else {
165			return this.getTracks250(actionStructure, skeleton, blenderContext);
166		}
167	}
168
169	/**
170	 * This method retuns the bone tracks for animation for blender version 2.50
171	 * and higher.
172	 *
173	 * @param actionStructure
174	 *            the structure containing the tracks
175	 * @param blenderContext
176	 *            the blender context
177	 * @return a list of tracks for the specified animation
178	 * @throws BlenderFileException
179	 *             an exception is thrown when there are problems with the blend
180	 *             file
181	 */
182	private BoneTrack[] getTracks250(Structure actionStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException {
183		LOGGER.log(Level.INFO, "Getting tracks!");
184		IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class);
185		int fps = blenderContext.getBlenderKey().getFps();
186		Structure groups = (Structure) actionStructure.getFieldValue("groups");
187		List<Structure> actionGroups = groups.evaluateListBase(blenderContext);// bActionGroup
188		List<BoneTrack> tracks = new ArrayList<BoneTrack>();
189		for (Structure actionGroup : actionGroups) {
190			String name = actionGroup.getFieldValue("name").toString();
191			int boneIndex = this.getBoneIndex(skeleton, name);
192			if (boneIndex >= 0) {
193				List<Structure> channels = ((Structure) actionGroup.getFieldValue("channels")).evaluateListBase(blenderContext);
194				BezierCurve[] bezierCurves = new BezierCurve[channels.size()];
195				int channelCounter = 0;
196				for (Structure c : channels) {
197					int type = ipoHelper.getCurveType(c, blenderContext);
198					Pointer pBezTriple = (Pointer) c.getFieldValue("bezt");
199					List<Structure> bezTriples = pBezTriple.fetchData(blenderContext.getInputStream());
200					bezierCurves[channelCounter++] = new BezierCurve(type, bezTriples, 2);
201				}
202
203				Ipo ipo = new Ipo(bezierCurves, fixUpAxis);
204				tracks.add((BoneTrack) ipo.calculateTrack(boneIndex, 0, ipo.getLastFrame(), fps, false));
205			}
206		}
207		return tracks.toArray(new BoneTrack[tracks.size()]);
208	}
209
210	/**
211	 * This method retuns the bone tracks for animation for blender version 2.49
212	 * (and probably several lower versions too).
213	 *
214	 * @param actionStructure
215	 *            the structure containing the tracks
216	 * @param blenderContext
217	 *            the blender context
218	 * @return a list of tracks for the specified animation
219	 * @throws BlenderFileException
220	 *             an exception is thrown when there are problems with the blend
221	 *             file
222	 */
223	private BoneTrack[] getTracks249(Structure actionStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException {
224		LOGGER.log(Level.INFO, "Getting tracks!");
225		IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class);
226		int fps = blenderContext.getBlenderKey().getFps();
227		Structure chanbase = (Structure) actionStructure.getFieldValue("chanbase");
228		List<Structure> actionChannels = chanbase.evaluateListBase(blenderContext);// bActionChannel
229		List<BoneTrack> tracks = new ArrayList<BoneTrack>();
230		for (Structure bActionChannel : actionChannels) {
231			String name = bActionChannel.getFieldValue("name").toString();
232			int boneIndex = this.getBoneIndex(skeleton, name);
233			if (boneIndex >= 0) {
234				Pointer p = (Pointer) bActionChannel.getFieldValue("ipo");
235				if (!p.isNull()) {
236					Structure ipoStructure = p.fetchData(blenderContext.getInputStream()).get(0);
237					Ipo ipo = ipoHelper.fromIpoStructure(ipoStructure, blenderContext);
238					tracks.add((BoneTrack) ipo.calculateTrack(boneIndex, 0, ipo.getLastFrame(), fps, false));
239				}
240			}
241		}
242		return tracks.toArray(new BoneTrack[tracks.size()]);
243	}
244
245	/**
246	 * This method returns the index of the bone in the given skeleton.
247	 *
248	 * @param skeleton
249	 *            the skeleton
250	 * @param boneName
251	 *            the name of the bone
252	 * @return the index of the bone
253	 */
254	private int getBoneIndex(Skeleton skeleton, String boneName) {
255		int result = -1;
256		for (int i = 0; i < skeleton.getBoneCount() && result == -1; ++i) {
257			if (boneName.equals(skeleton.getBone(i).getName())) {
258				result = i;
259			}
260		}
261		return result;
262	}
263}
264