1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.scenegraph;
18import com.android.scenegraph.CompoundTransform.TranslateComponent;
19import com.android.scenegraph.CompoundTransform.RotateComponent;
20import com.android.scenegraph.CompoundTransform.ScaleComponent;
21import java.io.IOException;
22import java.io.InputStream;
23import java.util.ArrayList;
24import java.util.Iterator;
25import java.util.List;
26import java.util.StringTokenizer;
27import java.util.HashMap;
28
29import javax.xml.parsers.DocumentBuilder;
30import javax.xml.parsers.DocumentBuilderFactory;
31import javax.xml.parsers.ParserConfigurationException;
32
33import org.w3c.dom.Document;
34import org.w3c.dom.Element;
35import org.w3c.dom.Node;
36import org.w3c.dom.NodeList;
37import org.xml.sax.SAXException;
38
39import android.renderscript.*;
40import android.util.Log;
41
42public class ColladaParser {
43    static final String TAG = "ColladaParser";
44    Document mDom;
45
46    HashMap<String, LightBase> mLights;
47    HashMap<String, Camera> mCameras;
48    HashMap<String, ArrayList<ShaderParam> > mEffectsParams;
49    HashMap<String, Texture2D> mImages;
50    HashMap<String, Texture2D> mSamplerImageMap;
51    HashMap<String, String> mMeshIdNameMap;
52    Scene mScene;
53
54    String mRootDir;
55
56    String toString(Float3 v) {
57        String valueStr = v.x + " " + v.y + " " + v.z;
58        return valueStr;
59    }
60
61    String toString(Float4 v) {
62        String valueStr = v.x + " " + v.y + " " + v.z + " " + v.w;
63        return valueStr;
64    }
65
66    public ColladaParser(){
67        mLights = new HashMap<String, LightBase>();
68        mCameras = new HashMap<String, Camera>();
69        mEffectsParams = new HashMap<String, ArrayList<ShaderParam> >();
70        mImages = new HashMap<String, Texture2D>();
71        mMeshIdNameMap = new HashMap<String, String>();
72    }
73
74    public void init(InputStream is, String rootDir) {
75        mLights.clear();
76        mCameras.clear();
77        mEffectsParams.clear();
78
79        mRootDir = rootDir;
80
81        long start = System.currentTimeMillis();
82        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
83        try {
84            DocumentBuilder db = dbf.newDocumentBuilder();
85            mDom = db.parse(is);
86        } catch(ParserConfigurationException e) {
87            e.printStackTrace();
88        } catch(SAXException e) {
89            e.printStackTrace();
90        } catch(IOException e) {
91            e.printStackTrace();
92        }
93        long end = System.currentTimeMillis();
94        Log.v("TIMER", "    Parse time: " + (end - start));
95        exportSceneData();
96    }
97
98    Scene getScene() {
99        return mScene;
100    }
101
102    private void exportSceneData(){
103        mScene = new Scene();
104
105        Element docEle = mDom.getDocumentElement();
106        NodeList nl = docEle.getElementsByTagName("light");
107        if (nl != null) {
108            for(int i = 0; i < nl.getLength(); i++) {
109                Element l = (Element)nl.item(i);
110                convertLight(l);
111            }
112        }
113
114        nl = docEle.getElementsByTagName("camera");
115        if (nl != null) {
116            for(int i = 0; i < nl.getLength(); i++) {
117                Element c = (Element)nl.item(i);
118                convertCamera(c);
119            }
120        }
121
122        nl = docEle.getElementsByTagName("image");
123        if (nl != null) {
124            for(int i = 0; i < nl.getLength(); i++) {
125                Element img = (Element)nl.item(i);
126                convertImage(img);
127            }
128        }
129
130        nl = docEle.getElementsByTagName("effect");
131        if (nl != null) {
132            for(int i = 0; i < nl.getLength(); i++) {
133                Element e = (Element)nl.item(i);
134                convertEffects(e);
135            }
136        }
137
138        // Material is just a link to the effect
139        nl = docEle.getElementsByTagName("material");
140        if (nl != null) {
141            for(int i = 0; i < nl.getLength(); i++) {
142                Element m = (Element)nl.item(i);
143                convertMaterials(m);
144            }
145        }
146
147        // Look through the geometry list and build up a correlation between id's and names
148        nl = docEle.getElementsByTagName("geometry");
149        if (nl != null) {
150            for(int i = 0; i < nl.getLength(); i++) {
151                Element m = (Element)nl.item(i);
152                convertGeometries(m);
153            }
154        }
155
156
157        nl = docEle.getElementsByTagName("visual_scene");
158        if (nl != null) {
159            for(int i = 0; i < nl.getLength(); i++) {
160                Element s = (Element)nl.item(i);
161                getScene(s);
162            }
163        }
164    }
165
166    private void getRenderable(Element shape, Transform t) {
167        String geoURL = shape.getAttribute("url").substring(1);
168        String geoName = mMeshIdNameMap.get(geoURL);
169        if (geoName != null) {
170            geoURL = geoName;
171        }
172        //RenderableGroup group = new RenderableGroup();
173        //group.setName(geoURL.substring(1));
174        //mScene.appendRenderable(group);
175        NodeList nl = shape.getElementsByTagName("instance_material");
176        if (nl != null) {
177            for(int i = 0; i < nl.getLength(); i++) {
178                Element materialRef = (Element)nl.item(i);
179                String meshIndexName = materialRef.getAttribute("symbol");
180                String materialName = materialRef.getAttribute("target");
181
182                Renderable d = new Renderable();
183                d.setMesh(geoURL, meshIndexName);
184                d.setMaterialName(materialName.substring(1));
185                d.setName(geoURL);
186
187                //Log.v(TAG, "Created drawable geo " + geoURL + " index " + meshIndexName + " material " + materialName);
188
189                d.setTransform(t);
190                //Log.v(TAG, "Set source param " + t.getName());
191
192                // Now find all the parameters that exist on the material
193                ArrayList<ShaderParam> materialParams;
194                materialParams = mEffectsParams.get(materialName.substring(1));
195                for (int pI = 0; pI < materialParams.size(); pI ++) {
196                    d.appendSourceParams(materialParams.get(pI));
197                    //Log.v(TAG, "Set source param i: " + pI + " name " + materialParams.get(pI).getParamName());
198                }
199                mScene.appendRenderable(d);
200                //group.appendChildren(d);
201            }
202        }
203    }
204
205    private void updateLight(Element shape, Transform t) {
206        String lightURL = shape.getAttribute("url");
207        // collada uses a uri structure to link things,
208        // but we ignore it for now and do a simple search
209        LightBase light = mLights.get(lightURL.substring(1));
210        if (light != null) {
211            light.setTransform(t);
212            //Log.v(TAG, "Set Light " + light.getName() + " " + t.getName());
213        }
214    }
215
216    private void updateCamera(Element shape, Transform t) {
217        String camURL = shape.getAttribute("url");
218        // collada uses a uri structure to link things,
219        // but we ignore it for now and do a simple search
220        Camera cam = mCameras.get(camURL.substring(1));
221        if (cam != null) {
222            cam.setTransform(t);
223            //Log.v(TAG, "Set Camera " + cam.getName() + " " + t.getName());
224        }
225    }
226
227    private void getNode(Element node, Transform parent, String indent) {
228        String name = node.getAttribute("name");
229        String id = node.getAttribute("id");
230        CompoundTransform current = new CompoundTransform();
231        current.setName(name);
232        if (parent != null) {
233            parent.appendChild(current);
234        } else {
235            mScene.appendTransform(current);
236        }
237
238        mScene.addToTransformMap(current);
239
240        //Log.v(TAG, indent + "|");
241        //Log.v(TAG, indent + "[" + name + "]");
242
243        Node childNode = node.getFirstChild();
244        while (childNode != null) {
245            if (childNode.getNodeType() == Node.ELEMENT_NODE) {
246                Element field = (Element)childNode;
247                String fieldName = field.getTagName();
248                String description = field.getAttribute("sid");
249                if (fieldName.equals("translate")) {
250                    Float3 value = getFloat3(field);
251                    current.addTranslate(description, value);
252                    //Log.v(TAG, indent + " translate " + description + toString(value));
253                } else if (fieldName.equals("rotate")) {
254                    Float4 value = getFloat4(field);
255                    //Log.v(TAG, indent + " rotate " + description + toString(value));
256                    Float3 axis = new Float3(value.x, value.y, value.z);
257                    current.addRotate(description, axis, value.w);
258                } else if (fieldName.equals("scale")) {
259                    Float3 value = getFloat3(field);
260                    //Log.v(TAG, indent + " scale " + description + toString(value));
261                    current.addScale(description, value);
262                } else if (fieldName.equals("instance_geometry")) {
263                    getRenderable(field, current);
264                } else if (fieldName.equals("instance_light")) {
265                    updateLight(field, current);
266                } else if (fieldName.equals("instance_camera")) {
267                    updateCamera(field, current);
268                } else if (fieldName.equals("node")) {
269                    getNode(field, current, indent + "   ");
270                }
271            }
272            childNode = childNode.getNextSibling();
273        }
274    }
275
276    // This will find the actual texture node, which is sometimes hidden behind a sampler
277    // and sometimes referenced directly
278    Texture2D getTexture(String samplerName) {
279        String texName = samplerName;
280
281        // Check to see if the image file is hidden by a sampler surface link combo
282        Element sampler = mDom.getElementById(samplerName);
283        if (sampler != null) {
284            NodeList nl = sampler.getElementsByTagName("source");
285            if (nl != null && nl.getLength() == 1) {
286                Element ref = (Element)nl.item(0);
287                String surfaceName = getString(ref);
288                if (surfaceName == null) {
289                    return null;
290                }
291
292                Element surface = mDom.getElementById(surfaceName);
293                if (surface == null) {
294                    return null;
295                }
296                nl = surface.getElementsByTagName("init_from");
297                if (nl != null && nl.getLength() == 1) {
298                    ref = (Element)nl.item(0);
299                    texName = getString(ref);
300                }
301            }
302        }
303
304        //Log.v(TAG, "Extracted texture name " + texName);
305        return mImages.get(texName);
306    }
307
308    void extractParams(Element fx, ArrayList<ShaderParam> params) {
309        Node paramNode = fx.getFirstChild();
310        while (paramNode != null) {
311            if (paramNode.getNodeType() == Node.ELEMENT_NODE) {
312                String name = paramNode.getNodeName();
313                // Now find what type it is
314                Node typeNode = paramNode.getFirstChild();
315                while (typeNode != null && typeNode.getNodeType() != Node.ELEMENT_NODE) {
316                    typeNode = typeNode.getNextSibling();
317                }
318                String paramType = typeNode.getNodeName();
319                Element typeElem = (Element)typeNode;
320                ShaderParam sceneParam = null;
321                if (paramType.equals("color")) {
322                    Float4Param f4p = new Float4Param(name);
323                    Float4 value = getFloat4(typeElem);
324                    f4p.setValue(value);
325                    sceneParam = f4p;
326                    //Log.v(TAG, "Extracted " + sceneParam.getParamName() + " value " + toString(value));
327                } else if (paramType.equals("float")) {
328                    Float4Param f4p = new Float4Param(name);
329                    float value = getFloat(typeElem);
330                    f4p.setValue(new Float4(value, value, value, value));
331                    sceneParam = f4p;
332                    //Log.v(TAG, "Extracted " + sceneParam.getParamName() + " value " + value);
333                }  else if (paramType.equals("texture")) {
334                    String samplerName = typeElem.getAttribute("texture");
335                    Texture2D tex = getTexture(samplerName);
336                    TextureParam texP = new TextureParam(name);
337                    texP.setTexture(tex);
338                    sceneParam = texP;
339                    //Log.v(TAG, "Extracted texture " + tex);
340                }
341                if (sceneParam != null) {
342                    params.add(sceneParam);
343                }
344            }
345            paramNode = paramNode.getNextSibling();
346        }
347    }
348
349    private void convertMaterials(Element mat) {
350        String id = mat.getAttribute("id");
351        NodeList nl = mat.getElementsByTagName("instance_effect");
352        if (nl != null && nl.getLength() == 1) {
353            Element ref = (Element)nl.item(0);
354            String url = ref.getAttribute("url");
355            ArrayList<ShaderParam> params = mEffectsParams.get(url.substring(1));
356            mEffectsParams.put(id, params);
357        }
358    }
359
360    private void convertGeometries(Element geo) {
361        String id = geo.getAttribute("id");
362        String name = geo.getAttribute("name");
363        if (!id.equals(name)) {
364            mMeshIdNameMap.put(id, name);
365        }
366    }
367
368    private void convertEffects(Element fx) {
369        String id = fx.getAttribute("id");
370        ArrayList<ShaderParam> params = new ArrayList<ShaderParam>();
371
372        NodeList nl = fx.getElementsByTagName("newparam");
373        if (nl != null) {
374            for(int i = 0; i < nl.getLength(); i++) {
375                Element field = (Element)nl.item(i);
376                field.setIdAttribute("sid", true);
377            }
378        }
379
380        nl = fx.getElementsByTagName("blinn");
381        if (nl != null) {
382            for(int i = 0; i < nl.getLength(); i++) {
383                Element field = (Element)nl.item(i);
384                //Log.v(TAG, "blinn");
385                extractParams(field, params);
386            }
387        }
388        nl = fx.getElementsByTagName("lambert");
389        if (nl != null) {
390            for(int i = 0; i < nl.getLength(); i++) {
391                Element field = (Element)nl.item(i);
392                //Log.v(TAG, "lambert");
393                extractParams(field, params);
394            }
395        }
396        nl = fx.getElementsByTagName("phong");
397        if (nl != null) {
398            for(int i = 0; i < nl.getLength(); i++) {
399                Element field = (Element)nl.item(i);
400                //Log.v(TAG, "phong");
401                extractParams(field, params);
402            }
403        }
404        mEffectsParams.put(id, params);
405    }
406
407    private void convertLight(Element light) {
408        String name = light.getAttribute("name");
409        String id = light.getAttribute("id");
410
411        // Determine type
412        String[] knownTypes = { "point", "spot", "directional" };
413        final int POINT_LIGHT = 0;
414        final int SPOT_LIGHT = 1;
415        final int DIR_LIGHT = 2;
416        int type = -1;
417        for (int i = 0; i < knownTypes.length; i ++) {
418            NodeList nl = light.getElementsByTagName(knownTypes[i]);
419            if (nl != null && nl.getLength() != 0) {
420                type = i;
421                break;
422            }
423        }
424
425        //Log.v(TAG, "Found Light Type " + type);
426
427        LightBase sceneLight = null;
428        switch (type) {
429        case POINT_LIGHT:
430            sceneLight = new PointLight();
431            break;
432        case SPOT_LIGHT: // TODO: finish light types
433            break;
434        case DIR_LIGHT: // TODO: finish light types
435            break;
436        }
437
438        if (sceneLight == null) {
439            return;
440        }
441
442        Float3 color = getFloat3(light, "color");
443        sceneLight.setColor(color.x, color.y, color.z);
444        sceneLight.setName(name);
445        mScene.appendLight(sceneLight);
446        mLights.put(id, sceneLight);
447
448        //Log.v(TAG, "Light " + name + " color " + toString(color));
449    }
450
451    private void convertCamera(Element camera) {
452        String name = camera.getAttribute("name");
453        String id = camera.getAttribute("id");
454        float fov = 30.0f;
455        if (getString(camera, "yfov") != null) {
456            fov = getFloat(camera, "yfov");
457        } else if(getString(camera, "xfov") != null) {
458            float aspect = getFloat(camera, "aspect_ratio");
459            fov = getFloat(camera, "xfov") / aspect;
460        }
461
462        float near = getFloat(camera, "znear");
463        float far = getFloat(camera, "zfar");
464
465        Camera sceneCamera = new Camera();
466        sceneCamera.setFOV(fov);
467        sceneCamera.setNear(near);
468        sceneCamera.setFar(far);
469        sceneCamera.setName(name);
470        mScene.appendCamera(sceneCamera);
471        mCameras.put(id, sceneCamera);
472    }
473
474    private void convertImage(Element img) {
475        String name = img.getAttribute("name");
476        String id = img.getAttribute("id");
477        String file = getString(img, "init_from");
478
479        Texture2D tex = new Texture2D();
480        tex.setFileName(file);
481        tex.setFileDir(mRootDir);
482        mScene.appendTextures(tex);
483        mImages.put(id, tex);
484    }
485
486    private void getScene(Element scene) {
487        String name = scene.getAttribute("name");
488        String id = scene.getAttribute("id");
489
490        Node childNode = scene.getFirstChild();
491        while (childNode != null) {
492            if (childNode.getNodeType() == Node.ELEMENT_NODE) {
493                String indent = "";
494                getNode((Element)childNode, null, indent);
495            }
496            childNode = childNode.getNextSibling();
497        }
498    }
499
500    private String getString(Element elem, String name) {
501        String text = null;
502        NodeList nl = elem.getElementsByTagName(name);
503        if (nl != null && nl.getLength() != 0) {
504            text = ((Element)nl.item(0)).getFirstChild().getNodeValue();
505        }
506        return text;
507    }
508
509    private String getString(Element elem) {
510        String text = null;
511        text = elem.getFirstChild().getNodeValue();
512        return text;
513    }
514
515    private int getInt(Element elem, String name) {
516        return Integer.parseInt(getString(elem, name));
517    }
518
519    private float getFloat(Element elem, String name) {
520        return Float.parseFloat(getString(elem, name));
521    }
522
523    private float getFloat(Element elem) {
524        return Float.parseFloat(getString(elem));
525    }
526
527    private Float3 parseFloat3(String valueString) {
528        StringTokenizer st = new StringTokenizer(valueString);
529        float x = Float.parseFloat(st.nextToken());
530        float y = Float.parseFloat(st.nextToken());
531        float z = Float.parseFloat(st.nextToken());
532        return new Float3(x, y, z);
533    }
534
535    private Float4 parseFloat4(String valueString) {
536        StringTokenizer st = new StringTokenizer(valueString);
537        float x = Float.parseFloat(st.nextToken());
538        float y = Float.parseFloat(st.nextToken());
539        float z = Float.parseFloat(st.nextToken());
540        float w = Float.parseFloat(st.nextToken());
541        return new Float4(x, y, z, w);
542    }
543
544    private Float3 getFloat3(Element elem, String name) {
545        String valueString = getString(elem, name);
546        return parseFloat3(valueString);
547    }
548
549    private Float4 getFloat4(Element elem, String name) {
550        String valueString = getString(elem, name);
551        return parseFloat4(valueString);
552    }
553
554    private Float3 getFloat3(Element elem) {
555        String valueString = getString(elem);
556        return parseFloat3(valueString);
557    }
558
559    private Float4 getFloat4(Element elem) {
560        String valueString = getString(elem);
561        return parseFloat4(valueString);
562    }
563}
564