1- /* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
2-
3- /*
4- Part of the Processing project - http://processing.org
5-
6- Copyright (c) 2012-16 The Processing Foundation
7-
8- This library is free software; you can redistribute it and/or
9- modify it under the terms of the GNU Lesser General Public
10- License version 2.1 as published by the Free Software Foundation.
11-
12- This library is distributed in the hope that it will be useful,
13- but WITHOUT ANY WARRANTY; without even the implied warranty of
14- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15- Lesser General Public License for more details.
16-
17- You should have received a copy of the GNU Lesser General
18- Public License along with this library; if not, write to the
19- Free Software Foundation, Inc., 59 Temple Place, Suite 330,
20- Boston, MA 02111-1307 USA
21- */
22-
231package processing .core ;
242
253import java .io .BufferedReader ;
4+ import java .io .File ;
265import java .util .ArrayList ;
27- import java .util .Hashtable ;
6+ import java .util .HashMap ;
7+ import java .util .Map ;
288
299/**
10+ * This class is not part of the Processing API and should not be used
11+ * directly. Instead, use loadShape() and methods like it, which will make
12+ * use of this class. Using this class directly will cause your code to break
13+ * when combined with future versions of Processing.
14+ * <p>
3015 * OBJ loading implemented using code from Saito's OBJLoader library:
3116 * http://code.google.com/p/saitoobjloader/
3217 * and OBJReader from Ahmet Kizilay
@@ -39,17 +24,21 @@ public class PShapeOBJ extends PShape {
3924 * Initializes a new OBJ Object with the given filename.
4025 */
4126 public PShapeOBJ (PApplet parent , String filename ) {
42- this (parent , parent .createReader (filename ));
27+ this (parent , parent .createReader (filename ), getBasePath ( parent , filename ) );
4328 }
4429
45-
4630 public PShapeOBJ (PApplet parent , BufferedReader reader ) {
31+ this (parent , reader , "" );
32+ }
33+
34+ public PShapeOBJ (PApplet parent , BufferedReader reader , String basePath ) {
4735 ArrayList <OBJFace > faces = new ArrayList <OBJFace >();
4836 ArrayList <OBJMaterial > materials = new ArrayList <OBJMaterial >();
4937 ArrayList <PVector > coords = new ArrayList <PVector >();
5038 ArrayList <PVector > normals = new ArrayList <PVector >();
5139 ArrayList <PVector > texcoords = new ArrayList <PVector >();
52- parseOBJ (parent , reader , faces , materials , coords , normals , texcoords );
40+ parseOBJ (parent , basePath , reader ,
41+ faces , materials , coords , normals , texcoords );
5342
5443 // The OBJ geometry is stored with each face in a separate child shape.
5544 parent = null ;
@@ -88,10 +77,10 @@ protected PShapeOBJ(OBJFace face, OBJMaterial mtl,
8877 vertexCount = face .vertIdx .size ();
8978 vertices = new float [vertexCount ][12 ];
9079 for (int j = 0 ; j < face .vertIdx .size (); j ++){
91- int vertIdx , normIdx ;
92- PVector vert , norms ;
80+ int vertIdx , normIdx , texIdx ;
81+ PVector vert , norms , tex ;
9382
94- vert = norms = null ;
83+ vert = norms = tex = null ;
9584
9685 vertIdx = face .vertIdx .get (j ).intValue () - 1 ;
9786 vert = coords .get (vertIdx );
@@ -103,6 +92,13 @@ protected PShapeOBJ(OBJFace face, OBJMaterial mtl,
10392 }
10493 }
10594
95+ if (j < face .texIdx .size ()) {
96+ texIdx = face .texIdx .get (j ).intValue () - 1 ;
97+ if (-1 < texIdx ) {
98+ tex = texcoords .get (texIdx );
99+ }
100+ }
101+
106102 vertices [j ][X ] = vert .x ;
107103 vertices [j ][Y ] = vert .y ;
108104 vertices [j ][Z ] = vert .z ;
@@ -118,23 +114,13 @@ protected PShapeOBJ(OBJFace face, OBJMaterial mtl,
118114 vertices [j ][PGraphics .NZ ] = norms .z ;
119115 }
120116
121- if (mtl != null && mtl .kdMap != null ) {
122- // This face is textured.
123- int texIdx ;
124- PVector tex = null ;
125-
126- if (j < face .texIdx .size ()) {
127- texIdx = face .texIdx .get (j ).intValue () - 1 ;
128- if (-1 < texIdx ) {
129- tex = texcoords .get (texIdx );
130- }
131- }
117+ if (tex != null ) {
118+ vertices [j ][PGraphics .U ] = tex .x ;
119+ vertices [j ][PGraphics .V ] = tex .y ;
120+ }
132121
122+ if (mtl != null && mtl .kdMap != null ) {
133123 image = mtl .kdMap ;
134- if (tex != null ) {
135- vertices [j ][PGraphics .U ] = tex .x ;
136- vertices [j ][PGraphics .V ] = tex .y ;
137- }
138124 }
139125 }
140126 }
@@ -164,14 +150,14 @@ protected void addChildren(ArrayList<OBJFace> faces,
164150 }
165151
166152
167- static protected void parseOBJ (PApplet parent ,
153+ static protected void parseOBJ (PApplet parent , String path ,
168154 BufferedReader reader ,
169155 ArrayList <OBJFace > faces ,
170156 ArrayList <OBJMaterial > materials ,
171157 ArrayList <PVector > coords ,
172158 ArrayList <PVector > normals ,
173159 ArrayList <PVector > texcoords ) {
174- Hashtable <String , Integer > mtlTable = new Hashtable <String , Integer >();
160+ Map <String , Integer > mtlTable = new HashMap <String , Integer >();
175161 int mtlIdxCur = -1 ;
176162 boolean readv , readvn , readvt ;
177163 try {
@@ -235,11 +221,16 @@ static protected void parseOBJ(PApplet parent,
235221 // Object name is ignored, for now.
236222 } else if (parts [0 ].equals ("mtllib" )) {
237223 if (parts [1 ] != null ) {
238- BufferedReader mreader = parent .createReader (parts [1 ]);
224+ String fn = parts [1 ];
225+ if (fn .indexOf (File .separator ) == -1 && !path .equals ("" )) {
226+ // Relative file name, adding the base path.
227+ fn = path + File .separator + fn ;
228+ }
229+ BufferedReader mreader = parent .createReader (fn );
239230 if (mreader != null ) {
240- parseMTL (parent , mreader , materials , mtlTable );
231+ parseMTL (parent , fn , path , mreader , materials , mtlTable );
232+ mreader .close ();
241233 }
242- mreader .close ();
243234 }
244235 } else if (parts [0 ].equals ("g" )) {
245236 gname = 1 < parts .length ? parts [1 ] : "" ;
@@ -326,10 +317,10 @@ static protected void parseOBJ(PApplet parent,
326317 }
327318
328319
329- static protected void parseMTL (PApplet parent ,
320+ static protected void parseMTL (PApplet parent , String mtlfn , String path ,
330321 BufferedReader reader ,
331322 ArrayList <OBJMaterial > materials ,
332- Hashtable <String , Integer > materialsHash ) {
323+ Map <String , Integer > materialsHash ) {
333324 try {
334325 String line ;
335326 OBJMaterial currentMtl = null ;
@@ -342,35 +333,53 @@ static protected void parseMTL(PApplet parent,
342333 if (parts [0 ].equals ("newmtl" )) {
343334 // Starting new material.
344335 String mtlname = parts [1 ];
345- currentMtl = new OBJMaterial (mtlname );
346- materialsHash .put (mtlname , new Integer (materials .size ()));
347- materials .add (currentMtl );
348- } else if (parts [0 ].equals ("map_Kd" ) && parts .length > 1 ) {
349- // Loading texture map.
350- String texname = parts [1 ];
351- currentMtl .kdMap = parent .loadImage (texname );
352- } else if (parts [0 ].equals ("Ka" ) && parts .length > 3 ) {
353- // The ambient color of the material
354- currentMtl .ka .x = Float .valueOf (parts [1 ]).floatValue ();
355- currentMtl .ka .y = Float .valueOf (parts [2 ]).floatValue ();
356- currentMtl .ka .z = Float .valueOf (parts [3 ]).floatValue ();
357- } else if (parts [0 ].equals ("Kd" ) && parts .length > 3 ) {
358- // The diffuse color of the material
359- currentMtl .kd .x = Float .valueOf (parts [1 ]).floatValue ();
360- currentMtl .kd .y = Float .valueOf (parts [2 ]).floatValue ();
361- currentMtl .kd .z = Float .valueOf (parts [3 ]).floatValue ();
362- } else if (parts [0 ].equals ("Ks" ) && parts .length > 3 ) {
363- // The specular color weighted by the specular coefficient
364- currentMtl .ks .x = Float .valueOf (parts [1 ]).floatValue ();
365- currentMtl .ks .y = Float .valueOf (parts [2 ]).floatValue ();
366- currentMtl .ks .z = Float .valueOf (parts [3 ]).floatValue ();
367- } else if ((parts [0 ].equals ("d" ) ||
368- parts [0 ].equals ("Tr" )) && parts .length > 1 ) {
369- // Reading the alpha transparency.
370- currentMtl .d = Float .valueOf (parts [1 ]).floatValue ();
371- } else if (parts [0 ].equals ("Ns" ) && parts .length > 1 ) {
372- // The specular component of the Phong shading model
373- currentMtl .ns = Float .valueOf (parts [1 ]).floatValue ();
336+ currentMtl = addMaterial (mtlname , materials , materialsHash );
337+ } else {
338+ if (currentMtl == null ) {
339+ currentMtl = addMaterial ("material" + materials .size (),
340+ materials , materialsHash );
341+ }
342+ if (parts [0 ].equals ("map_Kd" ) && parts .length > 1 ) {
343+ // Loading texture map.
344+ String texname = parts [1 ];
345+ if (texname .indexOf (File .separator ) == -1 && !path .equals ("" )) {
346+ // Relative file name, adding the base path.
347+ texname = path + File .separator + texname ;
348+ }
349+
350+ File file = new File (parent .dataPath (texname ));
351+ if (file .exists ()) {
352+ currentMtl .kdMap = parent .loadImage (texname );
353+ } else {
354+ System .err .println ("The texture map \" " + texname + "\" " +
355+ "in the materials definition file \" " + mtlfn + "\" " +
356+ "is missing or inaccessible, make sure " +
357+ "the URL is valid or that the file has been " +
358+ "added to your sketch and is readable." );
359+ }
360+ } else if (parts [0 ].equals ("Ka" ) && parts .length > 3 ) {
361+ // The ambient color of the material
362+ currentMtl .ka .x = Float .valueOf (parts [1 ]).floatValue ();
363+ currentMtl .ka .y = Float .valueOf (parts [2 ]).floatValue ();
364+ currentMtl .ka .z = Float .valueOf (parts [3 ]).floatValue ();
365+ } else if (parts [0 ].equals ("Kd" ) && parts .length > 3 ) {
366+ // The diffuse color of the material
367+ currentMtl .kd .x = Float .valueOf (parts [1 ]).floatValue ();
368+ currentMtl .kd .y = Float .valueOf (parts [2 ]).floatValue ();
369+ currentMtl .kd .z = Float .valueOf (parts [3 ]).floatValue ();
370+ } else if (parts [0 ].equals ("Ks" ) && parts .length > 3 ) {
371+ // The specular color weighted by the specular coefficient
372+ currentMtl .ks .x = Float .valueOf (parts [1 ]).floatValue ();
373+ currentMtl .ks .y = Float .valueOf (parts [2 ]).floatValue ();
374+ currentMtl .ks .z = Float .valueOf (parts [3 ]).floatValue ();
375+ } else if ((parts [0 ].equals ("d" ) ||
376+ parts [0 ].equals ("Tr" )) && parts .length > 1 ) {
377+ // Reading the alpha transparency.
378+ currentMtl .d = Float .valueOf (parts [1 ]).floatValue ();
379+ } else if (parts [0 ].equals ("Ns" ) && parts .length > 1 ) {
380+ // The specular component of the Phong shading model
381+ currentMtl .ns = Float .valueOf (parts [1 ]).floatValue ();
382+ }
374383 }
375384 }
376385 }
@@ -379,6 +388,14 @@ static protected void parseMTL(PApplet parent,
379388 }
380389 }
381390
391+ protected static OBJMaterial addMaterial (String mtlname ,
392+ ArrayList <OBJMaterial > materials ,
393+ Map <String , Integer > materialsHash ) {
394+ OBJMaterial currentMtl = new OBJMaterial (mtlname );
395+ materialsHash .put (mtlname , Integer .valueOf (materials .size ()));
396+ materials .add (currentMtl );
397+ return currentMtl ;
398+ }
382399
383400 protected static int rgbaValue (PVector color ) {
384401 return 0xFF000000 | ((int )(color .x * 255 ) << 16 ) |
@@ -413,6 +430,18 @@ static protected class OBJFace {
413430 }
414431
415432
433+ static protected String getBasePath (PApplet parent , String filename ) {
434+ // Obtaining the path
435+ File file = new File (parent .dataPath (filename ));
436+ if (!file .exists ()) {
437+ file = parent .sketchFile (filename );
438+ }
439+ String absolutePath = file .getAbsolutePath ();
440+ return absolutePath .substring (0 ,
441+ absolutePath .lastIndexOf (File .separator ));
442+ }
443+
444+
416445 // Stores a material defined in an MTL file.
417446 static protected class OBJMaterial {
418447 String name ;
0 commit comments