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.AnimControl;
35import com.jme3.animation.Animation;
36import com.jme3.animation.SkeletonControl;
37import com.jme3.asset.*;
38import com.jme3.material.Material;
39import com.jme3.material.MaterialList;
40import com.jme3.math.ColorRGBA;
41import com.jme3.renderer.queue.RenderQueue.Bucket;
42import com.jme3.scene.*;
43import com.jme3.scene.VertexBuffer.Format;
44import com.jme3.scene.VertexBuffer.Type;
45import com.jme3.scene.VertexBuffer.Usage;
46import com.jme3.scene.plugins.ogre.matext.OgreMaterialKey;
47import com.jme3.util.BufferUtils;
48import com.jme3.util.IntMap;
49import com.jme3.util.IntMap.Entry;
50import com.jme3.util.PlaceholderAssets;
51import static com.jme3.util.xml.SAXUtil.*;
52import java.io.IOException;
53import java.io.InputStreamReader;
54import java.nio.*;
55import java.util.ArrayList;
56import java.util.HashMap;
57import java.util.List;
58import java.util.logging.Level;
59import java.util.logging.Logger;
60import javax.xml.parsers.ParserConfigurationException;
61import javax.xml.parsers.SAXParserFactory;
62import org.xml.sax.Attributes;
63import org.xml.sax.InputSource;
64import org.xml.sax.SAXException;
65import org.xml.sax.XMLReader;
66import org.xml.sax.helpers.DefaultHandler;
67
68/**
69 * Loads Ogre3D mesh.xml files.
70 */
71public 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
114    public MeshLoader() {
115        super();
116    }
117
118    @Override
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
146    public void endDocument() {
147    }
148
149    private void pushIndex(int index){
150        if (ib != null){
151            ib.put(index);
152        }else{
153            sb.put((short)index);
154        }
155    }
156
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
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
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
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
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
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     */
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
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
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
444    private void startVertex() {
445        texCoordIndex = 0;
446    }
447
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
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
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
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
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
544    private void startLevelOfDetail(String numlevels) {
545//        numLevels = Integer.parseInt(numlevels);
546    }
547
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
559    private void startLodGenerated(String depthsqr) {
560    }
561
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