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.ogre;
33
34import com.jme3.animation.Animation;
35import com.jme3.animation.Bone;
36import com.jme3.animation.BoneTrack;
37import com.jme3.animation.Skeleton;
38import com.jme3.asset.AssetInfo;
39import com.jme3.asset.AssetLoader;
40import com.jme3.asset.AssetManager;
41import com.jme3.math.Quaternion;
42import com.jme3.math.Vector3f;
43import com.jme3.util.xml.SAXUtil;
44import java.io.IOException;
45import java.io.InputStream;
46import java.io.InputStreamReader;
47import java.util.ArrayList;
48import java.util.HashMap;
49import java.util.Map;
50import java.util.Stack;
51import java.util.logging.Logger;
52import javax.xml.parsers.ParserConfigurationException;
53import javax.xml.parsers.SAXParserFactory;
54import org.xml.sax.Attributes;
55import org.xml.sax.InputSource;
56import org.xml.sax.SAXException;
57import org.xml.sax.XMLReader;
58import org.xml.sax.helpers.DefaultHandler;
59
60public 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
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
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     */
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
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
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