/*
* $RCSfile: ObjectFile.java,v $
*
* Copyright (c) 2007 Sun Microsystems, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistribution of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistribution 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 Sun Microsystems, Inc. or the names of
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any
* kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
* WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
* EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
* NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
* USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
* ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
* CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
* REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
* INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed, licensed or
* intended for use in the design, construction, operation or
* maintenance of any nuclear facility.
*
* $Revision: 1.6 $
* $Date: 2009/12/21 13:55:05 $
* $State: Exp $
*/
package org.mt4j.util.modelImporter.fileObj;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StreamTokenizer;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import org.mt4j.MTApplication;
import org.mt4j.components.visibleComponents.GeometryInfo;
import org.mt4j.components.visibleComponents.shapes.mesh.MTTriangleMesh;
import org.mt4j.util.TriangleNormalGenerator;
import org.mt4j.util.math.Vector3D;
import org.mt4j.util.math.Vertex;
import org.mt4j.util.modelImporter.ModelImporterFactory;
import processing.core.PApplet;
import processing.opengl.PGraphicsOpenGL;
/**
* The ObjectFile class implements the Loader interface for the Wavefront
* .obj file format, a standard 3D object file format created for use with
* Wavefront's Advanced Visualizer (tm) and available for purchase from
* Viewpoint DataLabs, as well as other 3D model companies. Object Files
* are text based
* files supporting both polygonal and free-form geometry (curves
* and surfaces). The Java 3D .obj file loader supports a subset of the
* file format, but it is enough to load almost all commonly available
* Object Files. Free-form geometry is not supported.</p>
*
* The Object File tokens currently supported by this loader are:</p>
* <code>v <i>float</i> <i>float</i> <i>float</i></code></p>
* <dl><dd>A single vertex's geometric position in space. The first vertex
* listed in the file has index 1,
* and subsequent vertices are numbered sequentially.</dl></p>
* <code>vn <i>float</i> <i>float</i> <i>float</i></code></p>
* <dl><dd>A normal. The first normal in the file is index 1, and
* subsequent normals are numbered sequentially.</dl></p>
* <code>vt <i>float</i> <i>float</i></code></p>
* <dl><dd>A texture coordinate. The first texture coordinate in the file is
* index 1, and subsequent normals are numbered sequentially.</dl></p>
* <code>f <i>int</i> <i>int</i> <i>int</i> . . .</code></p>
* <dl><dd><i><b>or</b></i></dl></p>
* <code>f <i>int</i>/<i>int</i> <i>int</i>/<i>int</i> <i>int</i>/<i>int</i> . . .</code></p>
* <dl><dd><i><b>or</i></b></dl></p>
* <code>f <i>int</i>/<i>int</i>/<i>int</i> <i>int</i>/<i>int</i>/<i>int</i> <i>int</i>/<i>int</i>/<i>int</i> . . .</code></p>
* <dl><dd>A polygonal face. The numbers are indexes into the arrays of
* vertex positions, texture coordinates, and normals respectively.
* There is no maximum number of vertices that a single polygon may
* contain. The .obj file specification says that each face must
* be flat and convex, but if the TRIANGULATE flag is sent to the
* ObjectFile constructor, each face will be triangulated by the
* Java 3D Triangulator, and therefore may be concave.
* A number may be omitted if, for example, texture coordinates are
* not being defined in the model. Numbers are normally positive
* indexes, but may also be negative. An index of -1 means the last
* member added to the respective array, -2 is the one before that,
* and so on.</dl></p>
* <code>g <i>name</i></code></p>
* <dl><dd>Faces defined after this token will be added to the named group.
* These geometry groups are returned as separated Shape3D objects
* attached to the parent SceneGroup. Each named Shape3D will also
* be in the Hashtable returned by Scene.getNamedObjects(). It is
* legal to add faces to a group, switch to another group, and then
* add more faces to the original group by reissuing the same name
* with the g token. If faces are added to the model before the g
* token is seen, the faces are put into the default group called
* "default."</dl></p>
* <code>s <i>int</i></code></p>
* <dl><dd><i><b>or</i></b></dl></p>
* <code>s off</code></p>
* <dl><dd>If the vn token is not used in the file to specify vertex normals
* for the model, this token may be used to put faces into groups
* for normal calculation ("smoothing groups") in the same manner as
* the 'g' token
* is used to group faces geometrically. Faces in the same smoothing
* group will have their normals calculated as if they are part of
* the same smooth surface. To do this, we use the Java 3D NormalGenerator
* utility with the creaseAngle parameter set to PI (180 degrees -
* smooth shading, no creases) or to whatever the user has set the
* creaseAngle. Faces in group 0 or 'off' use a
* creaseAngle of zero, meaning there is no smoothing (the normal
* of the face is used at all vertices giving the surface a faceted
* look; there will be a
* crease, or "Hard Edge," between each face in group zero). There is
* also an implied hard edge <i>between</i> each smoothing group, where they
* meet each other.</p>
* </p>
* If neither the vn nor the s token is used in the file, then normals
* are calculated using the creaseAngle set in the contructor.
* Normals are calculated on each geometry
* group separately, meaning there will be a hard edge between each
* geometry group.</dl></p>
* </p>
* <code>usemtl <i>name</i></code></p>
* <dl><dd>The current (and subsequent) geometry groups (specified with
* the 'g' token) have applied
* to them the named material property. The following set of material
* properties are available by default:</dl></p>
* <pre>
* amber amber_trans aqua aqua_filter
* archwhite archwhite2 bflesh black
* blondhair blue_pure bluegrey bluetint
* blugrn blutan bluteal bone
* bone1 bone2 brass brnhair
* bronze brown brownlips brownskn
* brzskin chappie charcoal deepgreen
* default dkblue dkblue_pure dkbrown
* dkdkgrey dkgreen dkgrey dkorange
* dkpurple dkred dkteal emerald
* fgreen flaqua flblack flblonde
* flblue_pure flbrown fldkblue_pure fldkdkgrey
* fldkgreen fldkgreen2 fldkgrey fldkolivegreen
* fldkpurple fldkred flesh fleshtransparent
* flgrey fllime flltbrown flltgrey
* flltolivegreen flmintgreen flmustard florange
* flpinegreen flpurple flred fltan
* flwhite flwhite1 flyellow glass
* glassblutint glasstransparent gold green
* greenskn grey hair iris
* jetflame lavendar lcdgreen lighttan
* lighttan2 lighttan3 lighttannew lightyellow
* lime lips ltbrown ltgrey
* meh metal mintgrn muscle
* navy_blue offwhite.cool offwhite.warm olivegreen
* orange pale_green pale_pink pale_yellow
* peach periwinkle pink pinktan
* plasma purple red redbrick
* redbrown redorange redwood rubber
* ruby sand_stone sapphire shadow
* ship2 silver skin sky_blue
* smoked_glass tan taupe teeth
* violet white yellow yellow_green
* yellowbrt yelloworng
* </pre>
* <code>mtllib <i>filename</i></code></p>
* <dl><dd>Load material properties from the named file. Materials
* with the same name as the predefined materials above will override
* the default value. Any directory path information in (filename)
* is ignored. The .mtl files are assumed to be in the same directory
* as the .obj file. If they are in a different directory, use
* Loader.setBasePath() (or Loader.setBaseUrl() ). The format of the
* material properties files
* are as follows:</p>
* <code>newmtl <i>name</i></code></p>
* <dl><dd>Start the definition of a new named material property.</dl></p>
* <code>Ka <i>float</i> <i>float</i> <i>float</i></code></p>
* <dl><dd>Ambient color.</dl></p>
* <code>Kd <i>float</i> <i>float</i> <i>float</i></code></p>
* <dl><dd>Diffuse color.</dl></p>
* <code>Ks <i>float</i> <i>float</i> <i>float</i></code></p>
* <dl><dd>Specular color.</dl></p>
* <code>illum <i>(0, 1, or 2)</i></code></p>
* <dl><dd>0 to disable lighting, 1 for ambient & diffuse only (specular
* color set to black), 2 for full lighting.</dl></p>
* <code>Ns <i>float</i></code></p>
* <dl><dd>Shininess (clamped to 1.0 - 128.0).</dl></p>
* <code>map_Kd <i>filename</i></code></p>
* <dl><dd>Texture map. Supports .rgb, .rgba, .int, .inta, .sgi, and
* .bw files in addition to those supported by
* <a href="../../utils/image/TextureLoader.html">TextureLoader</a>.
* </dl></dl></p>
*/
public class ModelObjFileFactory extends ModelImporterFactory {
// 0=Input file assumed good
// 1=Input file checked for inconsistencies
// 2=path names
// 4=flags
// 8=Timing Info
// 16=Tokens
// 32=Token details (use with 16)
// 64=limits of model coordinates
private static final int DEBUG = 0;
// These are the values to be used in constructing the
// load flags for the loader. Users should OR the selected
// values together to construct an aggregate flag integer
// (see the setFlags() method). Users wishing to load all
// data in a file should use the LOAD_ALL specifier.
/** This flag enables the loading of light objects into the scene.*/
public static final int LOAD_LIGHT_NODES = 1;
/** This flag enables the loading of fog objects into the scene.*/
public static final int LOAD_FOG_NODES = 2;
/** This flag enables the loading of background objects into the scene.*/
public static final int LOAD_BACKGROUND_NODES = 4;
/** This flag enables the loading of behaviors into the scene.*/
public static final int LOAD_BEHAVIOR_NODES = 8;
/** This flag enables the loading of view (camera) objects into
* the scene.*/
public static final int LOAD_VIEW_GROUPS = 16;
/** This flag enables the loading of sound objects into the scene.*/
public static final int LOAD_SOUND_NODES = 32;
/** This flag enables the loading of all objects into the scene.*/
public static final int LOAD_ALL = 0xffffffff;
/**
* Flag sent to constructor. The object's vertices will be changed
* so that the object is centered at (0,0,0) and the coordinate
* positions are all in the range of (-1,-1,-1) to (1,1,1).
*/
public static final int RESIZE = LOAD_SOUND_NODES << 1;
/**
* Flag sent to constructor. The Shape3D object will be created
* by using the GeometryInfo POLYGON_ARRAY primitive, causing
* them to be Triangulated by GeometryInfo. Use
* this if you suspect concave or other non-behaving polygons
* in your model.
*/
public static final int TRIANGULATE = RESIZE << 1;
/**
* Flag sent to constructor. Use if the vertices in your .obj
* file were specified with clockwise winding (Java 3D wants
* counter-clockwise) so you see the back of the polygons and
* not the front. Calls GeometryInfo.reverse().
*/
public static final int REVERSE = TRIANGULATE << 1;
/**
* Flag sent to contructor. After normals are generated the data
* will be analyzed to find triangle strips. Use this if your
* hardware supports accelerated rendering of strips.
*/
public static final int STRIPIFY = REVERSE << 1;
private static final char BACKSLASH = '\\';
private int flags;
private String basePath = null;
private URL baseUrl = null;
private boolean fromUrl = false;
private float radians;
// First, lists of points are read from the .obj file into these arrays. . .
private ArrayList<Vertex> coordList; // Holds Point3f
private ArrayList<float[]> texList; // Holds TexCoord2f
private ArrayList<Vector3D> normList; // Holds Vector3f
// . . . and index lists are read into these arrays.
private ArrayList<Integer> coordIdxList; // Holds Integer index into coordList
private ArrayList<Integer> texIdxList; // Holds Integer index into texList
private ArrayList<Integer> normIdxList; // Holds Integer index into normList
// The length of each face is stored in this array.
private ArrayList<Integer> stripCounts; // Holds Integer
// Each face's Geometry Group membership is kept here. . .
private HashMap<Integer, String> groups; // key=Integer index into stripCounts
// value=String name of group
private String curGroup;
// . . . and Smoothing Group membership is kept here
private HashMap<Integer, String> sGroups; // key=Integer index into stripCounts
// value=String name of group
private String curSgroup;
// The name of each group's "usemtl" material property is kept here
private HashMap<String, String> groupMaterials; // key=String name of Group
// value=String name of material
// After reading the entire file, the faces are converted into triangles.
// The Geometry Group information is converted into these structures. . .
private HashMap triGroups; // key=String name of group
// value=ArrayList of Integer
// indices into coordIdxList
private ArrayList curTriGroup;
// . . . and Smoothing Group info is converted into these.
private HashMap triSgroups; // key=String name of group
// value=ArrayList of Integer
// indices into coordIdxList
private ArrayList curTriSgroup;
// Finally, coordList, texList, and normList are converted to arrays for
// use with GeometryInfo
private Vertex coordArray[] = null;
private Vector3D normArray[] = null;
// private TexCoord2f texArray[] = null;
private float[][] texArray = null;
// Used for debugging
private long time;
private ObjectFileMaterials materials = null;
private PApplet pa;
private HashMap<String, Group> groupNameToGroupObj;
// private boolean flipTextureY;
private boolean debugNormalGenerator;
/**
* Default constructor. Crease Angle set to default of
* 44 degrees (see NormalGenerator utility for details). Flags
* set to zero (0).
*/
public ModelObjFileFactory() {
this(0, -1.0f);
} // End of ObjectFile()
/**
* Constructor. Crease Angle set to default of
* 44 degrees (see NormalGenerator utility for details).
* @param flags The constants from above or from
* com.sun.j3d.IModelLoaders.IModelLoader, possibly "or'ed" (|) together.
*/
private ModelObjFileFactory(int flags) {
this(flags, -1.0f);
} // End of ObjectFile(int)
/**
* Constructor.
*
* @param flags The constants from above or from
* com.sun.j3d.IModelLoaders.IModelLoader, possibly "or'ed" (|) together.
* @param radians Ignored if the vn token is present in the model (user
* normals supplied). Otherwise, crease angle to use within smoothing
* groups, or within geometry groups if the s token isn't present either.
*/
private ModelObjFileFactory(int flags, float radians) {
this.setFlags(flags);
this.radians = radians;
groupNameToGroupObj = new HashMap<String, Group>();
debugNormalGenerator = false;
} // End of ObjectFile(int, float)
/**
* Load model.
*
* @param pa the pa
* @param filename the filename
* @param creaseAngle the crease angle
* @param flipTextureY the flip texture y
* @param flipTextureX the flip texture x
*
* @return the mT triangle mesh[]
*
* @throws FileNotFoundException the file not found exception
*/
public MTTriangleMesh[] loadModelImpl(PApplet pa, String filename, float creaseAngle, boolean flipTextureY, boolean flipTextureX) throws FileNotFoundException {
this.pa = pa;
this.setBasePathFromFilename(filename);
Reader reader = null;
File file = new File(filename);
if (file.exists()){
reader = new BufferedReader(new FileReader(filename));
}else{
InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(filename);
if (stream == null){
stream = pa.getClass().getResourceAsStream(filename);
}
if (stream == null){
throw new FileNotFoundException("File not found: " + filename);
}
reader = new BufferedReader(new InputStreamReader(stream));
}
// throw new FileNotFoundException("File not found: " + file.getAbsolutePath());
return load(reader,creaseAngle, flipTextureY, flipTextureX);
} // End of load(String)
/**
* The Object File is loaded from the already opened file.
* To attach the model to your scene, call getSceneGroup() on
* the Scene object passed back, and attach the returned
* BranchGroup to your scene graph. For an example, see
* j3d-examples/ObjLoad/ObjLoad.java.
* @param flipTextureX
*/
private MTTriangleMesh[] load(Reader reader, float creaseAngle, boolean flipTextureY, boolean flipTextureX) throws FileNotFoundException, ParsingErrorException {
// ObjectFileParser does lexical analysis
ObjectFileParser st = new ObjectFileParser(reader);
coordList = new ArrayList<Vertex>();
texList = new ArrayList<float[]>();
normList = new ArrayList<Vector3D>();
coordIdxList = new ArrayList<Integer>();
texIdxList = new ArrayList<Integer>();
normIdxList = new ArrayList<Integer>();
groups = new HashMap<Integer, String>(50);
curGroup = "default";
sGroups = new HashMap<Integer, String>(50);
curSgroup = null;
stripCounts = new ArrayList<Integer>();
groupMaterials = new HashMap<String, String>(50);
groupMaterials.put(curGroup, "default");
materials = new ObjectFileMaterials();
materials.pa = this.pa;
//FIXME ADDED
Group group = new Group(curGroup);
groupNameToGroupObj.put(curGroup, group);
time = 0L;
if ((DEBUG & 8) != 0) {
time = System.currentTimeMillis();
}
//GO THROUGH THE FILE
this.readFile(st);
if ((DEBUG & 8) != 0) {
time = System.currentTimeMillis() - time;
System.out.println("Read file: " + time + " ms");
time = System.currentTimeMillis();
}
// if ((flags & RESIZE) != 0)
resize(); //FIXME resize default ja / nein?
MTTriangleMesh[] meshes = createMeshesFromGroups(creaseAngle, flipTextureY, flipTextureX);
return meshes;
} // End of load(Reader)
/**
*
* @param creaseAngle
* @param flipTextureY
* @param flipTextureX
* @return
*/
private MTTriangleMesh[] createMeshesFromGroups(float creaseAngle, boolean flipTextureY, boolean flipTextureX){
ArrayList<MTTriangleMesh> meshList = new ArrayList<MTTriangleMesh>();
TriangleNormalGenerator normalGenerator = new TriangleNormalGenerator();
normalGenerator.setDebug(this.debugNormalGenerator);
//Go through all groups and create the meshes
int totalNumVerts = 0;
Iterator e = groupNameToGroupObj.keySet().iterator();
while (e.hasNext()) {
String currentGroupName = (String) e.next();
Group currentGroup = groupNameToGroupObj.get(currentGroupName);
//Compile vertices/indices/texture arrays just for this group as a teilmenge from all with recalculated indices
currentGroup.compileItsOwnLists(coordList, texList);
//Get the new arrays
Vertex[] vertices = currentGroup.getGroupVertices(); //currentGroup.verticesForGroup.toArray(new Vertex[currentGroup.verticesForGroup.size()]);
int[] indices = currentGroup.getIndexArray(); //currentGroup.indexArray;
float[][] textureCoords = currentGroup.getGroupTexCoords(); //currentGroup.texCoordsForGroup.toArray(new float[currentGroup.texCoordsForGroup.size()][]);
int[] texIndices = currentGroup.getTexCoordIndices(); //currentGroup.texCoordIndexArray;
System.out.println("\nGroup: \"" + currentGroup.name + "\" ->Vertices: " + currentGroup.verticesForGroup.size()+ " ->TextureCoords: " + currentGroup.texCoordsForGroup.size() + " ->Indices: " + currentGroup.indexArray.length + " ->Texcoord Indices: " + currentGroup.texCoordIndexArray.length );
System.out.println();
if (vertices.length > 2){
GeometryInfo geometry = null;
//Load as all vertex normals smoothed if creaseAngle == 180;
if (creaseAngle == 180){
geometry = normalGenerator.generateSmoothNormals(pa, vertices , indices, textureCoords, texIndices, creaseAngle, flipTextureY, flipTextureX);
}else{
geometry = normalGenerator.generateCreaseAngleNormals(pa, vertices, indices, textureCoords, texIndices, creaseAngle, flipTextureY, flipTextureX);
}
MTTriangleMesh mesh = new MTTriangleMesh(pa, geometry);
if (mesh != null){
mesh.setName(currentGroupName);
//Assign texture and material
String matName = (String)groupMaterials.get(currentGroupName);
materials.assignMaterial(((PGraphicsOpenGL)pa.g).gl, matName, mesh);
if (mesh.getTexture() != null){
mesh.setTextureEnabled(true);
}else{
System.out.println("No texture could be assigned to mesh.");
}
meshList.add(mesh);
}else{
System.err.println("Mesh not created, returned null from meshDenormalization.");
}
}else{
System.out.println("Group not created, < 2 vertices..");
}
totalNumVerts += currentGroup.verticesForGroup.size();
}
System.out.println("All groups on .obj file have total number of vertices: " + totalNumVerts);
//Cleanup
stripCounts = null;
groups = null;
sGroups = null;
groupNameToGroupObj.clear();
return meshList.toArray(new MTTriangleMesh[meshList.size()]);
}
/**
* readFile
*
* Read the model data from the file.
*/
void readFile(ObjectFileParser st) throws ParsingErrorException {
int t;
st.getToken();
while (st.ttype != ObjectFileParser.TT_EOF) {
// Print out one token for each line
if ((DEBUG & 16) != 0) {
System.out.print("Token ");
if (st.ttype == ObjectFileParser.TT_EOL) System.out.println("EOL");
else if (st.ttype == ObjectFileParser.TT_WORD)
System.out.println(st.sval);
else System.out.println((char)st.ttype);
}
if (st.ttype == ObjectFileParser.TT_WORD) {
if (st.sval.equals("v")) {
readVertex(st);
} else if (st.sval.equals("vn")) {
readNormal(st);
} else if (st.sval.equals("vt")) {
readTexture(st);
} else if (st.sval.equals("f")) {
readFace(st);
} else if (st.sval.equals("fo")) { // Not sure what the dif is
readFace(st);
} else if (st.sval.equals("g")) {
readPartName(st);
} else if (st.sval.equals("s")) {
readSmoothingGroup(st);
} else if (st.sval.equals("p")) {
st.skipToNextLine();
} else if (st.sval.equals("l")) {
st.skipToNextLine();
} else if (st.sval.equals("mtllib")) {
loadMaterialFile(st);
} else if (st.sval.equals("usemtl")) {
readMaterialName(st);
} else if (st.sval.equals("maplib")) {
st.skipToNextLine();
} else if (st.sval.equals("usemap")) {
st.skipToNextLine();
} else {
/*
throw new ParsingErrorException(
"Unrecognized token, line " + st.lineno());
*/
System.err.println("Unrecognized token, line " + st.lineno());
// st.getToken();
// st.skipToNextLine();
}
}
if (st.ttype == StreamTokenizer.TT_EOF) {
break;
}
st.skipToNextLine();
// Get next token
st.getToken();
}
} // End of readFile
void readVertex(ObjectFileParser st) throws ParsingErrorException {
Vertex p = new Vertex();
st.getNumber();
p.x = (float)st.nval;
st.getNumber();
p.y = (float)st.nval;
st.getNumber();
p.z = (float)st.nval;
if ((DEBUG & 32) != 0)
System.out.println(" (" + p.x + "," + p.y + "," + p.z + ")");
st.skipToNextLine();
// Add this vertex to the array
coordList.add(p);
} // End of readVertex
/**
* readNormal
*/
void readNormal(ObjectFileParser st) throws ParsingErrorException {
Vector3D p = new Vector3D();
st.getNumber();
p.x = (float)st.nval;
st.getNumber();
p.y = (float)st.nval;
st.getNumber();
p.z = (float)st.nval;
if ((DEBUG & 32) != 0)
System.out.println(" (" + p.x + "," + p.y + "," + p.z + ")");
st.skipToNextLine();
// Add this vertex to the array
normList.add(p);
} // End of readNormal
/**
* readTexture
*/
void readTexture(ObjectFileParser st) throws ParsingErrorException {
// TexCoord2f p = new TexCoord2f();
float[] p = new float[2];
st.getNumber();
// p.x = (float)st.nval;
p[0] = (float)st.nval;
st.getNumber();
// p.y = (float)st.nval;
p[1] = (float)st.nval;
if ((DEBUG & 32) != 0)
System.out.println(" (" + p[0] + "," + p[1] + ")");
st.skipToNextLine();
// Add this vertex to the array
texList.add(p);
} // End of readTexture
/**
* readFace
*
* Adds the indices of the current face to the arrays.
*
* ViewPoint files can have up to three arrays: Vertex Positions,
* Texture Coordinates, and Vertex Normals. Each vertex can
* contain indices into all three arrays.
*/
void readFace(ObjectFileParser st) throws ParsingErrorException {
int vertIndex,
texIndex = 0,
normIndex = 0;
int count = 0;
// There are n vertices on each line. Each vertex is comprised
// of 1-3 numbers separated by slashes ('/'). The slashes may
// be omitted if there's only one number.
st.getToken();
ArrayList<Integer> faceVertIndices = new ArrayList<Integer>();
ArrayList<Integer> faceTexIndices = new ArrayList<Integer>();
while (st.ttype != StreamTokenizer.TT_EOF &&
st.ttype != StreamTokenizer.TT_EOL
) {
// First token is always a number (or EOL)
st.pushBack();
st.getNumber();
vertIndex = (int)st.nval - 1;
if (vertIndex < 0)
vertIndex += coordList.size() + 1;
coordIdxList.add(new Integer(vertIndex));
// MODIFIED
faceVertIndices.add(vertIndex);
// Next token is a slash, a number, or EOL. Continue on slash
st.getToken();
if (st.ttype == '/') {
// If there's a number after the first slash, read it
st.getToken();
if (st.ttype == StreamTokenizer.TT_WORD) {
// It's a number
st.pushBack();
st.getNumber();
texIndex = (int)st.nval - 1;
if (texIndex < 0)
texIndex += texList.size() + 1;
texIdxList.add(new Integer(texIndex));
//MODIFIED
faceTexIndices.add(new Integer(texIndex));
st.getToken();
}
// Next token is a slash, a number, or EOL. Continue on slash
if (st.ttype == '/') {
// There has to be a number after the 2nd slash
st.getNumber();
normIndex = (int)st.nval - 1;
if (normIndex < 0)
normIndex += normList.size() + 1;
normIdxList.add(new Integer(normIndex));
st.getToken();
}
}
if ((DEBUG & 32) != 0) {
System.out.println(" " + vertIndex + '/' + texIndex + '/' + normIndex);
}
count++;
}
Integer faceNum = new Integer(stripCounts.size());
stripCounts.add(new Integer(count));
// Add face to current groups
groups.put(faceNum, curGroup);
if (curSgroup != null) sGroups.put(faceNum, curSgroup);
//TODO really Triangulate any faces with more than 4 vertices
//Add Face to current group
//"Triangulate" the Quad if the objecfile defines quad faces
//instead of triangle faces, just make 2 triangles out of the quad
Group currGroup = groupNameToGroupObj.get(curGroup);
if (currGroup != null){
if (faceVertIndices.size() == 3){
//Create my own face
AFace face = new AFace();
face.p0 = faceVertIndices.get(0);
face.p1 = faceVertIndices.get(1);
face.p2 = faceVertIndices.get(2);
if (faceTexIndices.size() >= 3){
face.t0 = faceTexIndices.get(0);
face.t1 = faceTexIndices.get(1);
face.t2 = faceTexIndices.get(2);
}
currGroup.addFace(face);
}else if (faceVertIndices.size() == 4){
AFace face1 = new AFace();
face1.p0 = faceVertIndices.get(0);
face1.p1 = faceVertIndices.get(1);
face1.p2 = faceVertIndices.get(2);
AFace face2 = new AFace();
face2.p0 = faceVertIndices.get(0);
face2.p1 = faceVertIndices.get(2);
face2.p2 = faceVertIndices.get(3);
if (faceTexIndices.size() == 4){
face1.t0 = faceTexIndices.get(0);
face1.t1 = faceTexIndices.get(1);
face1.t2 = faceTexIndices.get(2);
face2.t0 = faceTexIndices.get(0);
face2.t1 = faceTexIndices.get(2);
face2.t2 = faceTexIndices.get(3);
}
currGroup.addFace(face1);
currGroup.addFace(face2);
}else{
System.err.println("Obj-Loader only supports faces with 3 or 4 vertices per face!");
}
}else{
System.err.println("CURRENT GROUP IS NULL! SOMETHINGS WRONG!");
}
if (st.ttype == StreamTokenizer.TT_EOF) {
return;
}
// In case we exited early
st.skipToNextLine();
} // End of readFace
/**
* readPartName
*/
void readPartName(ObjectFileParser st) {
st.getToken();
// Find the Material Property of the current group
String curMat = (String)groupMaterials.get(curGroup);
// New faces will be added to the curGroup
if (st.ttype != ObjectFileParser.TT_WORD)
curGroup = "default";
else
curGroup = st.sval;
if ((DEBUG & 32) != 0)
System.out.println(" Changed to group " + curGroup);
// See if this group has Material Properties yet
if (groupMaterials.get(curGroup) == null) {
// It doesn't - carry over from last group
groupMaterials.put(curGroup, curMat);
}
//FIXME ADDED!
if (groupNameToGroupObj.get(curGroup) == null){
Group group = new Group(curGroup);
groupNameToGroupObj.put(curGroup, group);
}
st.skipToNextLine();
} // End of readPartName
/**
* readMaterialName
*/
void readMaterialName(ObjectFileParser st) throws ParsingErrorException {
st.getToken();
if (st.ttype == ObjectFileParser.TT_WORD) {
String useMaterialName = new String(st.sval);
//////FIXME ADDED! added, to group by material if no groups are in obj file!
// if (curGroup.equalsIgnoreCase("default")){
curGroup = useMaterialName;
if ((DEBUG & 32) != 0)
System.out.println(" Changed to group " + curGroup);
if (groupNameToGroupObj.get(curGroup) == null){
Group group = new Group(curGroup);
groupNameToGroupObj.put(curGroup, group);
}
// // See if this group has Material Properties yet
// if (groupMaterials.get(curGroup) == null) {
// // It doesn't - carry over from last group
// groupMaterials.put(curGroup, curMat);
// }
// }
//////// added to group by material if no groups are in obj file!
groupMaterials.put(curGroup, useMaterialName);
if ((DEBUG & 32) != 0) {
System.out.println(" Material Property " + st.sval +
" assigned to group " + curGroup);
}
}
st.skipToNextLine();
} // End of readMaterialName
/**
* loadMaterialFile
*
* Both types of slashes are returned as tokens from our parser,
* so we go through the line token by token and keep just the
* last token on the line. This should be the filename without
* any directory info.
*/
void loadMaterialFile(ObjectFileParser st) throws ParsingErrorException {
String s = null;
// Filenames are case sensitive
st.lowerCaseMode(false);
// Get name of material file (skip path)
do {
st.getToken();
if (st.ttype == ObjectFileParser.TT_WORD) s = st.sval;
} while (st.ttype != ObjectFileParser.TT_EOL);
materials.readMaterialFile(basePath, s);
st.lowerCaseMode(true);
st.skipToNextLine();
} // End of loadMaterialFile
/**
* readSmoothingGroup
*/
void readSmoothingGroup(ObjectFileParser st) throws ParsingErrorException {
st.getToken();
if (st.ttype != ObjectFileParser.TT_WORD) {
st.skipToNextLine();
return;
}
if (st.sval.equals("off")) curSgroup = "0";
else curSgroup = st.sval;
if ((DEBUG & 32) != 0) System.out.println(" Smoothing group " + curSgroup);
st.skipToNextLine();
} // End of readSmoothingGroup
/**
* Takes a file name and sets the base path to the directory
* containing that file.
*/
private void setBasePathFromFilename(String fileName) {
if (fileName.lastIndexOf(MTApplication.separator) == -1 && fileName.lastIndexOf(java.io.File.separator) == -1) {
// No path given - current directory
setBasePath("");
} else{
if (fileName.lastIndexOf(MTApplication.separator) != -1 ) {
setBasePath(
fileName.substring(0, fileName.lastIndexOf(MTApplication.separator)));
}else if (fileName.lastIndexOf(java.io.File.separator) != -1){
setBasePath(
fileName.substring(0, fileName.lastIndexOf(java.io.File.separator)));
}
}
/*
if (fileName.lastIndexOf(java.io.File.separator) == -1) {
// No path given - current directory
setBasePath("." + java.io.File.separator);
} else {
setBasePath(
fileName.substring(0, fileName.lastIndexOf(java.io.File.separator)));
}
*/
} // End of setBasePathFromFilename
/**
* getLimits
*
* Returns an array of Vertex which form a bounding box around the
* object. Element 0 is the low value, element 1 is the high value.
* See normalize() below for an example of how to use this method.
*/
private Vertex[] getLimits() {
Vertex cur_vtx = new Vertex();
// Find the limits of the model
Vertex[] limit = new Vertex[2];
limit[0] = new Vertex(Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE);
limit[1] = new Vertex(Float.MIN_VALUE, Float.MIN_VALUE, Float.MIN_VALUE);
for (int i = 0 ; i < coordList.size() ; i++) {
cur_vtx = (Vertex)coordList.get(i);
// Keep track of limits for normalization
if (cur_vtx.x < limit[0].x) limit[0].x = cur_vtx.x;
if (cur_vtx.x > limit[1].x) limit[1].x = cur_vtx.x;
if (cur_vtx.y < limit[0].y) limit[0].y = cur_vtx.y;
if (cur_vtx.y > limit[1].y) limit[1].y = cur_vtx.y;
if (cur_vtx.z < limit[0].z) limit[0].z = cur_vtx.z;
if (cur_vtx.z > limit[1].z) limit[1].z = cur_vtx.z;
}
if ((DEBUG & 64) != 0) {
System.out.println("Model range: (" +
limit[0].x + "," + limit[0].y + "," + limit[0].z + ") to (" +
limit[1].x + "," + limit[1].y + "," + limit[1].z + ")");
}
return limit;
} // End of getLimits
/**
* Center the object and make it (-1,-1,-1) to (1,1,1).
*/
private void resize() {
int i, j;
Vertex cur_vtx = new Vertex();
float biggest_dif;
Vertex[] limit = getLimits();
// Move object so it's centered on (0,0,0)
Vector3D offset = new Vector3D(-0.5f * (limit[0].x + limit[1].x),
-0.5f * (limit[0].y + limit[1].y),
-0.5f * (limit[0].z + limit[1].z));
if ((DEBUG & 64) != 0) {
System.out.println("Offset amount: (" +
offset.x + "," + offset.y + "," + offset.z + ")");
}
// Find the divide-by value for the normalization
biggest_dif = limit[1].x - limit[0].x;
if (biggest_dif < limit[1].y - limit[0].y)
biggest_dif = limit[1].y - limit[0].y;
if (biggest_dif < limit[1].z - limit[0].z)
biggest_dif = limit[1].z - limit[0].z;
biggest_dif /= 2.0f;
for (i = 0 ; i < coordList.size() ; i++) {
cur_vtx = (Vertex)coordList.get(i);
// cur_vtx.add(cur_vtx, offset);
Vector3D tmp = cur_vtx.getAdded(offset);
cur_vtx = new Vertex(tmp.x,tmp.y,tmp.z);
// Vector3D tmp = new Vector3D(offset);
// cur_vtx.addLocal(tmp);
// cur_vtx.addLocal(cur_vtx);
cur_vtx.x /= biggest_dif;
cur_vtx.y /= biggest_dif;
cur_vtx.z /= biggest_dif;
// coordList.setElementAt(cur_vtx, i);
}
} // End of resize
// private int[] objectToIntArray(ArrayList inList) {
// int outList[] = new int[inList.size()];
// for (int i = 0 ; i < inList.size() ; i++) {
// outList[i] = ((Integer)inList.get(i)).intValue();
// }
// return outList;
// } // End of objectToIntArray
//
//
// private Vertex[] objectToPoint3Array(ArrayList inList) {
// Vertex outList[] = new Vertex[inList.size()];
// for (int i = 0 ; i < inList.size() ; i++) {
// outList[i] = (Vertex)inList.get(i);
// }
// return outList;
// } // End of objectToPoint3Array
//
//
//
//// private TexCoord2f[] objectToTexCoord2Array(ArrayList inList) {
//// TexCoord2f outList[] = new TexCoord2f[inList.size()];
//// for (int i = 0 ; i < inList.size() ; i++) {
//// outList[i] = (TexCoord2f)inList.get(i);
//// }
//// return outList;
//// } // End of objectToTexCoord2Array
//
// private float[][] objectToTexCoord2Array(ArrayList inList) {
// float outList[][] = new float[inList.size()][2];
// for (int i = 0 ; i < inList.size() ; i++) {
//// outList[i] = (TexCoord2f)inList.get(i);
// outList[i][0] = ((float[]) inList.get(i))[0];
// outList[i][1] = ((float[]) inList.get(i))[1];
// }
// return outList;
// } //
//
//
// private Vector3D[] objectToVectorArray(ArrayList inList) {
// Vector3D outList[] = new Vector3D[inList.size()];
// for (int i = 0 ; i < inList.size() ; i++) {
// outList[i] = (Vector3D)inList.get(i);
// }
// return outList;
// } // End of objectToVectorArray
//
//
// /**
// * Each group is a list of indices into the model's index lists,
// * indicating the starting index of each triangle in the group.
// * This method converts those data structures
// * into an integer array to use with GeometryInfo.
// */
// private int[] groupIndices(ArrayList sourceList, ArrayList group) {
// int indices[] = new int[group.size() * 3];
// for (int i = 0 ; i < group.size() ; i++) {
// int j = ((Integer)group.get(i)).intValue();
// indices[i * 3 + 0] = ((Integer)sourceList.get(j + 0)).intValue();
// indices[i * 3 + 1] = ((Integer)sourceList.get(j + 1)).intValue();
// indices[i * 3 + 2] = ((Integer)sourceList.get(j + 2)).intValue();
// }
// return indices;
// } // end of groupIndices
//
/**
* For an .obj file loaded from a URL, set the URL where associated files
* (like material properties files) will be found.
* Only needs to be called to set it to a different URL
* from that containing the .obj file.
*/
public void setBaseUrl(URL url) {
baseUrl = url;
} // End of setBaseUrl
/**
* Return the URL where files associated with this .obj file (like
* material properties files) will be found.
*/
public URL getBaseUrl() {
return baseUrl;
} // End of getBaseUrl
/**
* Set the path where files associated with this .obj file are
* located.
* Only needs to be called to set it to a different directory
* from that containing the .obj file.
*/
private void setBasePath(String pathName) {
basePath = pathName;
// if (basePath == null || basePath == "")
// basePath = "." + java.io.File.separator;
// basePath = basePath.replace('/', java.io.File.separatorChar);
// basePath = basePath.replace('\\', java.io.File.separatorChar);
// if (!basePath.endsWith(java.io.File.separator))
// basePath = basePath + java.io.File.separator;
// if (basePath == null || basePath == "")
// basePath = MTApplication.separator;
// basePath = basePath.replace('/', MTApplication.separatorChar);
basePath = basePath.replace('\\', MTApplication.separatorChar);
if (!basePath.endsWith(MTApplication.separator))
basePath = basePath + MTApplication.separator;
} // End of setBasePath
/**
* Return the path where files associated with this .obj file (like material
* files) are located.
*/
public String getBasePath() {
return basePath;
} // End of getBasePath
/**
* Set parameters for loading the model.
* Flags defined in IModelLoader.java are ignored by the ObjectFile IModelLoader
* because the .obj file format doesn't include lights, fog, background,
* behaviors, views, or sounds. However, several flags are defined
* specifically for use with the ObjectFile IModelLoader (see above).
*/
public void setFlags(int flags) {
this.flags = flags;
if ((DEBUG & 4) != 0) System.out.println("Flags = " + flags);
} // End of setFlags
public void setDebug(boolean debugNormalGenerator) {
this.debugNormalGenerator = debugNormalGenerator;
}
/**
* Get the parameters currently defined for loading the model.
* Flags defined in IModelLoader.java are ignored by the ObjectFile IModelLoader
* because the .obj file format doesn't include lights, fog, background,
* behaviors, views, or sounds. However, several flags are defined
* specifically for use with the ObjectFile IModelLoader (see above).
*/
public int getFlags() {
return flags;
} // End of getFlags
/**
* A class representing one group in a .obj file
* @author C.Ruff
*
*/
private class Group{
private String name;
private ArrayList<AFace> faces;
private ArrayList<Vertex> verticesForGroup;
private HashMap<Integer, Integer> oldIndexToNewIndex;
private ArrayList<float[]> texCoordsForGroup;
private HashMap<Integer, Integer> oldTexIndexToNewTexIndex;
private int[] indexArray;
private int[] texCoordIndexArray;
public Group(String name){
this.name = name;
faces = new ArrayList<AFace>();
verticesForGroup = new ArrayList<Vertex>();
oldIndexToNewIndex = new HashMap<Integer, Integer>();
texCoordsForGroup = new ArrayList<float[]>();
oldTexIndexToNewTexIndex = new HashMap<Integer, Integer>();
indexArray = new int[0];
texCoordIndexArray = new int[0];
name = "default";
}
public void addFace(AFace face){
faces.add(face);
}
//Irgendwann am ende ausf�hren
//=>f�r jede gruppe eigene liste von vertices on indices speichern
//aus hauptlisten rausholen
/**
* Uses the faces attached to this group during the parsing process and the lists with
* all vertex and all texture coordinates of the obj file to create arrays for this group
* with vertices and texture coords that only belong to this single group.
*
* @param allFileVerts
* @param allTexCoords
*/
public void compileItsOwnLists(ArrayList<Vertex> allFileVerts, ArrayList<float[]> allTexCoords){
indexArray = new int[faces.size()*3];
if (allTexCoords.size() > 0){
texCoordIndexArray = new int[faces.size()*3];
}
for (int i = 0; i < faces.size(); i++) {
AFace currentFace = faces.get(i);
Vertex v0 = allFileVerts.get(currentFace.p0);
Vertex v1 = allFileVerts.get(currentFace.p1);
Vertex v2 = allFileVerts.get(currentFace.p2);
if ( allTexCoords.size() > currentFace.t0
&& allTexCoords.size() > currentFace.t1
&& allTexCoords.size() > currentFace.t2
){
float[] texV0 = allTexCoords.get(currentFace.t0);
float[] texV1 = allTexCoords.get(currentFace.t1);
float[] texV2 = allTexCoords.get(currentFace.t2);
//Etwas redundant immer wieder zu machen beim gleichen vertex..whatever
v0.setTexCoordU(texV0[0]);
v0.setTexCoordV(texV0[1]);
v1.setTexCoordU(texV1[0]);
v1.setTexCoordV(texV1[1]);
v2.setTexCoordU(texV2[0]);
v2.setTexCoordV(texV2[1]);
//Check if there is a texture index in the hashtable at the faces texture pointer
//if not, create a new index = the end of thexcoords list, and put the pointer into the hash
//if yes, point the faces texture pointer to the pointer in the hash
//This process maps the texture coords and indices of all the groups in the obj
//file to only this groups texture coord list and texture indices, the indices
//are created from the index in the thex coord list when they are put in
//Only the texture coordinates are added to the list that have not been adressed
//in the texture indices pointers in the faces
//Same texture pointers will point to the same texcoord in the list
Integer oldToNewT0 = oldTexIndexToNewTexIndex.get(currentFace.t0);
if (oldToNewT0 != null){
currentFace.t0 = oldToNewT0;
}else{
int newIndex = texCoordsForGroup.size();
texCoordsForGroup.add(texV0);
oldTexIndexToNewTexIndex.put(currentFace.t0, newIndex);
currentFace.t0 = newIndex;
}
Integer oldToNewT1 = oldTexIndexToNewTexIndex.get(currentFace.t1);
if (oldToNewT1 != null){
currentFace.t1 = oldToNewT1;
}else{
int newIndex = texCoordsForGroup.size();
texCoordsForGroup.add(texV1);
oldTexIndexToNewTexIndex.put(currentFace.t1, newIndex);
currentFace.t1 = newIndex;
}
Integer oldToNewT2 = oldTexIndexToNewTexIndex.get(currentFace.t2);
if (oldToNewT2 != null){
currentFace.t2 = oldToNewT2;
}else{
int newIndex = texCoordsForGroup.size();
texCoordsForGroup.add(texV2);
oldTexIndexToNewTexIndex.put(currentFace.t2, newIndex);
currentFace.t2 = newIndex;
}
}
//Do the same for the vertices.
//Create a new vertex pointer when adding the vertex to the list
Integer oldToNewP0 = oldIndexToNewIndex.get(currentFace.p0);
if (oldToNewP0 != null){
//index of the old vertex list has already been mapped to a new one here -> use the new index in the face
currentFace.p0 = oldToNewP0;
}else{
int newIndex = verticesForGroup.size();
verticesForGroup.add(v0);
//mark that the former index (for exmample 323) is now at new index (f.e. 1)
oldIndexToNewIndex.put(currentFace.p0, newIndex);
currentFace.p0 = newIndex;
}
Integer oldToNewP1 = oldIndexToNewIndex.get(currentFace.p1);
if (oldToNewP1 != null){
currentFace.p1 = oldToNewP1;
}else{
int newIndex = verticesForGroup.size();
verticesForGroup.add(v1);
oldIndexToNewIndex.put(currentFace.p1, newIndex);
currentFace.p1 = newIndex;
}
Integer oldToNewP2 = oldIndexToNewIndex.get(currentFace.p2);
if (oldToNewP2 != null){
currentFace.p2 = oldToNewP2;
}else{
int newIndex = verticesForGroup.size();
verticesForGroup.add(v2);
oldIndexToNewIndex.put(currentFace.p2, newIndex);
currentFace.p2 = newIndex;
}
indexArray[i*3] = currentFace.p0;
indexArray[i*3+1] = currentFace.p1;
indexArray[i*3+2] = currentFace.p2;
if (allTexCoords.size() > 0){
texCoordIndexArray[i*3] = currentFace.t0;
texCoordIndexArray[i*3+1] = currentFace.t1;
texCoordIndexArray[i*3+2] = currentFace.t2;
}
}
}
public int[] getIndexArray() {
return indexArray;
}
public String getName() {
return name;
}
public int[] getTexCoordIndices() {
return texCoordIndexArray;
}
public float[][] getGroupTexCoords() {
return texCoordsForGroup.toArray(new float[this.texCoordsForGroup.size()][]);
}
public Vertex[] getGroupVertices() {
return verticesForGroup.toArray(new Vertex[this.verticesForGroup.size()]);
}
}
/**
* A class representing a face in the obj file.
* Has pointers into the vertex and the texture arrays.
*
* @author C.Ruff
*
*/
private class AFace{
int p0;
int p1;
int p2;
int t0;
int t1;
int t2;
public AFace(){
p0 = -1;
p1 = -1;
p2 = -1;
t0 = 0;
t1 = 0;
t2 = 0;
}
}
/**
* Each face is converted to triangles. As each face is converted,
* we look up which geometry group and smoothing group the face
* belongs to. The generated triangles are added to each of these
* groups, which are also being converted to a new triangle based format.
*
* We need to convert to triangles before normals are generated
* because of smoothing groups. The faces in a smoothing group
* are copied into a GeometryInfo to have their normals calculated,
* and then the normals are copied out of the GeometryInfo using
* GeometryInfo.getNormalIndices. As part of Normal generation,
* the geometry gets converted to Triangles. So we need to convert
* to triangles *before* Normal generation so that the normals we
* read out of the GeometryInfo match up with the vertex data
* that we sent in. If we sent in TRIANGLE_FAN data, the normal
* generator would convert it to triangles and we'd read out
* normals formatted for Triangle data. This would not match up
* with our original Fan data, so we couldn't tell which normals
* go with which vertices.
*/
/* private void convertToTriangles() {
boolean triangulate = (flags & TRIANGULATE) != 0;
boolean textures = !texList.isEmpty() && !texIdxList.isEmpty() &&
(texIdxList.size() == coordIdxList.size());
boolean normals = !normList.isEmpty() && !normIdxList.isEmpty() &&
(normIdxList.size() == coordIdxList.size());
int numFaces = stripCounts.size();
boolean haveSgroups = curSgroup != null;
triGroups = new HashMap(50);
if (haveSgroups) triSgroups = new HashMap(50);
ArrayList newCoordIdxList = null;
ArrayList newTexIdxList = null;
ArrayList newNormIdxList = null;
if (triangulate) {
GeometryInfo gi = new GeometryInfo(GeometryInfo.POLYGON_ARRAY);
gi.setStripCounts(objectToIntArray(stripCounts));
gi.setCoordinates(coordArray);
gi.setCoordinateIndices(objectToIntArray(coordIdxList));
if (textures) {
gi.setTextureCoordinateParams(1, 2);
gi.setTextureCoordinates(0, texArray);
gi.setTextureCoordinateIndices(0, objectToIntArray(texIdxList));
}
if (normals) {
gi.setNormals(normArray);
gi.setNormalIndices(objectToIntArray(normIdxList));
}
gi.convertToIndexedTriangles();
// Data is now indexed triangles. Next step is to take the data
// out of the GeometryInfo and put into internal data structures
int coordIndicesArray[] = gi.getCoordinateIndices();
// Fix for #4366060
// Make sure triangulated geometry has the correct number of triangles
int tris = 0;
for (int i = 0 ; i < numFaces ; i++)
tris += ((Integer)stripCounts.get(i)).intValue() - 2;
if (coordIndicesArray.length != (tris * 3)) {
// Model contains bad polygons that didn't triangulate into the
// correct number of triangles. Fall back to "simple" triangulation
triangulate = false;
} else {
int texIndicesArray[] = gi.getTextureCoordinateIndices();
int normIndicesArray[] = gi.getNormalIndices();
// Convert index arrays to internal ArrayList format
coordIdxList.clear();
texIdxList.clear();
normIdxList.clear();
for (int i = 0 ; i < coordIndicesArray.length ; i++) {
coordIdxList.add(new Integer(coordIndicesArray[i]));
if (textures) texIdxList.add(new Integer(texIndicesArray[i]));
if (normals) normIdxList.add(new Integer(normIndicesArray[i]));
}
}
}
if (!triangulate) {
newCoordIdxList = new ArrayList();
if (textures) newTexIdxList = new ArrayList();
if (normals) newNormIdxList = new ArrayList();
}
// Repeat for each face in the model - add the triangles from each
// face to the Geometry and Smoothing Groups
int baseVertex = 0;
for (int f = 0 ; f < numFaces ; f++) {
int faceSize = ((Integer)stripCounts.get(f)).intValue();
// Find out the name of the group to which this face belongs
Integer curFace = new Integer(f);
curGroup = (String)groups.get(curFace);
// Change to a new geometry group, create if it doesn't exist
curTriGroup = (ArrayList)triGroups.get(curGroup);
if (curTriGroup == null) {
curTriGroup = new ArrayList();
triGroups.put(curGroup, curTriGroup);
}
// Change to a new smoothing group, create if it doesn't exist
if (haveSgroups) {
curSgroup = (String)sGroups.get(curFace);
if (curSgroup == null) {
// Weird case - this face has no smoothing group. Happens if the
// first 's' token comes after some faces have already been defined.
// Assume they wanted no smoothing for these faces
curSgroup = "0";
}
curTriSgroup = (ArrayList)triSgroups.get(curSgroup);
if (curTriSgroup == null) {
curTriSgroup = new ArrayList();
triSgroups.put(curSgroup, curTriSgroup);
}
}
if (triangulate) {
// Each polygon of n vertices is now n-2 triangles
for (int t = 0 ; t < faceSize - 2 ; t++) {
// The groups just remember the first vertex of each triangle
Integer triBaseVertex = new Integer(baseVertex);
curTriGroup.add(triBaseVertex);
if (haveSgroups) curTriSgroup.add(triBaseVertex);
baseVertex += 3;
}
} else {
// Triangulate simply
for (int v = 0 ; v < faceSize - 2 ; v++) {
// Add this triangle to the geometry group and the smoothing group
Integer triBaseVertex = new Integer(newCoordIdxList.size());
curTriGroup.add(triBaseVertex);
if (haveSgroups) curTriSgroup.add(triBaseVertex);
newCoordIdxList.add(coordIdxList.get(baseVertex));
newCoordIdxList.add(coordIdxList.get(baseVertex + v + 1));
newCoordIdxList.add(coordIdxList.get(baseVertex + v + 2));
if (textures) {
newTexIdxList.add(texIdxList.get(baseVertex));
newTexIdxList.add(texIdxList.get(baseVertex + v + 1));
newTexIdxList.add(texIdxList.get(baseVertex + v + 2));
}
if (normals) {
newNormIdxList.add(normIdxList.get(baseVertex));
newNormIdxList.add(normIdxList.get(baseVertex + v + 1));
newNormIdxList.add(normIdxList.get(baseVertex + v + 2));
}
}
baseVertex += faceSize;
}
}
// No need to keep these around
stripCounts = null;
groups = null;
sGroups = null;
if (!triangulate) {
coordIdxList = newCoordIdxList;
texIdxList = newTexIdxList;
normIdxList = newNormIdxList;
}
} // End of convertToTriangles
*/
// /**
// * smoothingGroupNormals
// *
// * Smoothing groups are groups of faces who should be grouped
// * together for normal calculation purposes. The faces are
// * put into a GeometryInfo object and normals are calculated
// * with a 180 degree creaseAngle (no creases) or whatever the
// * user has specified. The normals
// * are then copied out of the GeometryInfo and back into
// * ObjectFile data structures.
// */
// private void smoothingGroupNormals() {
// NormalGenerator ng =
// new NormalGenerator(radians == -1.0f ? Math.PI : radians);
// NormalGenerator ng0 = new NormalGenerator(0.0);
// normList.clear();
// normIdxList = null;
// int newNormIdxArray[] = new int[coordIdxList.size()];
// Iterator e = triSgroups.keySet().iterator();
// while (e.hasNext()) {
// String curname = (String)e.next();
// ArrayList triList = (ArrayList)triSgroups.get(curname);
// // Check for group with no faces
// if (triList.size() > 0) {
// GeometryInfo gi = new GeometryInfo(GeometryInfo.TRIANGLE_ARRAY);
// gi.setCoordinateIndices(groupIndices(coordIdxList, triList));
// gi.setCoordinates(coordArray);
// if (curname.equals("0")) ng0.generateNormals(gi);
// else ng.generateNormals(gi);
// // Get the generated normals and indices
// Vector3D genNorms[] = gi.getNormals();
// int genNormIndices[] = gi.getNormalIndices();
// // Now we need to copy the generated normals into ObjectFile
// // data structures (normList and normIdxList). The variable
// // normIdx is the index of the index of the normal currently
// // being put into the list. It takes some calculation to
// // figure out the new index and where to put it.
// int normIdx = 0;
// // Repeat for each triangle in the smoothing group
// for (int i = 0 ; i < triList.size() ; i++) {
// // Get the coordIdxList index of the first index in this face
// int idx = ((Integer)triList.get(i)).intValue();
// // Repeat for each vertex in the triangle
// for (int j = 0 ; j < 3 ; j++) {
// // Put the new normal's index into the index list
// newNormIdxArray[idx + j] = normList.size();
// // Add the vertex's normal to the normal list
// normList.add(genNorms[genNormIndices[normIdx++]]);
// }
// }
// }
// }
// normIdxList = new ArrayList(coordIdxList.size());
// for (int i = 0 ; i < coordIdxList.size() ; i++) {
// normIdxList.add(new Integer(newNormIdxArray[i]));
// }
// normArray = objectToVectorArray(normList);
// } // end of smoothingGroupNormals
} // End of class ObjectFile
//End of file ObjectFile.java