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