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.ogre; 33 34 import com.jme3.animation.Animation; 35 import com.jme3.animation.Bone; 36 import com.jme3.animation.BoneTrack; 37 import com.jme3.animation.Skeleton; 38 import com.jme3.asset.AssetInfo; 39 import com.jme3.asset.AssetLoader; 40 import com.jme3.asset.AssetManager; 41 import com.jme3.math.Quaternion; 42 import com.jme3.math.Vector3f; 43 import com.jme3.util.xml.SAXUtil; 44 import java.io.IOException; 45 import java.io.InputStream; 46 import java.io.InputStreamReader; 47 import java.util.ArrayList; 48 import java.util.HashMap; 49 import java.util.Map; 50 import java.util.Stack; 51 import java.util.logging.Logger; 52 import javax.xml.parsers.ParserConfigurationException; 53 import javax.xml.parsers.SAXParserFactory; 54 import org.xml.sax.Attributes; 55 import org.xml.sax.InputSource; 56 import org.xml.sax.SAXException; 57 import org.xml.sax.XMLReader; 58 import org.xml.sax.helpers.DefaultHandler; 59 60 public class SkeletonLoader extends DefaultHandler implements AssetLoader { 61 62 private static final Logger logger = Logger.getLogger(SceneLoader.class.getName()); 63 private AssetManager assetManager; 64 private Stack<String> elementStack = new Stack<String>(); 65 private HashMap<Integer, Bone> indexToBone = new HashMap<Integer, Bone>(); 66 private HashMap<String, Bone> nameToBone = new HashMap<String, Bone>(); 67 private BoneTrack track; 68 private ArrayList<BoneTrack> tracks = new ArrayList<BoneTrack>(); 69 private Animation animation; 70 private ArrayList<Animation> animations; 71 private Bone bone; 72 private Skeleton skeleton; 73 private ArrayList<Float> times = new ArrayList<Float>(); 74 private ArrayList<Vector3f> translations = new ArrayList<Vector3f>(); 75 private ArrayList<Quaternion> rotations = new ArrayList<Quaternion>(); 76 private ArrayList<Vector3f> scales = new ArrayList<Vector3f>(); 77 private float time = -1; 78 private Vector3f position; 79 private Quaternion rotation; 80 private Vector3f scale; 81 private float angle; 82 private Vector3f axis; 83 startElement(String uri, String localName, String qName, Attributes attribs)84 public void startElement(String uri, String localName, String qName, Attributes attribs) throws SAXException { 85 if (qName.equals("position") || qName.equals("translate")) { 86 position = SAXUtil.parseVector3(attribs); 87 } else if (qName.equals("rotation") || qName.equals("rotate")) { 88 angle = SAXUtil.parseFloat(attribs.getValue("angle")); 89 } else if (qName.equals("axis")) { 90 assert elementStack.peek().equals("rotation") 91 || elementStack.peek().equals("rotate"); 92 axis = SAXUtil.parseVector3(attribs); 93 } else if (qName.equals("scale")) { 94 scale = SAXUtil.parseVector3(attribs); 95 } else if (qName.equals("keyframe")) { 96 assert elementStack.peek().equals("keyframes"); 97 time = SAXUtil.parseFloat(attribs.getValue("time")); 98 } else if (qName.equals("keyframes")) { 99 assert elementStack.peek().equals("track"); 100 } else if (qName.equals("track")) { 101 assert elementStack.peek().equals("tracks"); 102 String boneName = SAXUtil.parseString(attribs.getValue("bone")); 103 Bone bone = nameToBone.get(boneName); 104 int index = skeleton.getBoneIndex(bone); 105 track = new BoneTrack(index); 106 } else if (qName.equals("boneparent")) { 107 assert elementStack.peek().equals("bonehierarchy"); 108 String boneName = attribs.getValue("bone"); 109 String parentName = attribs.getValue("parent"); 110 Bone bone = nameToBone.get(boneName); 111 Bone parent = nameToBone.get(parentName); 112 parent.addChild(bone); 113 } else if (qName.equals("bone")) { 114 assert elementStack.peek().equals("bones"); 115 116 // insert bone into indexed map 117 bone = new Bone(attribs.getValue("name")); 118 int id = SAXUtil.parseInt(attribs.getValue("id")); 119 indexToBone.put(id, bone); 120 nameToBone.put(bone.getName(), bone); 121 } else if (qName.equals("tracks")) { 122 assert elementStack.peek().equals("animation"); 123 tracks.clear(); 124 } else if (qName.equals("animation")) { 125 assert elementStack.peek().equals("animations"); 126 String name = SAXUtil.parseString(attribs.getValue("name")); 127 float length = SAXUtil.parseFloat(attribs.getValue("length")); 128 animation = new Animation(name, length); 129 } else if (qName.equals("bonehierarchy")) { 130 assert elementStack.peek().equals("skeleton"); 131 } else if (qName.equals("animations")) { 132 assert elementStack.peek().equals("skeleton"); 133 animations = new ArrayList<Animation>(); 134 } else if (qName.equals("bones")) { 135 assert elementStack.peek().equals("skeleton"); 136 } else if (qName.equals("skeleton")) { 137 assert elementStack.size() == 0; 138 } 139 elementStack.add(qName); 140 } 141 endElement(String uri, String name, String qName)142 public void endElement(String uri, String name, String qName) { 143 if (qName.equals("translate") || qName.equals("position") || qName.equals("scale")) { 144 } else if (qName.equals("axis")) { 145 } else if (qName.equals("rotate") || qName.equals("rotation")) { 146 rotation = new Quaternion(); 147 axis.normalizeLocal(); 148 rotation.fromAngleNormalAxis(angle, axis); 149 angle = 0; 150 axis = null; 151 } else if (qName.equals("bone")) { 152 bone.setBindTransforms(position, rotation, scale); 153 bone = null; 154 position = null; 155 rotation = null; 156 scale = null; 157 } else if (qName.equals("bonehierarchy")) { 158 Bone[] bones = new Bone[indexToBone.size()]; 159 // find bones without a parent and attach them to the skeleton 160 // also assign the bones to the bonelist 161 for (Map.Entry<Integer, Bone> entry : indexToBone.entrySet()) { 162 Bone bone = entry.getValue(); 163 bones[entry.getKey()] = bone; 164 } 165 indexToBone.clear(); 166 skeleton = new Skeleton(bones); 167 } else if (qName.equals("animation")) { 168 animations.add(animation); 169 animation = null; 170 } else if (qName.equals("track")) { 171 if (track != null) { // if track has keyframes 172 tracks.add(track); 173 track = null; 174 } 175 } else if (qName.equals("tracks")) { 176 BoneTrack[] trackList = tracks.toArray(new BoneTrack[tracks.size()]); 177 animation.setTracks(trackList); 178 tracks.clear(); 179 } else if (qName.equals("keyframe")) { 180 assert time >= 0; 181 assert position != null; 182 assert rotation != null; 183 184 times.add(time); 185 translations.add(position); 186 rotations.add(rotation); 187 if (scale != null) { 188 scales.add(scale); 189 }else{ 190 scales.add(new Vector3f(1,1,1)); 191 } 192 193 time = -1; 194 position = null; 195 rotation = null; 196 scale = null; 197 } else if (qName.equals("keyframes")) { 198 if (times.size() > 0) { 199 float[] timesArray = new float[times.size()]; 200 for (int i = 0; i < timesArray.length; i++) { 201 timesArray[i] = times.get(i); 202 } 203 204 Vector3f[] transArray = translations.toArray(new Vector3f[translations.size()]); 205 Quaternion[] rotArray = rotations.toArray(new Quaternion[rotations.size()]); 206 Vector3f[] scalesArray = scales.toArray(new Vector3f[scales.size()]); 207 208 track.setKeyframes(timesArray, transArray, rotArray, scalesArray); 209 //track.setKeyframes(timesArray, transArray, rotArray); 210 } else { 211 track = null; 212 } 213 214 times.clear(); 215 translations.clear(); 216 rotations.clear(); 217 scales.clear(); 218 } else if (qName.equals("skeleton")) { 219 nameToBone.clear(); 220 } 221 assert elementStack.peek().equals(qName); 222 elementStack.pop(); 223 } 224 225 /** 226 * Reset the SkeletonLoader in case an error occured while parsing XML. 227 * This allows future use of the loader even after an error. 228 */ fullReset()229 private void fullReset() { 230 elementStack.clear(); 231 indexToBone.clear(); 232 nameToBone.clear(); 233 track = null; 234 tracks.clear(); 235 animation = null; 236 if (animations != null) { 237 animations.clear(); 238 } 239 240 bone = null; 241 skeleton = null; 242 times.clear(); 243 rotations.clear(); 244 translations.clear(); 245 time = -1; 246 position = null; 247 rotation = null; 248 scale = null; 249 angle = 0; 250 axis = null; 251 } 252 load(InputStream in)253 public Object load(InputStream in) throws IOException { 254 try { 255 256 // Added by larynx 25.06.2011 257 // Android needs the namespace aware flag set to true 258 // Kirill 30.06.2011 259 // Now, hack is applied for both desktop and android to avoid 260 // checking with JmeSystem. 261 SAXParserFactory factory = SAXParserFactory.newInstance(); 262 factory.setNamespaceAware(true); 263 XMLReader xr = factory.newSAXParser().getXMLReader(); 264 265 xr.setContentHandler(this); 266 xr.setErrorHandler(this); 267 InputStreamReader r = new InputStreamReader(in); 268 xr.parse(new InputSource(r)); 269 if (animations == null) { 270 animations = new ArrayList<Animation>(); 271 } 272 AnimData data = new AnimData(skeleton, animations); 273 skeleton = null; 274 animations = null; 275 return data; 276 } catch (SAXException ex) { 277 IOException ioEx = new IOException("Error while parsing Ogre3D dotScene"); 278 ioEx.initCause(ex); 279 fullReset(); 280 throw ioEx; 281 } catch (ParserConfigurationException ex) { 282 IOException ioEx = new IOException("Error while parsing Ogre3D dotScene"); 283 ioEx.initCause(ex); 284 fullReset(); 285 throw ioEx; 286 } 287 288 } 289 load(AssetInfo info)290 public Object load(AssetInfo info) throws IOException { 291 assetManager = info.getManager(); 292 InputStream in = null; 293 try { 294 in = info.openStream(); 295 return load(in); 296 } finally { 297 if (in != null){ 298 in.close(); 299 } 300 } 301 } 302 } 303