All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.scijava.java3d.loaders.objectfile.ObjectFile Maven / Gradle / Ivy

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