JavaCAD
PolyObjImporter.java
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2008, 2014, Oracle and/or its affiliates.
3  * All rights reserved. Use is subject to license terms.
4  *
5  * This file is available and licensed under the following license:
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * - Redistributions of source code must retain the above copyright
12  * notice, this list of conditions and the following disclaimer.
13  * - Redistributions in binary form must reproduce the above copyright
14  * notice, this list of conditions and the following disclaimer in
15  * the documentation and/or other materials provided with the distribution.
16  * - Neither the name of Oracle Corporation nor the names of its
17  * contributors may be used to endorse or promote products derived
18  * from this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 package eu.mihosoft.vrl.v3d.ext.openjfx.importers.obj;
33 
34 
35 
36 import eu.mihosoft.vrl.v3d.ext.openjfx.importers.SmoothingGroups;
37 import eu.mihosoft.vrl.v3d.ext.openjfx.shape3d.PolygonMesh;
38 import eu.mihosoft.vrl.v3d.ext.openjfx.shape3d.PolygonMeshView;
39 import javafx.scene.paint.Color;
40 import javafx.scene.paint.Material;
41 import javafx.scene.paint.PhongMaterial;
42 
43 
44 import java.io.*;
45 import java.net.URL;
46 import java.util.*;
47 import java.util.logging.Level;
48 import java.util.logging.Logger;
49 
50 
51 // TODO: Auto-generated Javadoc
55 public class PolyObjImporter {
56 
63  private int vertexIndex(int vertexIndex) {
64  if (vertexIndex < 0) {
65  return vertexIndex + vertexes.size() / 3;
66  } else {
67  return vertexIndex - 1;
68  }
69  }
70 
77  private int uvIndex(int uvIndex) {
78  if (uvIndex < 0) {
79  return uvIndex + uvs.size() / 2;
80  } else {
81  return uvIndex - 1;
82  }
83  }
84 
91  private int normalIndex(int normalIndex) {
92  if (normalIndex < 0) {
93  return normalIndex + normals.size() / 3;
94  } else {
95  return normalIndex - 1;
96  }
97  }
98 
100  private static boolean debug = false;
101 
103  private static float scale = 1;
104 
106  private static boolean flatXZ = false;
107 
113  static void log(String string) {
114  if (debug) {
115  System.out.println(string);
116  }
117  }
118 
124  public Set<String> getMeshes() {
125  return meshes.keySet();
126  }
127 
129  private Map<String, PolygonMesh> meshes = new HashMap<>();
130 
132  private Map<String, Material> materials = new HashMap<>();
133 
135  private List<Map<String, Material>> materialLibrary = new ArrayList<>();
136 
138  private String objFilename;
139 
147  public PolyObjImporter(String filename) throws FileNotFoundException, IOException {
148  this.objFilename = filename;
149  log("Reading filename = " + filename);;
150  read(new URL(filename).openStream());
151  }
152 
159  public PolyObjImporter(InputStream inputStream) throws IOException {
160  read(inputStream);
161  }
162 
168  public PolygonMesh getMesh() {
169  return meshes.values().iterator().next();
170  }
171 
177  public Material getMaterial() {
178  return materials.values().iterator().next();
179  }
180 
187  public PolygonMesh getMesh(String key) {
188  return meshes.get(key);
189  }
190 
197  public Material getMaterial(String key) {
198  return materials.get(key);
199  }
200 
208  PolygonMeshView polygonMeshView = new PolygonMeshView();
209  polygonMeshView.setId(key);
210  polygonMeshView.setMaterial(materials.get(key));
211  polygonMeshView.setMesh(meshes.get(key));
212 // polygonMeshView.setCullFace(CullFace.NONE); TODO
213  return polygonMeshView;
214  }
215 
221  public static void setDebug(boolean debug) {
223  }
224 
230  public static void setScale(float scale) {
232  }
233 
236 
239 
241  private List<int[]> faces = new ArrayList<>();
242 
245 
248 
250  private List<int[]> faceNormals = new ArrayList<>();
251 
253  private Material material = new PhongMaterial(Color.WHITE);
254 
256  private int facesStart = 0;
257 
259  private int facesNormalStart = 0;
260 
262  private int smoothingGroupsStart = 0;
263 
270  private void read(InputStream inputStream) throws IOException {
271  BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
272  String line;
273  int currentSmoothGroup = 0;
274  String key = "default";
275  while ((line = br.readLine()) != null) {
276  try {
277  if (line.startsWith("g ") || line.equals("g")) {
278  addMesh(key);
279  key = line.length() > 2 ? line.substring(2) : "default";
280  log("key = " + key);
281  } else if (line.startsWith("v ")) {
282  String[] split = line.substring(2).trim().split(" +");
283  float x = Float.parseFloat(split[0]) * scale;
284  float y = Float.parseFloat(split[1]) * scale;
285  float z = Float.parseFloat(split[2]) * scale;
286 
287  // log("x = " + x + ", y = " + y + ", z = " + z);
288 
289  vertexes.add(x);
290  vertexes.add(y);
291  vertexes.add(z);
292 
293  if (flatXZ) {
294  uvs.add(x);
295  uvs.add(z);
296  }
297  } else if (line.startsWith("vt ")) {
298  String[] split = line.substring(3).trim().split(" +");
299  float u = split[0].trim().equalsIgnoreCase("nan") ? Float.NaN : Float.parseFloat(split[0]);
300  float v = split[1].trim().equalsIgnoreCase("nan") ? Float.NaN : Float.parseFloat(split[1]);
301 
302  // log("u = " + u + ", v = " + v);
303 
304  uvs.add(u);
305  uvs.add(1 - v);
306  } else if (line.startsWith("f ")) {
307  String[] split = line.substring(2).trim().split(" +");
308  int[] faceIndexes = new int[split.length*2];
309  int[] faceNormalIndexes = new int[split.length];
310  for (int i = 0; i < split.length; i++) {
311  String[] split2 = split[i].split("/");
312  faceIndexes[i*2] = vertexIndex(Integer.parseInt(split2[0]));
313  faceIndexes[(i*2)+1] = (split2.length > 1 && split2[1].length()>0) ? uvIndex(Integer.parseInt(split2[1])) : -1;
314  faceNormalIndexes[i] = (split2.length > 2 && split2[2].length()>0) ? normalIndex(Integer.parseInt(split2[2])) : -1;
315  }
316  faces.add(faceIndexes);
317  faceNormals.add(faceNormalIndexes);
318  smoothingGroups.add(currentSmoothGroup);
319  } else if (line.startsWith("s ")) {
320  if (line.substring(2).equals("off")) {
321  currentSmoothGroup = 0;
322  } else {
323  currentSmoothGroup = Integer.parseInt(line.substring(2));
324  }
325  } else if (line.startsWith("mtllib ")) {
326  // setting materials lib
327  String[] split = line.substring("mtllib ".length()).trim().split(" +");
328  for (String filename : split) {
329  MtlReader mtlReader = new MtlReader(filename, objFilename);
330  materialLibrary.add(mtlReader.getMaterials());
331  }
332  } else if (line.startsWith("usemtl ")) {
333  addMesh(key);
334  // setting new material for next mesh
335  String materialName = line.substring("usemtl ".length());
336  for (Map<String, Material> mm : materialLibrary) {
337  Material m = mm.get(materialName);
338  if (m != null) {
339  material = m;
340  break;
341  }
342  }
343  } else if (line.isEmpty() || line.startsWith("#")) {
344  // comments and empty lines are ignored
345  } else if (line.startsWith("vn ")) {
346  String[] split = line.substring(2).trim().split(" +");
347  float x = Float.parseFloat(split[0]);
348  float y = Float.parseFloat(split[1]);
349  float z = Float.parseFloat(split[2]);
350  normals.add(x);
351  normals.add(y);
352  normals.add(z);
353  } else {
354  log("line skipped: " + line);
355  }
356  } catch (Exception ex) {
357  Logger.getLogger(MtlReader.class.getName()).log(Level.SEVERE, "Failed to parse line:" + line, ex);
358  }
359  }
360  addMesh(key);
361 
362  log("Totally loaded " + (vertexes.size() / 3.) + " vertexes, "
363  + (uvs.size() / 2.) + " uvs, "
364  + (faces.size() / 6.) + " faces, "
365  + smoothingGroups.size() + " smoothing groups.");
366  }
367 
373  private void addMesh(String key) {
374  if (facesStart >= faces.size()) {
375  // we're only interested in faces
377  return;
378  }
379  Map<Integer, Integer> vertexMap = new HashMap<>(vertexes.size() / 2);
380  Map<Integer, Integer> uvMap = new HashMap<>(uvs.size() / 2);
381  Map<Integer, Integer> normalMap = new HashMap<>(normals.size() / 2);
382  FloatArrayList newVertexes = new FloatArrayList(vertexes.size() / 2);
383  FloatArrayList newUVs = new FloatArrayList(uvs.size() / 2);
384  FloatArrayList newNormals = new FloatArrayList(normals.size() / 2);
385  boolean useNormals = true;
386 
387  int[][] faceArrays = new int[faces.size()-facesStart][];
388  int[][] faceNormalArrays = new int[faceNormals.size()-facesNormalStart][];
389 
390  for (int i = facesStart; i < faces.size();i++) {
391  int[] faceIndexes = faces.get(i);
392  int[] faceNormalIndexes = faceNormals.get(i);
393  for (int j=0;j<faceIndexes.length;j+=2){
394  int vi = faceIndexes[j];
395  Integer nvi = vertexMap.get(vi);
396  if (nvi == null) {
397  nvi = newVertexes.size() / 3;
398  vertexMap.put(vi, nvi);
399  newVertexes.add(vertexes.get(vi * 3));
400  newVertexes.add(vertexes.get(vi * 3 + 1));
401  newVertexes.add(vertexes.get(vi * 3 + 2));
402  }
403  faceIndexes[j] = nvi;
404 // faces.set(i, nvi);
405  int uvi = faceIndexes[j+1];
406  Integer nuvi = uvMap.get(uvi);
407  if (nuvi == null) {
408  nuvi = newUVs.size() / 2;
409  uvMap.put(uvi, nuvi);
410  if (uvi >= 0) {
411  newUVs.add(uvs.get(uvi * 2));
412  newUVs.add(uvs.get(uvi * 2 + 1));
413  } else {
414  newUVs.add(0f);
415  newUVs.add(0f);
416  }
417  }
418  faceIndexes[j+1] = nuvi;
419 // faces.set(i + 1, nuvi);
420 
421  int ni = faceNormalIndexes[j/2];
422  Integer nni = normalMap.get(ni);
423  if (nni == null) {
424  nni = newNormals.size() / 3;
425  normalMap.put(ni, nni);
426  if (ni >= 0 && normals.size() >= (ni+1)*3) {
427  newNormals.add(normals.get(ni * 3));
428  newNormals.add(normals.get(ni * 3 + 1));
429  newNormals.add(normals.get(ni * 3 + 2));
430  } else {
431  useNormals = false;
432  newNormals.add(0f);
433  newNormals.add(0f);
434  newNormals.add(0f);
435  }
436  }
437  faceNormalIndexes[j/2] = nni;
438  }
439  faceArrays[i-facesStart] = faceIndexes;
440  faceNormalArrays[i-facesNormalStart] = faceNormalIndexes;
441  }
442 
443  PolygonMesh mesh = new PolygonMesh(
444  newVertexes.toFloatArray(),
445  newUVs.toFloatArray(),
446  faceArrays
447  );
448 
449  // Use normals if they are provided
450  if (useNormals) {
451  int[] smGroups = SmoothingGroups.calcSmoothGroups(faceArrays, faceNormalArrays, newNormals.toFloatArray());
452  mesh.getFaceSmoothingGroups().setAll(smGroups);
453  } else {
455  }
456 
457  if (debug) {
458  System.out.println("mesh.points = " + mesh.getPoints());
459  System.out.println("mesh.texCoords = " + mesh.getTexCoords());
460  System.out.println("mesh.faces: ");
461  for (int[] face: mesh.faces) {
462  System.out.println(" face:: "+Arrays.toString(face));
463  }
464  }
465 
466  int keyIndex = 2;
467  String keyBase = key;
468  while (meshes.get(key) != null) {
469  key = keyBase + " (" + keyIndex++ + ")";
470  }
471  meshes.put(key, mesh);
472  materials.put(key, material);
473 
474  log("Added mesh '" + key + "' of " + (mesh.getPoints().size()/3) + " vertexes, "
475  + (mesh.getTexCoords().size()/2) + " uvs, "
476  + mesh.faces.length + " faces, "
477  + 0 + " smoothing groups.");
478  log("material diffuse color = " + ((PhongMaterial) material).getDiffuseColor());
479  log("material diffuse map = " + ((PhongMaterial) material).getDiffuseMap());
480 
481  facesStart = faces.size();
482  facesNormalStart = faceNormals.size();
484  }
485 
491  public static void setFlatXZ(boolean flatXZ) {
493  }
494 }