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