Home | History | Annotate | Download | only in animations
      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  */
     32 package com.jme3.scene.plugins.blender.animations;
     33 
     34 import java.util.ArrayList;
     35 import java.util.HashMap;
     36 import java.util.List;
     37 import java.util.Map;
     38 import java.util.logging.Level;
     39 import java.util.logging.Logger;
     40 
     41 import com.jme3.animation.Bone;
     42 import com.jme3.animation.BoneTrack;
     43 import com.jme3.animation.Skeleton;
     44 import com.jme3.math.Matrix4f;
     45 import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
     46 import com.jme3.scene.plugins.blender.BlenderContext;
     47 import com.jme3.scene.plugins.blender.curves.BezierCurve;
     48 import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
     49 import com.jme3.scene.plugins.blender.file.Pointer;
     50 import 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  */
     58 public 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