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 
17 package com.android.scenegraph;
18 import com.android.scenegraph.CompoundTransform.TranslateComponent;
19 import com.android.scenegraph.CompoundTransform.RotateComponent;
20 import com.android.scenegraph.CompoundTransform.ScaleComponent;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.util.ArrayList;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.StringTokenizer;
27 import java.util.HashMap;
28 
29 import javax.xml.parsers.DocumentBuilder;
30 import javax.xml.parsers.DocumentBuilderFactory;
31 import javax.xml.parsers.ParserConfigurationException;
32 
33 import org.w3c.dom.Document;
34 import org.w3c.dom.Element;
35 import org.w3c.dom.Node;
36 import org.w3c.dom.NodeList;
37 import org.xml.sax.SAXException;
38 
39 import android.renderscript.*;
40 import android.util.Log;
41 
42 public 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 
toString(Float3 v)56     String toString(Float3 v) {
57         String valueStr = v.x + " " + v.y + " " + v.z;
58         return valueStr;
59     }
60 
toString(Float4 v)61     String toString(Float4 v) {
62         String valueStr = v.x + " " + v.y + " " + v.z + " " + v.w;
63         return valueStr;
64     }
65 
ColladaParser()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 
init(InputStream is, String rootDir)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 
getScene()98     Scene getScene() {
99         return mScene;
100     }
101 
exportSceneData()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 
getRenderable(Element shape, Transform t)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 
updateLight(Element shape, Transform t)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 
updateCamera(Element shape, Transform t)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 
getNode(Element node, Transform parent, String indent)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
getTexture(String samplerName)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 
extractParams(Element fx, ArrayList<ShaderParam> params)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 
convertMaterials(Element mat)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 
convertGeometries(Element geo)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 
convertEffects(Element fx)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 
convertLight(Element light)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 
convertCamera(Element camera)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 
convertImage(Element img)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 
getScene(Element scene)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 
getString(Element elem, String name)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 
getString(Element elem)509     private String getString(Element elem) {
510         String text = null;
511         text = elem.getFirstChild().getNodeValue();
512         return text;
513     }
514 
getInt(Element elem, String name)515     private int getInt(Element elem, String name) {
516         return Integer.parseInt(getString(elem, name));
517     }
518 
getFloat(Element elem, String name)519     private float getFloat(Element elem, String name) {
520         return Float.parseFloat(getString(elem, name));
521     }
522 
getFloat(Element elem)523     private float getFloat(Element elem) {
524         return Float.parseFloat(getString(elem));
525     }
526 
parseFloat3(String valueString)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 
parseFloat4(String valueString)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 
getFloat3(Element elem, String name)544     private Float3 getFloat3(Element elem, String name) {
545         String valueString = getString(elem, name);
546         return parseFloat3(valueString);
547     }
548 
getFloat4(Element elem, String name)549     private Float4 getFloat4(Element elem, String name) {
550         String valueString = getString(elem, name);
551         return parseFloat4(valueString);
552     }
553 
getFloat3(Element elem)554     private Float3 getFloat3(Element elem) {
555         String valueString = getString(elem);
556         return parseFloat3(valueString);
557     }
558 
getFloat4(Element elem)559     private Float4 getFloat4(Element elem) {
560         String valueString = getString(elem);
561         return parseFloat4(valueString);
562     }
563 }
564