aboutsummaryrefslogtreecommitdiff
path: root/engine/src/core-plugins/com/jme3/scene/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src/core-plugins/com/jme3/scene/plugins')
-rw-r--r--engine/src/core-plugins/com/jme3/scene/plugins/MTLLoader.java325
-rw-r--r--engine/src/core-plugins/com/jme3/scene/plugins/OBJLoader.java593
2 files changed, 918 insertions, 0 deletions
diff --git a/engine/src/core-plugins/com/jme3/scene/plugins/MTLLoader.java b/engine/src/core-plugins/com/jme3/scene/plugins/MTLLoader.java
new file mode 100644
index 0000000..1f701cd
--- /dev/null
+++ b/engine/src/core-plugins/com/jme3/scene/plugins/MTLLoader.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.plugins;
+
+import com.jme3.asset.*;
+import com.jme3.material.Material;
+import com.jme3.material.MaterialList;
+import com.jme3.material.RenderState.BlendMode;
+import com.jme3.math.ColorRGBA;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+import com.jme3.texture.Texture2D;
+import com.jme3.util.PlaceholderAssets;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Locale;
+import java.util.NoSuchElementException;
+import java.util.Scanner;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class MTLLoader implements AssetLoader {
+
+ private static final Logger logger = Logger.getLogger(MTLLoader.class.getName());
+
+ protected Scanner scan;
+ protected MaterialList matList;
+ //protected Material material;
+ protected AssetManager assetManager;
+ protected String folderName;
+ protected AssetKey key;
+
+ protected Texture diffuseMap, normalMap, specularMap, alphaMap;
+ protected ColorRGBA ambient = new ColorRGBA();
+ protected ColorRGBA diffuse = new ColorRGBA();
+ protected ColorRGBA specular = new ColorRGBA();
+ protected float shininess = 16;
+ protected boolean shadeless;
+ protected String matName;
+ protected float alpha = 1;
+ protected boolean transparent = false;
+ protected boolean disallowTransparency = false;
+ protected boolean disallowAmbient = false;
+ protected boolean disallowSpecular = false;
+
+ public void reset(){
+ scan = null;
+ matList = null;
+// material = null;
+
+ resetMaterial();
+ }
+
+ protected ColorRGBA readColor(){
+ ColorRGBA v = new ColorRGBA();
+ v.set(scan.nextFloat(), scan.nextFloat(), scan.nextFloat(), 1.0f);
+ return v;
+ }
+
+ protected String nextStatement(){
+ scan.useDelimiter("\n");
+ String result = scan.next();
+ scan.useDelimiter("\\p{javaWhitespace}+");
+ return result;
+ }
+
+ protected boolean skipLine(){
+ try {
+ scan.skip(".*\r{0,1}\n");
+ return true;
+ } catch (NoSuchElementException ex){
+ // EOF
+ return false;
+ }
+ }
+
+ protected void resetMaterial(){
+ ambient.set(ColorRGBA.DarkGray);
+ diffuse.set(ColorRGBA.LightGray);
+ specular.set(ColorRGBA.Black);
+ shininess = 16;
+ disallowTransparency = false;
+ disallowAmbient = false;
+ disallowSpecular = false;
+ shadeless = false;
+ transparent = false;
+ matName = null;
+ diffuseMap = null;
+ specularMap = null;
+ normalMap = null;
+ alphaMap = null;
+ alpha = 1;
+ }
+
+ protected void createMaterial(){
+ Material material;
+
+ if (alpha < 1f && transparent && !disallowTransparency){
+ diffuse.a = alpha;
+ }
+
+ if (shadeless){
+ material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+ material.setColor("Color", diffuse.clone());
+ material.setTexture("ColorMap", diffuseMap);
+ // TODO: Add handling for alpha map?
+ }else{
+ material = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+ material.setBoolean("UseMaterialColors", true);
+ material.setColor("Ambient", ambient.clone());
+ material.setColor("Diffuse", diffuse.clone());
+ material.setColor("Specular", specular.clone());
+ material.setFloat("Shininess", shininess); // prevents "premature culling" bug
+
+ if (diffuseMap != null) material.setTexture("DiffuseMap", diffuseMap);
+ if (specularMap != null) material.setTexture("SpecularMap", specularMap);
+ if (normalMap != null) material.setTexture("NormalMap", normalMap);
+ if (alphaMap != null) material.setTexture("AlphaMap", alphaMap);
+ }
+
+ if (transparent && !disallowTransparency){
+ material.setTransparent(true);
+ material.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
+ material.getAdditionalRenderState().setAlphaTest(true);
+ material.getAdditionalRenderState().setAlphaFallOff(0.01f);
+ }
+
+ matList.put(matName, material);
+ }
+
+ protected void startMaterial(String name){
+ if (matName != null){
+ // material is already in cache, generate it
+ createMaterial();
+ }
+
+ // now, reset the params and set the name to start a new material
+ resetMaterial();
+ matName = name;
+ }
+
+ protected Texture loadTexture(String path){
+ String[] split = path.trim().split("\\p{javaWhitespace}+");
+
+ // will crash if path is an empty string
+ path = split[split.length-1];
+
+ String name = new File(path).getName();
+ TextureKey texKey = new TextureKey(folderName + name);
+ texKey.setGenerateMips(true);
+ Texture texture;
+ try {
+ texture = assetManager.loadTexture(texKey);
+ texture.setWrap(WrapMode.Repeat);
+ } catch (AssetNotFoundException ex){
+ logger.log(Level.WARNING, "Cannot locate {0} for material {1}", new Object[]{texKey, key});
+ texture = new Texture2D(PlaceholderAssets.getPlaceholderImage());
+ }
+ return texture;
+ }
+
+ protected boolean readLine(){
+ if (!scan.hasNext()){
+ return false;
+ }
+
+ String cmd = scan.next().toLowerCase();
+ if (cmd.startsWith("#")){
+ // skip entire comment until next line
+ return skipLine();
+ }else if (cmd.equals("newmtl")){
+ String name = scan.next();
+ startMaterial(name);
+ }else if (cmd.equals("ka")){
+ ambient.set(readColor());
+ }else if (cmd.equals("kd")){
+ diffuse.set(readColor());
+ }else if (cmd.equals("ks")){
+ specular.set(readColor());
+ }else if (cmd.equals("ns")){
+ float shiny = scan.nextFloat();
+ if (shiny >= 1){
+ shininess = shiny; /* (128f / 1000f)*/
+ if (specular.equals(ColorRGBA.Black)){
+ specular.set(ColorRGBA.White);
+ }
+ }else{
+ // For some reason blender likes to export Ns 0 statements
+ // Ignore Ns 0 instead of setting it
+ }
+
+ }else if (cmd.equals("d") || cmd.equals("tr")){
+ alpha = scan.nextFloat();
+ transparent = true;
+ }else if (cmd.equals("map_ka")){
+ // ignore it for now
+ return skipLine();
+ }else if (cmd.equals("map_kd")){
+ String path = nextStatement();
+ diffuseMap = loadTexture(path);
+ }else if (cmd.equals("map_bump") || cmd.equals("bump")){
+ if (normalMap == null){
+ String path = nextStatement();
+ normalMap = loadTexture(path);
+ }
+ }else if (cmd.equals("map_ks")){
+ String path = nextStatement();
+ specularMap = loadTexture(path);
+ if (specularMap != null){
+ // NOTE: since specular color is modulated with specmap
+ // make sure we have it set
+ if (specular.equals(ColorRGBA.Black)){
+ specular.set(ColorRGBA.White);
+ }
+ }
+ }else if (cmd.equals("map_d")){
+ String path = scan.next();
+ alphaMap = loadTexture(path);
+ transparent = true;
+ }else if (cmd.equals("illum")){
+ int mode = scan.nextInt();
+
+ switch (mode){
+ case 0:
+ // no lighting
+ shadeless = true;
+ disallowTransparency = true;
+ break;
+ case 1:
+ disallowSpecular = true;
+ disallowTransparency = true;
+ break;
+ case 2:
+ case 3:
+ case 5:
+ case 8:
+ disallowTransparency = true;
+ break;
+ case 4:
+ case 6:
+ case 7:
+ case 9:
+ // Enable transparency
+ // Works best if diffuse map has an alpha channel
+ transparent = true;
+ break;
+ }
+ }else if (cmd.equals("ke") || cmd.equals("ni")){
+ // Ni: index of refraction - unsupported in jME
+ // Ke: emission color
+ return skipLine();
+ }else{
+ logger.log(Level.WARNING, "Unknown statement in MTL! {0}", cmd);
+ return skipLine();
+ }
+
+ return true;
+ }
+
+ @SuppressWarnings("empty-statement")
+ public Object load(AssetInfo info) throws IOException{
+ reset();
+
+ this.key = info.getKey();
+ this.assetManager = info.getManager();
+ folderName = info.getKey().getFolder();
+ matList = new MaterialList();
+
+ InputStream in = null;
+ try {
+ in = info.openStream();
+ scan = new Scanner(in);
+ scan.useLocale(Locale.US);
+
+ while (readLine());
+ } finally {
+ if (in != null){
+ in.close();
+ }
+ }
+
+ if (matName != null){
+ // still have a material in the vars
+ createMaterial();
+ resetMaterial();
+ }
+
+ MaterialList list = matList;
+
+
+
+ return list;
+ }
+}
diff --git a/engine/src/core-plugins/com/jme3/scene/plugins/OBJLoader.java b/engine/src/core-plugins/com/jme3/scene/plugins/OBJLoader.java
new file mode 100644
index 0000000..3ce7f52
--- /dev/null
+++ b/engine/src/core-plugins/com/jme3/scene/plugins/OBJLoader.java
@@ -0,0 +1,593 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.plugins;
+
+import com.jme3.asset.*;
+import com.jme3.material.Material;
+import com.jme3.material.MaterialList;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.scene.Mesh.Mode;
+import com.jme3.scene.*;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.mesh.IndexBuffer;
+import com.jme3.scene.mesh.IndexIntBuffer;
+import com.jme3.scene.mesh.IndexShortBuffer;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.IntMap;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
+import java.util.Map.Entry;
+import java.util.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Reads OBJ format models.
+ */
+public final class OBJLoader implements AssetLoader {
+
+ private static final Logger logger = Logger.getLogger(OBJLoader.class.getName());
+
+ protected final ArrayList<Vector3f> verts = new ArrayList<Vector3f>();
+ protected final ArrayList<Vector2f> texCoords = new ArrayList<Vector2f>();
+ protected final ArrayList<Vector3f> norms = new ArrayList<Vector3f>();
+
+ protected final ArrayList<Face> faces = new ArrayList<Face>();
+ protected final HashMap<String, ArrayList<Face>> matFaces = new HashMap<String, ArrayList<Face>>();
+
+ protected String currentMatName;
+ protected String currentObjectName;
+
+ protected final HashMap<Vertex, Integer> vertIndexMap = new HashMap<Vertex, Integer>(100);
+ protected final IntMap<Vertex> indexVertMap = new IntMap<Vertex>(100);
+ protected int curIndex = 0;
+ protected int objectIndex = 0;
+ protected int geomIndex = 0;
+
+ protected Scanner scan;
+ protected ModelKey key;
+ protected AssetManager assetManager;
+ protected MaterialList matList;
+
+ protected String objName;
+ protected Node objNode;
+
+ protected static class Vertex {
+
+ Vector3f v;
+ Vector2f vt;
+ Vector3f vn;
+ int index;
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final Vertex other = (Vertex) obj;
+ if (this.v != other.v && (this.v == null || !this.v.equals(other.v))) {
+ return false;
+ }
+ if (this.vt != other.vt && (this.vt == null || !this.vt.equals(other.vt))) {
+ return false;
+ }
+ if (this.vn != other.vn && (this.vn == null || !this.vn.equals(other.vn))) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 5;
+ hash = 53 * hash + (this.v != null ? this.v.hashCode() : 0);
+ hash = 53 * hash + (this.vt != null ? this.vt.hashCode() : 0);
+ hash = 53 * hash + (this.vn != null ? this.vn.hashCode() : 0);
+ return hash;
+ }
+ }
+
+ protected static class Face {
+ Vertex[] verticies;
+ }
+
+ protected class ObjectGroup {
+
+ final String objectName;
+
+ public ObjectGroup(String objectName){
+ this.objectName = objectName;
+ }
+
+ public Spatial createGeometry(){
+ Node groupNode = new Node(objectName);
+
+// if (matFaces.size() > 0){
+// for (Entry<String, ArrayList<Face>> entry : matFaces.entrySet()){
+// ArrayList<Face> materialFaces = entry.getValue();
+// if (materialFaces.size() > 0){
+// Geometry geom = createGeometry(materialFaces, entry.getKey());
+// objNode.attachChild(geom);
+// }
+// }
+// }else if (faces.size() > 0){
+// // generate final geometry
+// Geometry geom = createGeometry(faces, null);
+// objNode.attachChild(geom);
+// }
+
+ return groupNode;
+ }
+ }
+
+ public void reset(){
+ verts.clear();
+ texCoords.clear();
+ norms.clear();
+ faces.clear();
+ matFaces.clear();
+
+ vertIndexMap.clear();
+ indexVertMap.clear();
+
+ currentMatName = null;
+ matList = null;
+ curIndex = 0;
+ geomIndex = 0;
+ scan = null;
+ }
+
+ protected void findVertexIndex(Vertex vert){
+ Integer index = vertIndexMap.get(vert);
+ if (index != null){
+ vert.index = index.intValue();
+ }else{
+ vert.index = curIndex++;
+ vertIndexMap.put(vert, vert.index);
+ indexVertMap.put(vert.index, vert);
+ }
+ }
+
+ protected Face[] quadToTriangle(Face f){
+ assert f.verticies.length == 4;
+
+ Face[] t = new Face[]{ new Face(), new Face() };
+ t[0].verticies = new Vertex[3];
+ t[1].verticies = new Vertex[3];
+
+ Vertex v0 = f.verticies[0];
+ Vertex v1 = f.verticies[1];
+ Vertex v2 = f.verticies[2];
+ Vertex v3 = f.verticies[3];
+
+ // find the pair of verticies that is closest to each over
+ // v0 and v2
+ // OR
+ // v1 and v3
+ float d1 = v0.v.distanceSquared(v2.v);
+ float d2 = v1.v.distanceSquared(v3.v);
+ if (d1 < d2){
+ // put an edge in v0, v2
+ t[0].verticies[0] = v0;
+ t[0].verticies[1] = v1;
+ t[0].verticies[2] = v3;
+
+ t[1].verticies[0] = v1;
+ t[1].verticies[1] = v2;
+ t[1].verticies[2] = v3;
+ }else{
+ // put an edge in v1, v3
+ t[0].verticies[0] = v0;
+ t[0].verticies[1] = v1;
+ t[0].verticies[2] = v2;
+
+ t[1].verticies[0] = v0;
+ t[1].verticies[1] = v2;
+ t[1].verticies[2] = v3;
+ }
+
+ return t;
+ }
+
+ private ArrayList<Vertex> vertList = new ArrayList<Vertex>();
+
+ protected void readFace(){
+ Face f = new Face();
+ vertList.clear();
+
+ String line = scan.nextLine().trim();
+ String[] verticies = line.split("\\s");
+ for (String vertex : verticies){
+ int v = 0;
+ int vt = 0;
+ int vn = 0;
+
+ String[] split = vertex.split("/");
+ if (split.length == 1){
+ v = Integer.parseInt(split[0].trim());
+ }else if (split.length == 2){
+ v = Integer.parseInt(split[0].trim());
+ vt = Integer.parseInt(split[1].trim());
+ }else if (split.length == 3 && !split[1].equals("")){
+ v = Integer.parseInt(split[0].trim());
+ vt = Integer.parseInt(split[1].trim());
+ vn = Integer.parseInt(split[2].trim());
+ }else if (split.length == 3){
+ v = Integer.parseInt(split[0].trim());
+ vn = Integer.parseInt(split[2].trim());
+ }
+
+ Vertex vx = new Vertex();
+ vx.v = verts.get(v - 1);
+
+ if (vt > 0)
+ vx.vt = texCoords.get(vt - 1);
+
+ if (vn > 0)
+ vx.vn = norms.get(vn - 1);
+
+ vertList.add(vx);
+ }
+
+ if (vertList.size() > 4 || vertList.size() <= 2)
+ logger.warning("Edge or polygon detected in OBJ. Ignored.");
+
+ f.verticies = new Vertex[vertList.size()];
+ for (int i = 0; i < vertList.size(); i++){
+ f.verticies[i] = vertList.get(i);
+ }
+
+ if (matList != null && matFaces.containsKey(currentMatName)){
+ matFaces.get(currentMatName).add(f);
+ }else{
+ faces.add(f); // faces that belong to the default material
+ }
+ }
+
+ protected Vector3f readVector3(){
+ Vector3f v = new Vector3f();
+
+ v.set(Float.parseFloat(scan.next()),
+ Float.parseFloat(scan.next()),
+ Float.parseFloat(scan.next()));
+
+ return v;
+ }
+
+ protected Vector2f readVector2(){
+ Vector2f v = new Vector2f();
+
+ String line = scan.nextLine().trim();
+ String[] split = line.split("\\s");
+ v.setX( Float.parseFloat(split[0].trim()) );
+ v.setY( Float.parseFloat(split[1].trim()) );
+
+// v.setX(scan.nextFloat());
+// if (scan.hasNextFloat()){
+// v.setY(scan.nextFloat());
+// if (scan.hasNextFloat()){
+// scan.nextFloat(); // ignore
+// }
+// }
+
+ return v;
+ }
+
+ protected void loadMtlLib(String name) throws IOException{
+ if (!name.toLowerCase().endsWith(".mtl"))
+ throw new IOException("Expected .mtl file! Got: " + name);
+
+ // NOTE: Cut off any relative/absolute paths
+ name = new File(name).getName();
+ AssetKey mtlKey = new AssetKey(key.getFolder() + name);
+ try {
+ matList = (MaterialList) assetManager.loadAsset(mtlKey);
+ } catch (AssetNotFoundException ex){
+ logger.log(Level.WARNING, "Cannot locate {0} for model {1}", new Object[]{name, key});
+ }
+
+ if (matList != null){
+ // create face lists for every material
+ for (String matName : matList.keySet()){
+ matFaces.put(matName, new ArrayList<Face>());
+ }
+ }
+ }
+
+ protected boolean nextStatement(){
+ try {
+ scan.skip(".*\r{0,1}\n");
+ return true;
+ } catch (NoSuchElementException ex){
+ // EOF
+ return false;
+ }
+ }
+
+ protected boolean readLine() throws IOException{
+ if (!scan.hasNext()){
+ return false;
+ }
+
+ String cmd = scan.next();
+ if (cmd.startsWith("#")){
+ // skip entire comment until next line
+ return nextStatement();
+ }else if (cmd.equals("v")){
+ // vertex position
+ verts.add(readVector3());
+ }else if (cmd.equals("vn")){
+ // vertex normal
+ norms.add(readVector3());
+ }else if (cmd.equals("vt")){
+ // texture coordinate
+ texCoords.add(readVector2());
+ }else if (cmd.equals("f")){
+ // face, can be triangle, quad, or polygon (unsupported)
+ readFace();
+ }else if (cmd.equals("usemtl")){
+ // use material from MTL lib for the following faces
+ currentMatName = scan.next();
+// if (!matList.containsKey(currentMatName))
+// throw new IOException("Cannot locate material " + currentMatName + " in MTL file!");
+
+ }else if (cmd.equals("mtllib")){
+ // specify MTL lib to use for this OBJ file
+ String mtllib = scan.nextLine().trim();
+ loadMtlLib(mtllib);
+ }else if (cmd.equals("s") || cmd.equals("g")){
+ return nextStatement();
+ }else{
+ // skip entire command until next line
+ logger.log(Level.WARNING, "Unknown statement in OBJ! {0}", cmd);
+ return nextStatement();
+ }
+
+ return true;
+ }
+
+ protected Geometry createGeometry(ArrayList<Face> faceList, String matName) throws IOException{
+ if (faceList.isEmpty())
+ throw new IOException("No geometry data to generate mesh");
+
+ // Create mesh from the faces
+ Mesh mesh = constructMesh(faceList);
+
+ Geometry geom = new Geometry(objName + "-geom-" + (geomIndex++), mesh);
+
+ Material material = null;
+ if (matName != null && matList != null){
+ // Get material from material list
+ material = matList.get(matName);
+ }
+ if (material == null){
+ // create default material
+ material = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+ material.setFloat("Shininess", 64);
+ }
+ geom.setMaterial(material);
+ if (material.isTransparent())
+ geom.setQueueBucket(Bucket.Transparent);
+ else
+ geom.setQueueBucket(Bucket.Opaque);
+
+ if (material.getMaterialDef().getName().contains("Lighting")
+ && mesh.getFloatBuffer(Type.Normal) == null){
+ logger.log(Level.WARNING, "OBJ mesh {0} doesn't contain normals! "
+ + "It might not display correctly", geom.getName());
+ }
+
+ return geom;
+ }
+
+ protected Mesh constructMesh(ArrayList<Face> faceList){
+ Mesh m = new Mesh();
+ m.setMode(Mode.Triangles);
+
+ boolean hasTexCoord = false;
+ boolean hasNormals = false;
+
+ ArrayList<Face> newFaces = new ArrayList<Face>(faceList.size());
+ for (int i = 0; i < faceList.size(); i++){
+ Face f = faceList.get(i);
+
+ for (Vertex v : f.verticies){
+ findVertexIndex(v);
+
+ if (!hasTexCoord && v.vt != null)
+ hasTexCoord = true;
+ if (!hasNormals && v.vn != null)
+ hasNormals = true;
+ }
+
+ if (f.verticies.length == 4){
+ Face[] t = quadToTriangle(f);
+ newFaces.add(t[0]);
+ newFaces.add(t[1]);
+ }else{
+ newFaces.add(f);
+ }
+ }
+
+ FloatBuffer posBuf = BufferUtils.createFloatBuffer(vertIndexMap.size() * 3);
+ FloatBuffer normBuf = null;
+ FloatBuffer tcBuf = null;
+
+ if (hasNormals){
+ normBuf = BufferUtils.createFloatBuffer(vertIndexMap.size() * 3);
+ }
+ if (hasTexCoord){
+ tcBuf = BufferUtils.createFloatBuffer(vertIndexMap.size() * 2);
+ }
+
+ IndexBuffer indexBuf = null;
+ if (vertIndexMap.size() >= 65536){
+ // too many verticies: use intbuffer instead of shortbuffer
+ IntBuffer ib = BufferUtils.createIntBuffer(newFaces.size() * 3);
+ m.setBuffer(VertexBuffer.Type.Index, 3, ib);
+ indexBuf = new IndexIntBuffer(ib);
+ }else{
+ ShortBuffer sb = BufferUtils.createShortBuffer(newFaces.size() * 3);
+ m.setBuffer(VertexBuffer.Type.Index, 3, sb);
+ indexBuf = new IndexShortBuffer(sb);
+ }
+
+ int numFaces = newFaces.size();
+ for (int i = 0; i < numFaces; i++){
+ Face f = newFaces.get(i);
+ if (f.verticies.length != 3)
+ continue;
+
+ Vertex v0 = f.verticies[0];
+ Vertex v1 = f.verticies[1];
+ Vertex v2 = f.verticies[2];
+
+ posBuf.position(v0.index * 3);
+ posBuf.put(v0.v.x).put(v0.v.y).put(v0.v.z);
+ posBuf.position(v1.index * 3);
+ posBuf.put(v1.v.x).put(v1.v.y).put(v1.v.z);
+ posBuf.position(v2.index * 3);
+ posBuf.put(v2.v.x).put(v2.v.y).put(v2.v.z);
+
+ if (normBuf != null){
+ if (v0.vn != null){
+ normBuf.position(v0.index * 3);
+ normBuf.put(v0.vn.x).put(v0.vn.y).put(v0.vn.z);
+ normBuf.position(v1.index * 3);
+ normBuf.put(v1.vn.x).put(v1.vn.y).put(v1.vn.z);
+ normBuf.position(v2.index * 3);
+ normBuf.put(v2.vn.x).put(v2.vn.y).put(v2.vn.z);
+ }
+ }
+
+ if (tcBuf != null){
+ if (v0.vt != null){
+ tcBuf.position(v0.index * 2);
+ tcBuf.put(v0.vt.x).put(v0.vt.y);
+ tcBuf.position(v1.index * 2);
+ tcBuf.put(v1.vt.x).put(v1.vt.y);
+ tcBuf.position(v2.index * 2);
+ tcBuf.put(v2.vt.x).put(v2.vt.y);
+ }
+ }
+
+ int index = i * 3; // current face * 3 = current index
+ indexBuf.put(index, v0.index);
+ indexBuf.put(index+1, v1.index);
+ indexBuf.put(index+2, v2.index);
+ }
+
+ m.setBuffer(VertexBuffer.Type.Position, 3, posBuf);
+ m.setBuffer(VertexBuffer.Type.Normal, 3, normBuf);
+ m.setBuffer(VertexBuffer.Type.TexCoord, 2, tcBuf);
+ // index buffer was set on creation
+
+ m.setStatic();
+ m.updateBound();
+ m.updateCounts();
+ //m.setInterleaved();
+
+ // clear data generated face statements
+ // to prepare for next mesh
+ vertIndexMap.clear();
+ indexVertMap.clear();
+ curIndex = 0;
+
+ return m;
+ }
+
+ @SuppressWarnings("empty-statement")
+ public Object load(AssetInfo info) throws IOException{
+ reset();
+
+ key = (ModelKey) info.getKey();
+ assetManager = info.getManager();
+ objName = key.getName();
+
+ String folderName = key.getFolder();
+ String ext = key.getExtension();
+ objName = objName.substring(0, objName.length() - ext.length() - 1);
+ if (folderName != null && folderName.length() > 0){
+ objName = objName.substring(folderName.length());
+ }
+
+ objNode = new Node(objName + "-objnode");
+
+ if (!(info.getKey() instanceof ModelKey))
+ throw new IllegalArgumentException("Model assets must be loaded using a ModelKey");
+
+ InputStream in = null;
+ try {
+ in = info.openStream();
+
+ scan = new Scanner(in);
+ scan.useLocale(Locale.US);
+
+ while (readLine());
+ } finally {
+ if (in != null){
+ in.close();
+ }
+ }
+
+ if (matFaces.size() > 0){
+ for (Entry<String, ArrayList<Face>> entry : matFaces.entrySet()){
+ ArrayList<Face> materialFaces = entry.getValue();
+ if (materialFaces.size() > 0){
+ Geometry geom = createGeometry(materialFaces, entry.getKey());
+ objNode.attachChild(geom);
+ }
+ }
+ }else if (faces.size() > 0){
+ // generate final geometry
+ Geometry geom = createGeometry(faces, null);
+ objNode.attachChild(geom);
+ }
+
+ if (objNode.getQuantity() == 1)
+ // only 1 geometry, so no need to send node
+ return objNode.getChild(0);
+ else
+ return objNode;
+ }
+
+}