org.scijava.java3d.loaders.objectfile.ObjectFile Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of j3dutils Show documentation
Show all versions of j3dutils Show documentation
Utility functions for the Java 3D Graphics API
The newest version!
/*
* 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.
*
*/
package org.scijava.java3d.loaders.objectfile;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import org.scijava.java3d.BranchGroup;
import org.scijava.java3d.Shape3D;
import org.scijava.vecmath.Point3f;
import org.scijava.vecmath.TexCoord2f;
import org.scijava.vecmath.Vector3f;
import org.scijava.java3d.loaders.IncorrectFormatException;
import org.scijava.java3d.loaders.Loader;
import org.scijava.java3d.loaders.ParsingErrorException;
import org.scijava.java3d.loaders.Scene;
import org.scijava.java3d.loaders.SceneBase;
import org.scijava.java3d.utils.geometry.GeometryInfo;
import org.scijava.java3d.utils.geometry.NormalGenerator;
import org.scijava.java3d.utils.geometry.Stripifier;
/**
* 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.
*
* The Object File tokens currently supported by this loader are:
* v float float float
* - A single vertex's geometric position in space. The first vertex
* listed in the file has index 1,
* and subsequent vertices are numbered sequentially.
* vn float float float
* - A normal. The first normal in the file is index 1, and
* subsequent normals are numbered sequentially.
* vt float float
* - A texture coordinate. The first texture coordinate in the file is
* index 1, and subsequent normals are numbered sequentially.
* f int int int . . .
* - or
* f int/int int/int int/int . . .
* - or
* f int/int/int int/int/int int/int/int . . .
* - 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.
* g name
* - 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."
* s int
* - or
* s off
* - 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 between each smoothing group, where they
* meet each other.
*
* 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.
*
* usemtl name
* - 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:
*
* 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
*
* mtllib filename
* - 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:
*
newmtl name
* - Start the definition of a new named material property.
* Ka float float float
* - Ambient color.
* Kd float float float
* - Diffuse color.
* Ks float float float
* - Specular color.
* illum (0, 1, or 2)
* - 0 to disable lighting, 1 for ambient & diffuse only (specular
* color set to black), 2 for full lighting.
* Ns float
* - Shininess (clamped to 1.0 - 128.0).
* map_Kd filename
* - Texture map. Supports .rgb, .rgba, .int, .inta, .sgi, and
* .bw files in addition to those supported by
* TextureLoader.
*
*/
public class ObjectFile implements Loader {
// 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;
/**
* 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 coordList;
private ArrayList texList;
private ArrayList normList;
// . . . and index lists are read into these arrays.
private ArrayList coordIdxList; // Holds Integer index into coordList
private ArrayList texIdxList; // Holds Integer index into texList
private ArrayList normIdxList; // Holds Integer index into normList
// The length of each face is stored in this array.
private ArrayList stripCounts; // Holds Integer
// Each face's Geometry Group membership is kept here. . .
private HashMap groups; // key=Integer index into stripCounts
// value=String name of group
private String curGroup;
// . . . and Smoothing Group membership is kept here
private HashMap 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 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 Point3f coordArray[] = null;
private Vector3f normArray[] = null;
private TexCoord2f texArray[] = null;
// Used for debugging
private long time;
private ObjectFileMaterials materials = null;
void readVertex(ObjectFileParser st) throws ParsingErrorException {
Point3f p = new Point3f();
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 {
Vector3f p = new Vector3f();
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();
st.getNumber();
p.x = (float)st.nval;
st.getNumber();
p.y = (float)st.nval;
if ((DEBUG & 32) != 0)
System.out.println(" (" + p.x + "," + p.y + ")");
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();
while (st.ttype != st.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));
// 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 == st.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));
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);
// 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);
}
st.skipToNextLine();
} // End of readPartName
/**
* readMaterialName
*/
void readMaterialName(ObjectFileParser st) throws ParsingErrorException {
st.getToken();
if (st.ttype == ObjectFileParser.TT_WORD) {
groupMaterials.put(curGroup, new String(st.sval));
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(fromUrl,
fromUrl ? baseUrl.toString() : 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
/**
* 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());
}
}
st.skipToNextLine();
// Get next token
st.getToken();
}
} // End of readFile
/**
* Constructor.
*
* @param flags The constants from above or from
* org.scijava.java3d.loaders.Loader, 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.
*/
public ObjectFile(int flags, float radians) {
setFlags(flags);
this.radians = radians;
} // End of ObjectFile(int, float)
/**
* Constructor. Crease Angle set to default of
* 44 degrees (see NormalGenerator utility for details).
* @param flags The constants from above or from
* org.scijava.java3d.loaders.Loader, possibly "or'ed" (|) together.
*/
public ObjectFile(int flags) {
this(flags, -1.0f);
} // End of ObjectFile(int)
/**
* Default constructor. Crease Angle set to default of
* 44 degrees (see NormalGenerator utility for details). Flags
* set to zero (0).
*/
public ObjectFile() {
this(0, -1.0f);
} // End of ObjectFile()
/**
* Takes a file name and sets the base path to the directory
* containing that file.
*/
private void setBasePathFromFilename(String fileName) {
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
/**
* The Object File is loaded from the .obj file specified by
* the filename.
* 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.
*/
@Override
public Scene load(String filename) throws FileNotFoundException,
IncorrectFormatException,
ParsingErrorException {
setBasePathFromFilename(filename);
Reader reader = new BufferedReader(new FileReader(filename));
return load(reader);
} // End of load(String)
private void setBaseUrlFromUrl(URL url) throws FileNotFoundException {
String u = url.toString();
String s;
if (u.lastIndexOf('/') == -1) {
s = url.getProtocol() + ":";
} else {
s = u.substring(0, u.lastIndexOf('/') + 1);
}
try {
baseUrl = new URL(s);
}
catch (MalformedURLException e) {
throw new FileNotFoundException(e.getMessage());
}
} // End of setBaseUrlFromUrl
/**
* The object file is loaded off of the web.
* 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.
*/
@Override
public Scene load(URL url) throws FileNotFoundException,
IncorrectFormatException,
ParsingErrorException {
BufferedReader reader;
if (baseUrl == null) setBaseUrlFromUrl(url);
try {
reader = new BufferedReader(new InputStreamReader(url.openStream()));
}
catch (IOException e) {
throw new FileNotFoundException(e.getMessage());
}
fromUrl = true;
return load(reader);
} // End of load(URL)
/**
* getLimits
*
* Returns an array of Point3f 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 Point3f[] getLimits() {
// Find the limits of the model
Point3f[] limit = new Point3f[2];
limit[0] = new Point3f(Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE);
limit[1] = new Point3f(Float.MIN_VALUE, Float.MIN_VALUE, Float.MIN_VALUE);
for (int i = 0 ; i < coordList.size() ; i++) {
Point3f cur_vtx = 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;
float biggest_dif;
Point3f[] limit = getLimits();
// Move object so it's centered on (0,0,0)
Vector3f offset = new Vector3f(-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++) {
Point3f cur_vtx = coordList.get(i);
cur_vtx.add(cur_vtx, offset);
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 Point3f[] objectToPoint3Array(ArrayList inList) {
Point3f outList[] = new Point3f[inList.size()];
for (int i = 0 ; i < inList.size() ; i++) {
outList[i] = 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] = inList.get(i);
}
return outList;
} // End of objectToTexCoord2Array
private Vector3f[] objectToVectorArray(ArrayList inList) {
Vector3f outList[] = new Vector3f[inList.size()];
for (int i = 0 ; i < inList.size() ; i++) {
outList[i] = 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
/**
* 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
Vector3f 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
/**
* 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
private SceneBase makeScene() {
// Create Scene to pass back
SceneBase scene = new SceneBase();
BranchGroup group = new BranchGroup();
scene.setSceneGroup(group);
boolean gen_norms = normList.isEmpty() || normIdxList.isEmpty() ||
(normIdxList.size() != coordIdxList.size());
boolean do_tex = !texList.isEmpty() && !texIdxList.isEmpty() &&
(texIdxList.size() == coordIdxList.size());
// Convert ArrayLists to arrays
coordArray = objectToPoint3Array(coordList);
if (!gen_norms) normArray = objectToVectorArray(normList);
if (do_tex) texArray = objectToTexCoord2Array(texList);
convertToTriangles();
if ((DEBUG & 8) != 0) {
time = System.currentTimeMillis() - time;
System.out.println("Convert to triangles: " + time + " ms");
time = System.currentTimeMillis();
}
if ((gen_norms) && (curSgroup != null)) {
smoothingGroupNormals();
gen_norms = false;
if ((DEBUG & 8) != 0) {
time = System.currentTimeMillis() - time;
System.out.println("Smoothing group normals: " + time + " ms");
time = System.currentTimeMillis();
}
}
NormalGenerator ng = null;
if (gen_norms) ng = new NormalGenerator(radians);
Stripifier strippy = null;
if ((flags & STRIPIFY) != 0) strippy = new Stripifier();
long t1 = 0, t2 = 0, t3 = 0, t4 = 0;
// Each "Group" of faces in the model will be one Shape3D
Iterator e = triGroups.keySet().iterator();
while (e.hasNext()) {
String curname = (String)e.next();
ArrayList triList = (ArrayList)triGroups.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 (do_tex) {
gi.setTextureCoordinateParams(1, 2);
gi.setTextureCoordinates(0, texArray);
gi.setTextureCoordinateIndices(0, groupIndices(texIdxList, triList));
}
if ((DEBUG & 8) != 0) time = System.currentTimeMillis();
if (gen_norms) {
if ((flags & REVERSE) != 0) gi.reverse();
ng.generateNormals(gi);
if ((DEBUG & 8) != 0) {
t2 += System.currentTimeMillis() - time;
System.out.println("Generate normals: " + t2 + " ms");
time = System.currentTimeMillis();
}
} else {
gi.setNormalIndices(groupIndices(normIdxList, triList));
gi.setNormals(normArray);
if ((flags & REVERSE) != 0) gi.reverse();
}
if ((flags & STRIPIFY) != 0) {
strippy.stripify(gi);
if ((DEBUG & 8) != 0) {
t3 += System.currentTimeMillis() - time;
System.out.println("Stripify: " + t3 + " ms");
time = System.currentTimeMillis();
}
}
// Put geometry into Shape3d
Shape3D shape = new Shape3D();
// issue 638; default to BY_COPY for consistency
shape.setGeometry(gi.getGeometryArray(false, false, false));
String matName = (String)groupMaterials.get(curname);
materials.assignMaterial(matName, shape);
group.addChild(shape);
scene.addNamedObject(curname, shape);
if ((DEBUG & 8) != 0) {
t4 += System.currentTimeMillis() - time;
System.out.println("Shape 3D: " + t4 + " ms");
time = System.currentTimeMillis();
}
}
}
return scene;
} // end of makeScene
/**
* 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.
*/
@Override
public Scene load(Reader reader) throws FileNotFoundException,
IncorrectFormatException,
ParsingErrorException {
// ObjectFileParser does lexical analysis
ObjectFileParser st = new ObjectFileParser(reader);
coordList = new ArrayList();
texList = new ArrayList();
normList = new ArrayList();
coordIdxList = new ArrayList();
texIdxList = new ArrayList();
normIdxList = new ArrayList();
groups = new HashMap(50);
curGroup = "default";
sGroups = new HashMap(50);
curSgroup = null;
stripCounts = new ArrayList();
groupMaterials = new HashMap(50);
groupMaterials.put(curGroup, "default");
materials = new ObjectFileMaterials();
time = 0L;
if ((DEBUG & 8) != 0) {
time = System.currentTimeMillis();
}
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();
return makeScene();
} // End of load(Reader)
/**
* 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.
*/
@Override
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.
*/
@Override
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.
*/
@Override
public 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;
} // End of setBasePath
/**
* Return the path where files associated with this .obj file (like material
* files) are located.
*/
@Override
public String getBasePath() {
return basePath;
} // End of getBasePath
/**
* Set parameters for loading the model.
* Flags defined in Loader.java are ignored by the ObjectFile Loader
* 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 Loader (see above).
*/
@Override
public void setFlags(int flags) {
this.flags = flags;
if ((DEBUG & 4) != 0) System.out.println("Flags = " + flags);
} // End of setFlags
/**
* Get the parameters currently defined for loading the model.
* Flags defined in Loader.java are ignored by the ObjectFile Loader
* 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 Loader (see above).
*/
@Override
public int getFlags() {
return flags;
} // End of getFlags
} // End of class ObjectFile
// End of file ObjectFile.java
© 2015 - 2024 Weber Informatics LLC | Privacy Policy