package rajawali.parser.fbx; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; import java.util.Stack; import rajawali.BaseObject3D; import rajawali.Camera; import rajawali.lights.ALight; import rajawali.lights.DirectionalLight; import rajawali.materials.AMaterial; import rajawali.materials.DiffuseMaterial; import rajawali.materials.PhongMaterial; import rajawali.materials.SimpleMaterial; import rajawali.math.Number3D; import rajawali.math.Vector2D; import rajawali.parser.AMeshParser; import rajawali.parser.fbx.FBXValues.Connections.Connect; import rajawali.parser.fbx.FBXValues.FBXColor4; import rajawali.parser.fbx.FBXValues.FBXFloatBuffer; import rajawali.parser.fbx.FBXValues.FBXIntBuffer; import rajawali.parser.fbx.FBXValues.FBXMatrix; import rajawali.parser.fbx.FBXValues.Objects.Material; import rajawali.parser.fbx.FBXValues.Objects.Model; import rajawali.parser.fbx.FBXValues.Objects.Texture; import rajawali.parser.fbx.FBXValues.Version5.FogOptions; import rajawali.renderer.RajawaliRenderer; import rajawali.util.RajLog; import android.graphics.Bitmap; import android.graphics.BitmapFactory; public class FBXParser extends AMeshParser { private static final char COMMENT = ';'; private static final String OBJECT_TYPE = "ObjectType:"; private static final String MODEL = "Model:"; private static final String PROPERTIES = "Properties"; private static final String LAYER_ELEMENT = "LayerElement"; private static final String LAYER = "Layer:"; private static final String PROPERTY = "Property:"; private static final String TYPE_VECTOR3D = "Vector3D"; private static final String TYPE_VECTOR = "Vector"; private static final String TYPE_COLOR = "Color"; private static final String TYPE_COLOR_RGB = "ColorRGB"; private static final String TYPE_LCL_TRANSLATION = "LclTranslation"; private static final String TYPE_LCL_ROTATION = "LclRotation"; private static final String TYPE_LCL_SCALING = "LclScaling"; private static final String MATERIAL = "Material:"; private static final String POSE = "Pose:"; private static final String POSE_NODE = "PoseNode:"; private static final String CONNECT = "Connect:"; private static final String TEXTURE = "Texture:"; private static final String FBX_U = "FBX"; private static final String FBX_L = FBX_U.toLowerCase(); private static final String REGEX_CLEAN = "\\s|\\t|\\n"; private static final String REGEX_NO_SPACE_NO_QUOTE = "\\\"|\\s"; private static final String REGEX_NO_QUOTE = "\\\""; private static final String REGEX_NO_FUNNY_CHARS = "\\W"; private static final String REPLACE_EMPTY = ""; private FBXValues mFbx; private Stack<Object> mObjStack; private RajawaliRenderer mRenderer; public FBXParser(RajawaliRenderer renderer, String fileOnSDCard) { super(renderer, fileOnSDCard); mRenderer = renderer; mObjStack = new Stack<Object>(); mFbx = new FBXValues(); mObjStack.add(mFbx); } public FBXParser(RajawaliRenderer renderer, int resourceId) { super(renderer.getContext().getResources(), renderer.getTextureManager(), resourceId); mRenderer = renderer; mObjStack = new Stack<Object>(); mFbx = new FBXValues(); mObjStack.add(mFbx); } @Override public FBXParser parse() throws ParsingException { super.parse(); BufferedReader buffer = null; if(mFile == null) { InputStream fileIn = mResources.openRawResource(mResourceId); buffer = new BufferedReader(new InputStreamReader(fileIn)); } else { try { buffer = new BufferedReader(new FileReader(mFile)); } catch (FileNotFoundException e) { RajLog.e("["+getClass().getCanonicalName()+"] Could not find file."); throw new ParsingException(e); } } String line; try { while((line = buffer.readLine()) != null) { String repl = line.replaceAll(REGEX_CLEAN, REPLACE_EMPTY); if(repl.length() == 0 || repl.charAt(0) == COMMENT) continue; readLine(buffer, line); } buffer.close(); } catch(Exception e) { throw new ParsingException(e); } // -- get lights Stack<Model> lights = mFbx.objects.getModelsByType(FBXValues.MODELTYPE_LIGHT); int numLights = lights.size(); RajawaliRenderer.setMaxLights(numLights == 0 ? 1 : numLights); Stack<ALight> sceneLights = new Stack<ALight>(); for(int i=0; i<numLights; ++i) { Model l = lights.get(i); // -- really need to add more light types ALight light = new DirectionalLight(); light.setPosition(l.properties.lclTranslation); light.setX(light.getX() * -1); light.setRotation(l.properties.lclRotation); light.setPower(l.properties.intensity / 100f); light.setColor(l.properties.color); sceneLights.add(light); } if(numLights == 0) { ALight light = new DirectionalLight(); light.setPosition(2, 0, -5); light.setPower(1); sceneLights.add(light); } // -- check fog if(mFbx.version5.fogOptions.fogEnable != null && mFbx.version5.fogOptions.fogEnable == 1) { FogOptions fogOptions = mFbx.version5.fogOptions; mRenderer.setFogEnabled(true); Camera cam = mRenderer.getCamera(); cam.setFogEnabled(true); cam.setFogNear(fogOptions.fogStart); cam.setFogColor(fogOptions.fogColor.color); mRenderer.setBackgroundColor(fogOptions.fogColor.color); } // -- get meshes Stack<Model> models = mFbx.objects.getModelsByType(FBXValues.MODELTYPE_MESH); for(int i=0; i<models.size(); ++i) { buildMesh(models.get(i), sceneLights); } // -- get cameras Stack<Model> cameras = mFbx.objects.getModelsByType(FBXValues.MODELTYPE_CAMERA); Model camera = null; for(int i=0; i<cameras.size(); ++i) { if(cameras.get(i).hidden == null || !cameras.get(i).hidden.equals("True")) { camera = cameras.get(i); break; } } if(camera != null) { Camera cam = mRenderer.getCamera(); cam.setPosition(camera.position); cam.setX(mRenderer.getCamera().getX() * -1); Number3D lookAt = camera.lookAt; lookAt.x = -lookAt.x; cam.setLookAt(lookAt); cam.setNearPlane(camera.properties.nearPlane); cam.setFarPlane(camera.properties.farPlane); cam.setFieldOfView(camera.properties.fieldOfView); } return this; } private void buildMesh(Model model, Stack<ALight> lights) { BaseObject3D o = new BaseObject3D(model.name); boolean hasUVs = model.layerElementUV.uVIndex != null; int[] vidx = model.polygonVertexIndex.data; int[] uvidx = null; float[] modelVerts = model.vertices.data; float[] modelNorm = model.layerElementNormal.normals.data; float[] modelUv = null; ArrayList<Integer> indices = new ArrayList<Integer>(); ArrayList<Float> vertices = new ArrayList<Float>(); ArrayList<Float> normals = new ArrayList<Float>(); ArrayList<Float> uvs = null; if(hasUVs) { uvs = new ArrayList<Float>(); uvidx = model.layerElementUV.uVIndex.data; modelUv = model.layerElementUV.uV.data; } int count = 0; int indexCount = 0; int[] triIds = new int[3]; int[] quadIds = new int[6]; int i = 0, j = 0, k = 0; int vidxLen = vidx.length; for(i=0; i<vidxLen; ++i) { count++; if(vidx[i] < 0) { if(count==3) { int index1 = vidx[i-2], index2 = vidx[i-1], index3 = (vidx[i] * -1) - 1; indices.add(indexCount++); indices.add(indexCount++); indices.add(indexCount++); triIds[0] = index1 * 3; triIds[1] = index2 * 3; triIds[2] = index3 * 3; for(j=0; j<3; ++j) { int cid = triIds[j]; for(k=0; k<3; ++k) { vertices.add(modelVerts[cid+k]); int dir = i==0 ? -1 : 1; normals.add(modelNorm[cid+k] * dir); } } if(hasUVs) { int uvIndex3 = uvidx[i] * 2; int uvIndex2 = uvidx[i-1] * 2; int uvIndex1 = uvidx[i-2] * 2; uvs.add(modelUv[uvIndex1+0]); uvs.add(1f-modelUv[uvIndex1+1]); uvs.add(modelUv[uvIndex2+0]); uvs.add(1f-modelUv[uvIndex2+1]); uvs.add(modelUv[uvIndex3+0]); uvs.add(1f-modelUv[uvIndex3+1]); } } else { int index1 = vidx[i-3]; int index2 = vidx[i-2]; int index3 = vidx[i-1]; int index4 = (vidx[i] * -1)-1; indices.add(indexCount++); indices.add(indexCount++); indices.add(indexCount++); indices.add(indexCount++); indices.add(indexCount++); indices.add(indexCount++); quadIds[0] = index1 * 3; quadIds[1] = index2 * 3; quadIds[2] = index3 * 3; quadIds[3] = index4 * 3; quadIds[4] = index1 * 3; quadIds[5] = index3 * 3; for(j=0; j<6; ++j) { int cid = quadIds[j]; for(k=0; k<3; ++k) { vertices.add(modelVerts[cid+k]); normals.add(modelNorm[cid+k]); } } if(hasUVs) { int uvIndex1 = uvidx[i-3] * 2; int uvIndex2 = uvidx[i-2] * 2; int uvIndex3 = uvidx[i-1] * 2; int uvIndex4 = uvidx[i] * 2; quadIds[0] = uvIndex1; quadIds[1] = uvIndex2; quadIds[2] = uvIndex3; quadIds[3] = uvIndex4; quadIds[4] = uvIndex1; quadIds[5] = uvIndex3; for(j=0; j<6; ++j) { int cid = quadIds[j]; for(k=0; k<2; ++k) { if(k==0) uvs.add(modelUv[cid + k]); else uvs.add(1f-modelUv[cid + k]); } } } } count = 0; } } o.setData(convertFloats(vertices), convertFloats(normals), hasUVs ? convertFloats(uvs) : null, null, convertIntegers(indices)); vertices.clear(); vertices = null; normals.clear(); normals = null; if(hasUVs) { uvs.clear(); uvs = null; } indices.clear(); indices = null; o.setMaterial(getMaterialForMesh(o, model.name)); o.getMaterial().setUseColor(true); o.setLights(lights); setMeshTextures(o, model.name); o.setPosition(model.properties.lclTranslation); o.setX(o.getX() * -1); o.setScale(model.properties.lclScaling); o.setRotation(model.properties.lclRotation); o.setRotZ(-o.getRotZ()); mRootObject.addChild(o); } public static int[] convertIntegers(List<Integer> integers) { int[] ret = new int[integers.size()]; int len = ret.length; for (int i=0; i < len; ++i) { ret[i] = integers.get(i).intValue(); } return ret; } public static float[] convertFloats(List<Float> floats) { float[] ret = new float[floats.size()]; int len = ret.length; for (int i=0; i < len; ++i) { ret[i] = floats.get(i).floatValue(); } return ret; } private void setMeshTextures(BaseObject3D o, String name) { Stack<Texture> textures = mFbx.objects.textures; Stack<Connect> connections = mFbx.connections.connections; int numTex = textures.size(); int numCon = connections.size(); for(int i=0; i<numTex; ++i) { Texture tex = textures.get(i); for(int j=0; j<numCon; ++j) { Connect conn = connections.get(j); if(conn.object2.equals(name) && conn.object1.equals(tex.textureName)) { // -- one texture for now String textureName = tex.fileName; try { Bitmap texture = null; if(mFile == null) { int identifier = mResources.getIdentifier(getFileNameWithoutExtension(textureName).toLowerCase(), "drawable", mResources.getResourcePackageName(mResourceId)); texture = BitmapFactory.decodeResource(mResources, identifier); } else { try { String filePath = mFile.getParent() + File.separatorChar + getOnlyFileName(textureName); texture = BitmapFactory.decodeFile(filePath); } catch (Exception e) { RajLog.e("["+getClass().getCanonicalName()+"] Could not find file " + getOnlyFileName(textureName)); e.printStackTrace(); return; } } o.addTexture(mTextureManager.addTexture(texture)); } catch(Exception e) { RajLog.e("Could not load texture [" + textureName + "]: " + e.getMessage() ); } return; } } } } private AMaterial getMaterialForMesh(BaseObject3D o, String name) { AMaterial mat = new SimpleMaterial(); Material material = null; Stack<Connect> conns = mFbx.connections.connections; int num = conns.size(); String materialName = null; for(int i=0; i<num; ++i) { if(conns.get(i).object2.equals(name)) { materialName = conns.get(i).object1; break; } } if(materialName != null) { Stack<Material> materials = mFbx.objects.materials; num = materials.size(); for(int i=0; i<num; ++i) { if(materials.get(i).name.equals(materialName)) { material = materials.get(i); break; } } } if(material != null) { if(material.shadingModel.equals("lambert") || material.shadingModel.equals("phong")) { PhongMaterial phong = new PhongMaterial(); o.setColor(material.properties.diffuseColor); phong.setAmbientColor(material.properties.ambientColor); phong.setAmbientIntensity(material.properties.ambientFactor); if(material.properties.specularColor != null) phong.setSpecularColor(material.properties.specularColor); if(material.properties.shininess != null) phong.setShininess(material.properties.shininess); mat = phong; } else { DiffuseMaterial diffuse = new DiffuseMaterial(); o.setColor(material.properties.diffuseColor); diffuse.setAmbientColor(material.properties.ambientColor); diffuse.setAmbientIntensity(material.properties.ambientFactor); mat = diffuse; } } return mat; } private void readLine(BufferedReader buffer, String line) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, IOException { if(line.replaceAll(REGEX_CLEAN, REPLACE_EMPTY).length() == 0) return; if(line.contains("{")) { // -- found new object Object last = mObjStack.peek(); if(line.contains(":")) { if(line.contains(OBJECT_TYPE)) { String val = line.split(":")[1].replaceAll(REGEX_NO_FUNNY_CHARS, REPLACE_EMPTY); Object ot = last.getClass().getDeclaredMethod("addObjectType", String.class).invoke(last, val); mObjStack.push(ot); return; } else if(line.contains(MODEL)) { String[] vals = line.split(","); if(vals.length < 2) { // TODO add model object for Take mObjStack.push(new Object()); return; } vals[0] = vals[0].split(": ")[1].replaceAll(REGEX_NO_QUOTE, REPLACE_EMPTY); vals[1] = vals[1].replaceAll(REGEX_NO_FUNNY_CHARS, REPLACE_EMPTY); Object mo = last.getClass().getDeclaredMethod("addModel", String.class, String.class).invoke(last, vals[0], vals[1]); mObjStack.push(mo); return; } else if(line.contains(MATERIAL) && !line.contains(LAYER_ELEMENT)) { String[] vals = line.split(": ")[1].split(","); vals[0] = vals[0].replaceAll(REGEX_NO_SPACE_NO_QUOTE, REPLACE_EMPTY); Object ma = last.getClass().getDeclaredMethod("addMaterial", String.class).invoke(last, vals[0]); mObjStack.push(ma); return; } else if(line.contains(POSE_NODE)) { Object pn = last.getClass().getDeclaredMethod("addPoseNode").invoke(last); mObjStack.push(pn); return; } else if(line.contains(PROPERTIES)) { line = "Properties"; } else if(line.contains(LAYER_ELEMENT)) { line = line.replaceAll("\\W|\\d", REPLACE_EMPTY); } else if(line.contains(LAYER)) { line = LAYER; } else if(line.contains(POSE)) { String val = line.split(":")[1]; String[] vals = val.split(","); last.getClass().getDeclaredMethod("setPoseName", String.class).invoke(last, vals[0].replaceAll(REGEX_NO_FUNNY_CHARS, REPLACE_EMPTY)); line = POSE; } else if(line.contains(TEXTURE)) { String val = line.split(": ")[1]; String[] vals = val.split(","); Object te = last.getClass().getDeclaredMethod("addTexture", String.class, String.class).invoke(last, vals[0].replaceAll(REGEX_NO_QUOTE, REPLACE_EMPTY), vals[1].replace(REGEX_NO_QUOTE, REPLACE_EMPTY)); mObjStack.push(te); return; } } line = line.replaceAll(REGEX_NO_FUNNY_CHARS, REPLACE_EMPTY); line = line.replaceAll(FBX_U, FBX_L); line = line.substring(0,1).toLowerCase() + line.substring(1); try { Field field = last.getClass().getField(line); mObjStack.push(field.get(last)); } catch(NoSuchFieldException e) { // -- create a generic object mObjStack.push(new Object()); return; } } else if(line.contains("}")) { // -- end of object mObjStack.pop(); } else { // -- found property Object last = mObjStack.peek(); String[] spl = line.split(": "); if(spl.length == 0) return; String prop = spl[0].replaceAll(REGEX_NO_FUNNY_CHARS, REPLACE_EMPTY); prop = prop.replaceAll(FBX_U, FBX_L); prop = prop.substring(0,1).toLowerCase() + prop.substring(1); boolean processNextLine = false; Object obj = mObjStack.peek(); try { if(spl.length < 2) return; String val = spl[1]; if(line.contains(PROPERTY)) { String[] vals = val.split(","); prop = vals[0].replaceAll(REGEX_NO_FUNNY_CHARS, REPLACE_EMPTY); prop = prop.substring(0,1).toLowerCase() + prop.substring(1); String type = vals[1].replaceAll(REGEX_NO_FUNNY_CHARS, REPLACE_EMPTY); if(type.equals(TYPE_VECTOR3D) || type.equals(TYPE_COLOR) || type.equals(TYPE_COLOR_RGB) || type.equals(TYPE_LCL_ROTATION) || type.equals(TYPE_LCL_SCALING) || type.equals(TYPE_LCL_TRANSLATION) || type.equals(TYPE_VECTOR)) { val = vals[3] +","+vals[4]+","+vals[5]; } else { if(vals.length < 4) return; val = vals[3].replaceAll(REGEX_NO_QUOTE, REPLACE_EMPTY); } } else if(line.contains(CONNECT)) { String[] vals = line.substring(line.indexOf(':')).split(","); last.getClass().getDeclaredMethod("addConnection", String.class, String.class, String.class) .invoke(last, vals[0].replaceAll(REGEX_NO_SPACE_NO_QUOTE, REPLACE_EMPTY), vals[1].replaceAll(REGEX_NO_SPACE_NO_QUOTE, REPLACE_EMPTY), vals[2].replaceAll(REGEX_NO_SPACE_NO_QUOTE, REPLACE_EMPTY)); return; } Field field = obj.getClass().getField(prop); Class<?> clazz = field.getType(); if(clazz.equals(Integer.class)) { // TODO investigate why there are multiple values in TextureId sometimes if(val.split(",").length > 0) val = val.split(",")[0]; field.set(obj, Integer.valueOf(val.replaceAll(REGEX_NO_SPACE_NO_QUOTE, REPLACE_EMPTY))); } else if(clazz.equals(String.class)) field.set(obj, val.replaceAll(REGEX_NO_QUOTE, REPLACE_EMPTY)); else if(clazz.equals(Long.class)) field.set(obj, Long.valueOf(val.replaceAll(REGEX_NO_SPACE_NO_QUOTE, REPLACE_EMPTY))); else if(clazz.equals(Float.class)) field.set(obj, Float.valueOf(val.replaceAll(REGEX_NO_SPACE_NO_QUOTE, REPLACE_EMPTY))); else if(clazz.equals(Number3D.class)) { field.set(obj, new Number3D(val.split(","))); } else if(clazz.equals(FBXFloatBuffer.class)) { StringBuffer sb = new StringBuffer(val); String noSpace; while((line = buffer.readLine()) != null) { noSpace = line.replaceAll("\\s", REPLACE_EMPTY); if(noSpace.length() > 0 && noSpace.charAt(0) == ',') sb.append(noSpace); else { processNextLine = true; break; } } field.set(obj, new FBXFloatBuffer(sb.toString())); } else if(clazz.equals(FBXIntBuffer.class)) { StringBuffer sb = new StringBuffer(val); String noSpace; while((line = buffer.readLine()) != null) { noSpace = line.replaceAll("\\s", REPLACE_EMPTY); if(noSpace.length() > 0 && noSpace.charAt(0) == ',') sb.append(noSpace); else { processNextLine = true; break; } } field.set(obj, new FBXIntBuffer(sb.toString())); } else if(clazz.equals(FBXMatrix.class)) { StringBuffer sb = new StringBuffer(val); String noSpace; while((line = buffer.readLine()) != null) { noSpace = line.replaceAll(REGEX_CLEAN, REPLACE_EMPTY); if(noSpace.length() > 0 && noSpace.charAt(0) == ',') sb.append(noSpace); else { processNextLine = true; break; } } field.set(obj, new FBXMatrix(sb.toString())); } else if(clazz.equals(FBXColor4.class)) { field.set(obj, new FBXColor4(val)); } else if(clazz.equals(Vector2D.class)) { field.set(obj, new Vector2D(val.replaceAll("\\s", REPLACE_EMPTY).split(","))); } if(processNextLine && line.replaceAll(REGEX_CLEAN, REPLACE_EMPTY).length() > 0) readLine(buffer, line); } catch(NoSuchFieldException e) { return; } } } }