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.AnimControl;
35 import com.jme3.animation.Animation;
36 import com.jme3.animation.SkeletonControl;
37 import com.jme3.asset.*;
38 import com.jme3.material.Material;
39 import com.jme3.material.MaterialList;
40 import com.jme3.math.ColorRGBA;
41 import com.jme3.renderer.queue.RenderQueue.Bucket;
42 import com.jme3.scene.*;
43 import com.jme3.scene.VertexBuffer.Format;
44 import com.jme3.scene.VertexBuffer.Type;
45 import com.jme3.scene.VertexBuffer.Usage;
46 import com.jme3.scene.plugins.ogre.matext.OgreMaterialKey;
47 import com.jme3.util.BufferUtils;
48 import com.jme3.util.IntMap;
49 import com.jme3.util.IntMap.Entry;
50 import com.jme3.util.PlaceholderAssets;
51 import static com.jme3.util.xml.SAXUtil.*;
52 import java.io.IOException;
53 import java.io.InputStreamReader;
54 import java.nio.*;
55 import java.util.ArrayList;
56 import java.util.HashMap;
57 import java.util.List;
58 import java.util.logging.Level;
59 import java.util.logging.Logger;
60 import javax.xml.parsers.ParserConfigurationException;
61 import javax.xml.parsers.SAXParserFactory;
62 import org.xml.sax.Attributes;
63 import org.xml.sax.InputSource;
64 import org.xml.sax.SAXException;
65 import org.xml.sax.XMLReader;
66 import org.xml.sax.helpers.DefaultHandler;
67 
68 /**
69  * Loads Ogre3D mesh.xml files.
70  */
71 public class MeshLoader extends DefaultHandler implements AssetLoader {
72 
73     private static final Logger logger = Logger.getLogger(MeshLoader.class.getName());
74     public static boolean AUTO_INTERLEAVE = true;
75     public static boolean HARDWARE_SKINNING = false;
76     private static final Type[] TEXCOORD_TYPES =
77             new Type[]{
78         Type.TexCoord,
79         Type.TexCoord2,
80         Type.TexCoord3,
81         Type.TexCoord4,
82         Type.TexCoord5,
83         Type.TexCoord6,
84         Type.TexCoord7,
85         Type.TexCoord8,};
86     private AssetKey key;
87     private String meshName;
88     private String folderName;
89     private AssetManager assetManager;
90     private MaterialList materialList;
91     // Data per submesh/sharedgeom
92     private ShortBuffer sb;
93     private IntBuffer ib;
94     private FloatBuffer fb;
95     private VertexBuffer vb;
96     private Mesh mesh;
97     private Geometry geom;
98     private ByteBuffer indicesData;
99     private FloatBuffer weightsFloatData;
100     private boolean actuallyHasWeights = false;
101     private int vertCount;
102     private boolean usesSharedVerts;
103     private boolean usesBigIndices;
104     // Global data
105     private Mesh sharedMesh;
106     private int meshIndex = 0;
107     private int texCoordIndex = 0;
108     private String ignoreUntilEnd = null;
109     private List<Geometry> geoms = new ArrayList<Geometry>();
110     private ArrayList<Boolean> usesSharedMesh = new ArrayList<Boolean>();
111     private IntMap<List<VertexBuffer>> lodLevels = new IntMap<List<VertexBuffer>>();
112     private AnimData animData;
113 
MeshLoader()114     public MeshLoader() {
115         super();
116     }
117 
118     @Override
startDocument()119     public void startDocument() {
120         geoms.clear();
121         lodLevels.clear();
122 
123         sb = null;
124         ib = null;
125         fb = null;
126         vb = null;
127         mesh = null;
128         geom = null;
129         sharedMesh = null;
130 
131         usesSharedMesh.clear();
132         usesSharedVerts = false;
133         vertCount = 0;
134         meshIndex = 0;
135         texCoordIndex = 0;
136         ignoreUntilEnd = null;
137 
138         animData = null;
139 
140         actuallyHasWeights = false;
141         indicesData = null;
142         weightsFloatData = null;
143     }
144 
145     @Override
endDocument()146     public void endDocument() {
147     }
148 
pushIndex(int index)149     private void pushIndex(int index){
150         if (ib != null){
151             ib.put(index);
152         }else{
153             sb.put((short)index);
154         }
155     }
156 
pushFace(String v1, String v2, String v3)157     private void pushFace(String v1, String v2, String v3) throws SAXException {
158         // TODO: fan/strip support
159         switch (mesh.getMode()){
160             case Triangles:
161                 pushIndex(parseInt(v1));
162                 pushIndex(parseInt(v2));
163                 pushIndex(parseInt(v3));
164                 break;
165             case Lines:
166                 pushIndex(parseInt(v1));
167                 pushIndex(parseInt(v2));
168                 break;
169             case Points:
170                 pushIndex(parseInt(v1));
171                 break;
172         }
173     }
174 
175 //    private boolean isUsingSharedVerts(Geometry geom) {
176         // Old code for buffer sharer
177         //return geom.getUserData(UserData.JME_SHAREDMESH) != null;
178 //    }
179 
startFaces(String count)180     private void startFaces(String count) throws SAXException {
181         int numFaces = parseInt(count);
182         int indicesPerFace = 0;
183 
184         switch (mesh.getMode()){
185             case Triangles:
186                 indicesPerFace = 3;
187                 break;
188             case Lines:
189                 indicesPerFace = 2;
190                 break;
191             case Points:
192                 indicesPerFace = 1;
193                 break;
194             default:
195                 throw new SAXException("Strips or fans not supported!");
196         }
197 
198         int numIndices = indicesPerFace * numFaces;
199 
200         vb = new VertexBuffer(VertexBuffer.Type.Index);
201         if (!usesBigIndices) {
202             sb = BufferUtils.createShortBuffer(numIndices);
203             ib = null;
204             vb.setupData(Usage.Static, indicesPerFace, Format.UnsignedShort, sb);
205         } else {
206             ib = BufferUtils.createIntBuffer(numIndices);
207             sb = null;
208             vb.setupData(Usage.Static, indicesPerFace, Format.UnsignedInt, ib);
209         }
210         mesh.setBuffer(vb);
211     }
212 
applyMaterial(Geometry geom, String matName)213     private void applyMaterial(Geometry geom, String matName) {
214         Material mat = null;
215         if (matName.endsWith(".j3m")) {
216             // load as native jme3 material instance
217             try {
218                 mat = assetManager.loadMaterial(matName);
219             } catch (AssetNotFoundException ex){
220                 // Warning will be raised (see below)
221                 if (!ex.getMessage().equals(matName)){
222                     throw ex;
223                 }
224             }
225         } else {
226             if (materialList != null) {
227                 mat = materialList.get(matName);
228             }
229         }
230 
231         if (mat == null) {
232             logger.log(Level.WARNING, "Cannot locate {0} for model {1}", new Object[]{matName, key});
233             mat = PlaceholderAssets.getPlaceholderMaterial(assetManager);
234         }
235 
236         if (mat.isTransparent()) {
237             geom.setQueueBucket(Bucket.Transparent);
238         }
239 
240         geom.setMaterial(mat);
241     }
242 
startSubMesh(String matName, String usesharedvertices, String use32bitIndices, String opType)243     private void startSubMesh(String matName, String usesharedvertices, String use32bitIndices, String opType) throws SAXException {
244         mesh = new Mesh();
245         if (opType == null || opType.equals("triangle_list")) {
246             mesh.setMode(Mesh.Mode.Triangles);
247         //} else if (opType.equals("triangle_strip")) {
248         //    mesh.setMode(Mesh.Mode.TriangleStrip);
249         //} else if (opType.equals("triangle_fan")) {
250         //    mesh.setMode(Mesh.Mode.TriangleFan);
251         } else if (opType.equals("line_list")) {
252             mesh.setMode(Mesh.Mode.Lines);
253         } else {
254             throw new SAXException("Unsupported operation type: " + opType);
255         }
256 
257         usesBigIndices = parseBool(use32bitIndices, false);
258         usesSharedVerts = parseBool(usesharedvertices, false);
259         if (usesSharedVerts) {
260             usesSharedMesh.add(true);
261 
262             // Old code for buffer sharer
263             // import vertexbuffers from shared geom
264 //            IntMap<VertexBuffer> sharedBufs = sharedMesh.getBuffers();
265 //            for (Entry<VertexBuffer> entry : sharedBufs) {
266 //                mesh.setBuffer(entry.getValue());
267 //            }
268         }else{
269             usesSharedMesh.add(false);
270         }
271 
272         if (meshName == null) {
273             geom = new Geometry("OgreSubmesh-" + (++meshIndex), mesh);
274         } else {
275             geom = new Geometry(meshName + "-geom-" + (++meshIndex), mesh);
276         }
277 
278         if (usesSharedVerts) {
279             // Old code for buffer sharer
280             // this mesh is shared!
281             //geom.setUserData(UserData.JME_SHAREDMESH, sharedMesh);
282         }
283 
284         applyMaterial(geom, matName);
285         geoms.add(geom);
286     }
287 
startSharedGeom(String vertexcount)288     private void startSharedGeom(String vertexcount) throws SAXException {
289         sharedMesh = new Mesh();
290         vertCount = parseInt(vertexcount);
291         usesSharedVerts = false;
292 
293         geom = null;
294         mesh = sharedMesh;
295     }
296 
startGeometry(String vertexcount)297     private void startGeometry(String vertexcount) throws SAXException {
298         vertCount = parseInt(vertexcount);
299     }
300 
301     /**
302      * Normalizes weights if needed and finds largest amount of weights used
303      * for all vertices in the buffer.
304      */
endBoneAssigns()305     private void endBoneAssigns() {
306 //        if (mesh != sharedMesh && isUsingSharedVerts(geom)) {
307 //            return;
308 //        }
309 
310         if (!actuallyHasWeights){
311             // No weights were actually written (the tag didn't have any entries)
312             // remove those buffers
313             mesh.clearBuffer(Type.BoneIndex);
314             mesh.clearBuffer(Type.BoneWeight);
315 
316             weightsFloatData = null;
317             indicesData = null;
318 
319             return;
320         }
321 
322         //int vertCount = mesh.getVertexCount();
323         int maxWeightsPerVert = 0;
324         weightsFloatData.rewind();
325         for (int v = 0; v < vertCount; v++) {
326             float w0 = weightsFloatData.get(),
327                     w1 = weightsFloatData.get(),
328                     w2 = weightsFloatData.get(),
329                     w3 = weightsFloatData.get();
330 
331             if (w3 != 0) {
332                 maxWeightsPerVert = Math.max(maxWeightsPerVert, 4);
333             } else if (w2 != 0) {
334                 maxWeightsPerVert = Math.max(maxWeightsPerVert, 3);
335             } else if (w1 != 0) {
336                 maxWeightsPerVert = Math.max(maxWeightsPerVert, 2);
337             } else if (w0 != 0) {
338                 maxWeightsPerVert = Math.max(maxWeightsPerVert, 1);
339             }
340 
341             float sum = w0 + w1 + w2 + w3;
342             if (sum != 1f) {
343                 weightsFloatData.position(weightsFloatData.position() - 4);
344                 // compute new vals based on sum
345                 float sumToB = 1f / sum;
346                 weightsFloatData.put(w0 * sumToB);
347                 weightsFloatData.put(w1 * sumToB);
348                 weightsFloatData.put(w2 * sumToB);
349                 weightsFloatData.put(w3 * sumToB);
350             }
351         }
352         weightsFloatData.rewind();
353 
354         actuallyHasWeights = false;
355         weightsFloatData = null;
356         indicesData = null;
357 
358         mesh.setMaxNumWeights(maxWeightsPerVert);
359     }
360 
startBoneAssigns()361     private void startBoneAssigns() {
362         if (mesh != sharedMesh && usesSharedVerts) {
363             // will use bone assignments from shared mesh (?)
364             return;
365         }
366 
367         // current mesh will have bone assigns
368         //int vertCount = mesh.getVertexCount();
369         // each vertex has
370         // - 4 bone weights
371         // - 4 bone indices
372         if (HARDWARE_SKINNING) {
373             weightsFloatData = BufferUtils.createFloatBuffer(vertCount * 4);
374             indicesData = BufferUtils.createByteBuffer(vertCount * 4);
375         } else {
376             // create array-backed buffers if software skinning for access speed
377             weightsFloatData = FloatBuffer.allocate(vertCount * 4);
378             indicesData = ByteBuffer.allocate(vertCount * 4);
379         }
380 
381         VertexBuffer weights = new VertexBuffer(Type.BoneWeight);
382         VertexBuffer indices = new VertexBuffer(Type.BoneIndex);
383 
384         Usage usage = HARDWARE_SKINNING ? Usage.Static : Usage.CpuOnly;
385         weights.setupData(usage, 4, Format.Float, weightsFloatData);
386         indices.setupData(usage, 4, Format.UnsignedByte, indicesData);
387 
388         mesh.setBuffer(weights);
389         mesh.setBuffer(indices);
390     }
391 
startVertexBuffer(Attributes attribs)392     private void startVertexBuffer(Attributes attribs) throws SAXException {
393         if (parseBool(attribs.getValue("positions"), false)) {
394             vb = new VertexBuffer(Type.Position);
395             fb = BufferUtils.createFloatBuffer(vertCount * 3);
396             vb.setupData(Usage.Static, 3, Format.Float, fb);
397             mesh.setBuffer(vb);
398         }
399         if (parseBool(attribs.getValue("normals"), false)) {
400             vb = new VertexBuffer(Type.Normal);
401             fb = BufferUtils.createFloatBuffer(vertCount * 3);
402             vb.setupData(Usage.Static, 3, Format.Float, fb);
403             mesh.setBuffer(vb);
404         }
405         if (parseBool(attribs.getValue("colours_diffuse"), false)) {
406             vb = new VertexBuffer(Type.Color);
407             fb = BufferUtils.createFloatBuffer(vertCount * 4);
408             vb.setupData(Usage.Static, 4, Format.Float, fb);
409             mesh.setBuffer(vb);
410         }
411         if (parseBool(attribs.getValue("tangents"), false)) {
412             int dimensions = parseInt(attribs.getValue("tangent_dimensions"), 3);
413             vb = new VertexBuffer(Type.Tangent);
414             fb = BufferUtils.createFloatBuffer(vertCount * dimensions);
415             vb.setupData(Usage.Static, dimensions, Format.Float, fb);
416             mesh.setBuffer(vb);
417         }
418         if (parseBool(attribs.getValue("binormals"), false)) {
419             vb = new VertexBuffer(Type.Binormal);
420             fb = BufferUtils.createFloatBuffer(vertCount * 3);
421             vb.setupData(Usage.Static, 3, Format.Float, fb);
422             mesh.setBuffer(vb);
423         }
424 
425         int texCoords = parseInt(attribs.getValue("texture_coords"), 0);
426         for (int i = 0; i < texCoords; i++) {
427             int dims = parseInt(attribs.getValue("texture_coord_dimensions_" + i), 2);
428             if (dims < 1 || dims > 4) {
429                 throw new SAXException("Texture coord dimensions must be 1 <= dims <= 4");
430             }
431 
432             if (i <= 7) {
433                 vb = new VertexBuffer(TEXCOORD_TYPES[i]);
434             } else {
435                 // more than 8 texture coordinates are not supported by ogre.
436                 throw new SAXException("More than 8 texture coordinates not supported");
437             }
438             fb = BufferUtils.createFloatBuffer(vertCount * dims);
439             vb.setupData(Usage.Static, dims, Format.Float, fb);
440             mesh.setBuffer(vb);
441         }
442     }
443 
startVertex()444     private void startVertex() {
445         texCoordIndex = 0;
446     }
447 
pushAttrib(Type type, Attributes attribs)448     private void pushAttrib(Type type, Attributes attribs) throws SAXException {
449         try {
450             FloatBuffer buf = (FloatBuffer) mesh.getBuffer(type).getData();
451             buf.put(parseFloat(attribs.getValue("x"))).put(parseFloat(attribs.getValue("y"))).put(parseFloat(attribs.getValue("z")));
452         } catch (Exception ex) {
453             throw new SAXException("Failed to push attrib", ex);
454         }
455     }
456 
pushTangent(Attributes attribs)457     private void pushTangent(Attributes attribs) throws SAXException {
458         try {
459             VertexBuffer tangentBuf = mesh.getBuffer(Type.Tangent);
460             FloatBuffer buf = (FloatBuffer) tangentBuf.getData();
461             buf.put(parseFloat(attribs.getValue("x"))).put(parseFloat(attribs.getValue("y"))).put(parseFloat(attribs.getValue("z")));
462             if (tangentBuf.getNumComponents() == 4) {
463                 buf.put(parseFloat(attribs.getValue("w")));
464             }
465         } catch (Exception ex) {
466             throw new SAXException("Failed to push attrib", ex);
467         }
468     }
469 
pushTexCoord(Attributes attribs)470     private void pushTexCoord(Attributes attribs) throws SAXException {
471         if (texCoordIndex >= 8) {
472             return; // More than 8 not supported by ogre.
473         }
474         Type type = TEXCOORD_TYPES[texCoordIndex];
475 
476         VertexBuffer tcvb = mesh.getBuffer(type);
477         FloatBuffer buf = (FloatBuffer) tcvb.getData();
478 
479         buf.put(parseFloat(attribs.getValue("u")));
480         if (tcvb.getNumComponents() >= 2) {
481             buf.put(parseFloat(attribs.getValue("v")));
482             if (tcvb.getNumComponents() >= 3) {
483                 buf.put(parseFloat(attribs.getValue("w")));
484                 if (tcvb.getNumComponents() == 4) {
485                     buf.put(parseFloat(attribs.getValue("x")));
486                 }
487             }
488         }
489 
490         texCoordIndex++;
491     }
492 
pushColor(Attributes attribs)493     private void pushColor(Attributes attribs) throws SAXException {
494         FloatBuffer buf = (FloatBuffer) mesh.getBuffer(Type.Color).getData();
495         String value = parseString(attribs.getValue("value"));
496         String[] vals = value.split("\\s");
497         if (vals.length != 3 && vals.length != 4) {
498             throw new SAXException("Color value must contain 3 or 4 components");
499         }
500 
501         ColorRGBA color = new ColorRGBA();
502         color.r = parseFloat(vals[0]);
503         color.g = parseFloat(vals[1]);
504         color.b = parseFloat(vals[2]);
505         if (vals.length == 3) {
506             color.a = 1f;
507         } else {
508             color.a = parseFloat(vals[3]);
509         }
510 
511         buf.put(color.r).put(color.g).put(color.b).put(color.a);
512     }
513 
startLodFaceList(String submeshindex, String numfaces)514     private void startLodFaceList(String submeshindex, String numfaces) {
515         int index = Integer.parseInt(submeshindex);
516         mesh = geoms.get(index).getMesh();
517         int faceCount = Integer.parseInt(numfaces);
518 
519         VertexBuffer originalIndexBuffer = mesh.getBuffer(Type.Index);
520         vb = new VertexBuffer(VertexBuffer.Type.Index);
521         if (originalIndexBuffer.getFormat() == Format.UnsignedInt){
522             // LOD buffer should also be integer
523             ib = BufferUtils.createIntBuffer(faceCount * 3);
524             sb = null;
525             vb.setupData(Usage.Static, 3, Format.UnsignedInt, ib);
526         }else{
527             sb = BufferUtils.createShortBuffer(faceCount * 3);
528             ib = null;
529             vb.setupData(Usage.Static, 3, Format.UnsignedShort, sb);
530         }
531 
532         List<VertexBuffer> levels = lodLevels.get(index);
533         if (levels == null) {
534             // Create the LOD levels list
535             levels = new ArrayList<VertexBuffer>();
536 
537             // Add the first LOD level (always the original index buffer)
538             levels.add(originalIndexBuffer);
539             lodLevels.put(index, levels);
540         }
541         levels.add(vb);
542     }
543 
startLevelOfDetail(String numlevels)544     private void startLevelOfDetail(String numlevels) {
545 //        numLevels = Integer.parseInt(numlevels);
546     }
547 
endLevelOfDetail()548     private void endLevelOfDetail() {
549         // set the lod data for each mesh
550         for (Entry<List<VertexBuffer>> entry : lodLevels) {
551             Mesh m = geoms.get(entry.getKey()).getMesh();
552             List<VertexBuffer> levels = entry.getValue();
553             VertexBuffer[] levelArray = new VertexBuffer[levels.size()];
554             levels.toArray(levelArray);
555             m.setLodLevels(levelArray);
556         }
557     }
558 
startLodGenerated(String depthsqr)559     private void startLodGenerated(String depthsqr) {
560     }
561 
pushBoneAssign(String vertIndex, String boneIndex, String weight)562     private void pushBoneAssign(String vertIndex, String boneIndex, String weight) throws SAXException {
563         int vert = parseInt(vertIndex);
564         float w = parseFloat(weight);
565         byte bone = (byte) parseInt(boneIndex);
566 
567         assert bone >= 0;
568         assert vert >= 0 && vert < mesh.getVertexCount();
569 
570         int i;
571         float v = 0;
572         // see which weights are unused for a given bone
573         for (i = vert * 4; i < vert * 4 + 4; i++) {
574             v = weightsFloatData.get(i);
575             if (v == 0) {
576                 break;
577             }
578         }
579         if (v != 0) {
580             logger.log(Level.WARNING, "Vertex {0} has more than 4 weights per vertex! Ignoring..", vert);
581             return;
582         }
583 
584         weightsFloatData.put(i, w);
585         indicesData.put(i, bone);
586         actuallyHasWeights = true;
587     }
588 
589     private void startSkeleton(String name) {
590         AssetKey assetKey = new AssetKey(folderName + name + ".xml");
591         try {
592             animData = (AnimData) assetManager.loadAsset(assetKey);
593         } catch (AssetNotFoundException ex){
594             logger.log(Level.WARNING, "Cannot locate {0} for model {1}", new Object[]{assetKey, key});
595             animData = null;
596         }
597     }
598 
599     private void startSubmeshName(String indexStr, String nameStr) {
600         int index = Integer.parseInt(indexStr);
601         geoms.get(index).setName(nameStr);
602     }
603 
604     @Override
605     public void startElement(String uri, String localName, String qName, Attributes attribs) throws SAXException {
606         if (ignoreUntilEnd != null) {
607             return;
608         }
609 
610         if (qName.equals("texcoord")) {
611             pushTexCoord(attribs);
612         } else if (qName.equals("vertexboneassignment")) {
613             pushBoneAssign(attribs.getValue("vertexindex"),
614                     attribs.getValue("boneindex"),
615                     attribs.getValue("weight"));
616         } else if (qName.equals("face")) {
617             pushFace(attribs.getValue("v1"),
618                     attribs.getValue("v2"),
619                     attribs.getValue("v3"));
620         } else if (qName.equals("position")) {
621             pushAttrib(Type.Position, attribs);
622         } else if (qName.equals("normal")) {
623             pushAttrib(Type.Normal, attribs);
624         } else if (qName.equals("tangent")) {
625             pushTangent(attribs);
626         } else if (qName.equals("binormal")) {
627             pushAttrib(Type.Binormal, attribs);
628         } else if (qName.equals("colour_diffuse")) {
629             pushColor(attribs);
630         } else if (qName.equals("vertex")) {
631             startVertex();
632         } else if (qName.equals("faces")) {
633             startFaces(attribs.getValue("count"));
634         } else if (qName.equals("geometry")) {
635             String count = attribs.getValue("vertexcount");
636             if (count == null) {
637                 count = attribs.getValue("count");
638             }
639             startGeometry(count);
640         } else if (qName.equals("vertexbuffer")) {
641             startVertexBuffer(attribs);
642         } else if (qName.equals("lodfacelist")) {
643             startLodFaceList(attribs.getValue("submeshindex"),
644                     attribs.getValue("numfaces"));
645         } else if (qName.equals("lodgenerated")) {
646             startLodGenerated(attribs.getValue("fromdepthsquared"));
647         } else if (qName.equals("levelofdetail")) {
648             startLevelOfDetail(attribs.getValue("numlevels"));
649         } else if (qName.equals("boneassignments")) {
650             startBoneAssigns();
651         } else if (qName.equals("submesh")) {
652             startSubMesh(attribs.getValue("material"),
653                     attribs.getValue("usesharedvertices"),
654                     attribs.getValue("use32bitindexes"),
655                     attribs.getValue("operationtype"));
656         } else if (qName.equals("sharedgeometry")) {
657             String count = attribs.getValue("vertexcount");
658             if (count == null) {
659                 count = attribs.getValue("count");
660             }
661 
662             if (count != null && !count.equals("0")) {
663                 startSharedGeom(count);
664             }
665         } else if (qName.equals("submeshes")) {
666             // ok
667         } else if (qName.equals("skeletonlink")) {
668             startSkeleton(attribs.getValue("name"));
669         } else if (qName.equals("submeshnames")) {
670             // ok
671         } else if (qName.equals("submeshname")) {
672             startSubmeshName(attribs.getValue("index"), attribs.getValue("name"));
673         } else if (qName.equals("mesh")) {
674             // ok
675         } else {
676             logger.log(Level.WARNING, "Unknown tag: {0}. Ignoring.", qName);
677             ignoreUntilEnd = qName;
678         }
679     }
680 
681     @Override
682     public void endElement(String uri, String name, String qName) {
683         if (ignoreUntilEnd != null) {
684             if (ignoreUntilEnd.equals(qName)) {
685                 ignoreUntilEnd = null;
686             }
687             return;
688         }
689 
690         if (qName.equals("submesh")) {
691             usesBigIndices = false;
692             geom = null;
693             mesh = null;
694         } else if (qName.equals("submeshes")) {
695             // IMPORTANT: restore sharedmesh, for use with shared boneweights
696             geom = null;
697             mesh = sharedMesh;
698             usesSharedVerts = false;
699         } else if (qName.equals("faces")) {
700             if (ib != null) {
701                 ib.flip();
702             } else {
703                 sb.flip();
704             }
705 
706             vb = null;
707             ib = null;
708             sb = null;
709         } else if (qName.equals("vertexbuffer")) {
710             fb = null;
711             vb = null;
712         } else if (qName.equals("geometry")
713                 || qName.equals("sharedgeometry")) {
714             // finish writing to buffers
715             for (VertexBuffer buf : mesh.getBufferList().getArray()) {
716                 Buffer data = buf.getData();
717                 if (data.position() != 0) {
718                     data.flip();
719                 }
720             }
721             mesh.updateBound();
722             mesh.setStatic();
723 
724             if (qName.equals("sharedgeometry")) {
725                 geom = null;
726                 mesh = null;
727             }
728         } else if (qName.equals("lodfacelist")) {
729             sb.flip();
730             vb = null;
731             sb = null;
732         } else if (qName.equals("levelofdetail")) {
733             endLevelOfDetail();
734         } else if (qName.equals("boneassignments")) {
735             endBoneAssigns();
736         }
737     }
738 
739     @Override
740     public void characters(char ch[], int start, int length) {
741     }
742 
743     private Node compileModel() {
744         Node model = new Node(meshName + "-ogremesh");
745 
746         for (int i = 0; i < geoms.size(); i++) {
747             Geometry g = geoms.get(i);
748             Mesh m = g.getMesh();
749 
750             // New code for buffer extract
751             if (sharedMesh != null && usesSharedMesh.get(i)) {
752                 m.extractVertexData(sharedMesh);
753             }
754 
755             // Old code for buffer sharer
756             //if (sharedMesh != null && isUsingSharedVerts(g)) {
757             //    m.setBound(sharedMesh.getBound().clone());
758             //}
759             model.attachChild(geoms.get(i));
760         }
761 
762         // Do not attach shared geometry to the node!
763 
764         if (animData != null) {
765             // This model uses animation
766 
767             // Old code for buffer sharer
768             // generate bind pose for mesh
769             // ONLY if not using shared geometry
770             // This includes the shared geoemtry itself actually
771             //if (sharedMesh != null) {
772             //    sharedMesh.generateBindPose(!HARDWARE_SKINNING);
773             //}
774 
775             for (int i = 0; i < geoms.size(); i++) {
776                 Geometry g = geoms.get(i);
777                 Mesh m = geoms.get(i).getMesh();
778 
779                 m.generateBindPose(!HARDWARE_SKINNING);
780 
781                 // Old code for buffer sharer
782                 //boolean useShared = isUsingSharedVerts(g);
783                 //if (!useShared) {
784                     // create bind pose
785                     //m.generateBindPose(!HARDWARE_SKINNING);
786                 //}
787             }
788 
789             // Put the animations in the AnimControl
790             HashMap<String, Animation> anims = new HashMap<String, Animation>();
791             ArrayList<Animation> animList = animData.anims;
792             for (int i = 0; i < animList.size(); i++) {
793                 Animation anim = animList.get(i);
794                 anims.put(anim.getName(), anim);
795             }
796 
797             AnimControl ctrl = new AnimControl(animData.skeleton);
798             ctrl.setAnimations(anims);
799             model.addControl(ctrl);
800 
801             // Put the skeleton in the skeleton control
802             SkeletonControl skeletonControl = new SkeletonControl(animData.skeleton);
803 
804             // This will acquire the targets from the node
805             model.addControl(skeletonControl);
806         }
807 
808         return model;
809     }
810 
811     public Object load(AssetInfo info) throws IOException {
812         try {
813             key = info.getKey();
814             meshName = key.getName();
815             folderName = key.getFolder();
816             String ext = key.getExtension();
817             meshName = meshName.substring(0, meshName.length() - ext.length() - 1);
818             if (folderName != null && folderName.length() > 0) {
819                 meshName = meshName.substring(folderName.length());
820             }
821             assetManager = info.getManager();
822 
823             if (key instanceof OgreMeshKey) {
824                 // OgreMeshKey is being used, try getting the material list
825                 // from it
826                 OgreMeshKey meshKey = (OgreMeshKey) key;
827                 materialList = meshKey.getMaterialList();
828                 String materialName = meshKey.getMaterialName();
829 
830                 // Material list not set but material name is available
831                 if (materialList == null && materialName != null) {
832                     OgreMaterialKey materialKey = new OgreMaterialKey(folderName + materialName + ".material");
833                     try {
834                         materialList = (MaterialList) assetManager.loadAsset(materialKey);
835                     } catch (AssetNotFoundException e) {
836                         logger.log(Level.WARNING, "Cannot locate {0} for model {1}", new Object[]{materialKey, key});
837                     }
838                 }
839             }else{
840                 // Make sure to reset it to null so that previous state
841                 // doesn't leak onto this one
842                 materialList = null;
843             }
844 
845             // If for some reason material list could not be found through
846             // OgreMeshKey, or if regular ModelKey specified, load using
847             // default method.
848             if (materialList == null){
849                 OgreMaterialKey materialKey = new OgreMaterialKey(folderName + meshName + ".material");
850                 try {
851                     materialList = (MaterialList) assetManager.loadAsset(materialKey);
852                 } catch (AssetNotFoundException e) {
853                     logger.log(Level.WARNING, "Cannot locate {0} for model {1}", new Object[]{ materialKey, key });
854                 }
855             }
856 
857             // Added by larynx 25.06.2011
858             // Android needs the namespace aware flag set to true
859             // Kirill 30.06.2011
860             // Now, hack is applied for both desktop and android to avoid
861             // checking with JmeSystem.
862             SAXParserFactory factory = SAXParserFactory.newInstance();
863             factory.setNamespaceAware(true);
864 
865             XMLReader xr = factory.newSAXParser().getXMLReader();
866             xr.setContentHandler(this);
867             xr.setErrorHandler(this);
868 
869             InputStreamReader r = null;
870             try {
871                 r = new InputStreamReader(info.openStream());
872                 xr.parse(new InputSource(r));
873             } finally {
874                 if (r != null){
875                     r.close();
876                 }
877             }
878 
879             return compileModel();
880         } catch (SAXException ex) {
881             IOException ioEx = new IOException("Error while parsing Ogre3D mesh.xml");
882             ioEx.initCause(ex);
883             throw ioEx;
884         } catch (ParserConfigurationException ex) {
885             IOException ioEx = new IOException("Error while parsing Ogre3D mesh.xml");
886             ioEx.initCause(ex);
887             throw ioEx;
888         }
889 
890     }
891 }
892