JavaCAD
ObjImporter.java
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2010, 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 import eu.mihosoft.vrl.v3d.ObjFile;
35 import eu.mihosoft.vrl.v3d.ext.openjfx.importers.SmoothingGroups;
36 import java.io.BufferedReader;
37 import java.io.FileNotFoundException;
38 import java.io.IOException;
39 import java.io.InputStream;
40 import java.io.InputStreamReader;
41 import java.net.URL;
42 import java.util.ArrayList;
43 import java.util.Collection;
44 import java.util.HashMap;
45 import java.util.List;
46 import java.util.Map;
47 import java.util.Set;
48 import java.util.logging.Level;
49 import java.util.logging.Logger;
50 import javafx.scene.paint.Color;
51 import javafx.scene.paint.Material;
52 import javafx.scene.paint.PhongMaterial;
53 import javafx.scene.shape.CullFace;
54 import javafx.scene.shape.Mesh;
55 import javafx.scene.shape.MeshView;
56 import javafx.scene.shape.TriangleMesh;
57 
58 // TODO: Auto-generated Javadoc
62 public class ObjImporter {
63 
70  private int vertexIndex(int vertexIndex) {
71  if (vertexIndex < 0) {
72  return vertexIndex + vertexes.size() / 3;
73  } else {
74  return vertexIndex - 1;
75  }
76  }
77 
84  private int uvIndex(int uvIndex) {
85  if (uvIndex < 0) {
86  return uvIndex + uvs.size() / 2;
87  } else {
88  return uvIndex - 1;
89  }
90  }
91 
98  private int normalIndex(int normalIndex) {
99  if (normalIndex < 0) {
100  return normalIndex + normals.size() / 3;
101  } else {
102  return normalIndex - 1;
103  }
104  }
105 
107  private static boolean debug = false;
108 
110  private static float scale = 1;
111 
113  private static boolean flatXZ = false;
114 
120  static void log(String string) {
121  if (debug) {
122  System.out.println(string);
123  }
124  }
125 
131  public Set<String> getMeshes() {
132  return meshes.keySet();
133  }
134 
136  private final Map<String, TriangleMesh> meshes = new HashMap<>();
137 
139  private final Map<String, Material> materials = new HashMap<>();
140 
142  private final List<Map<String, Material>> materialLibrary = new ArrayList<>();
143 
145  private String objFileUrl;
146 
154  public ObjImporter(String objFileUrl) throws FileNotFoundException, IOException {
155  this.objFileUrl = objFileUrl;
156  log("Reading filename = " + objFileUrl);
157  read(new URL(objFileUrl).openStream());
158  }
159 
166  public ObjImporter(InputStream inputStream) throws IOException {
167  read(inputStream);
168  }
169 
176  public ObjImporter(ObjFile obj) throws IOException {
177  read(obj.getObjStream(), obj.getMtlStream());
178  }
179 
185  public TriangleMesh getMesh() {
186  return meshes.values().iterator().next();
187  }
188 
194  public Collection<TriangleMesh> getMeshCollection() {
195  return meshes.values();
196  }
197 
203  public Collection<Material> getMaterialCollection() {
204  return materials.values();
205  }
206 
212  public Material getMaterial() {
213  return materials.values().iterator().next();
214  }
215 
222  public TriangleMesh getMesh(String key) {
223  return meshes.get(key);
224  }
225 
232  public Material getMaterial(String key) {
233  return materials.get(key);
234  }
235 
242  public MeshView buildMeshView(String key) {
243  MeshView meshView = new MeshView();
244  meshView.setId(key);
245  meshView.setMaterial(materials.get(key));
246  meshView.setMesh(meshes.get(key));
247  meshView.setCullFace(CullFace.NONE);
248  return meshView;
249  }
250 
256  public static void setDebug(boolean debug) {
258  }
259 
265  public static void setScale(float scale) {
267  }
268 
271 
274 
277 
280 
283 
286 
288  private Material material = new PhongMaterial(Color.WHITE);
289 
291  private int facesStart = 0;
292 
294  private int facesNormalStart = 0;
295 
297  private int smoothingGroupsStart = 0;
298 
305  private void read(InputStream objInputStream) throws IOException {
306  read(objInputStream, null);
307  }
308 
316  private void read(InputStream objInputStream, InputStream mtlInputStream) throws IOException {
317  BufferedReader br = new BufferedReader(new InputStreamReader(objInputStream));
318  String line;
319  int currentSmoothGroup = 0;
320  String key = "default";
321  while ((line = br.readLine()) != null) {
322  try {
323  if (line.startsWith("g ") || line.equals("g")) {
324  addMesh(key);
325  key = line.length() > 2 ? line.substring(2) : "default";
326  log("key = " + key);
327  } else if (line.startsWith("v ")) {
328  String[] split = line.substring(2).trim().split(" +");
329  float x = Float.parseFloat(split[0]) * scale;
330  float y = Float.parseFloat(split[1]) * scale;
331  float z = Float.parseFloat(split[2]) * scale;
332 
333  // log("x = " + x + ", y = " + y + ", z = " + z);
334  vertexes.add(x);
335  vertexes.add(y);
336  vertexes.add(z);
337 
338  if (flatXZ) {
339  uvs.add(x);
340  uvs.add(z);
341  }
342  } else if (line.startsWith("vt ")) {
343  String[] split = line.substring(3).trim().split(" +");
344  float u = Float.parseFloat(split[0]);
345  float v = Float.parseFloat(split[1]);
346 
347  // log("u = " + u + ", v = " + v);
348  uvs.add(u);
349  uvs.add(1 - v);
350  } else if (line.startsWith("f ")) {
351  String[] split = line.substring(2).trim().split(" +");
352  int[][] data = new int[split.length][];
353  boolean uvProvided = true;
354  boolean normalProvided = true;
355  for (int i = 0; i < split.length; i++) {
356  String[] split2 = split[i].split("/");
357  if (split2.length < 2) {
358  uvProvided = false;
359  }
360  if (split2.length < 3) {
361  normalProvided = false;
362  }
363  data[i] = new int[split2.length];
364  for (int j = 0; j < split2.length; j++) {
365  if (split2[j].length() == 0) {
366  data[i][j] = 0;
367  if (j == 1) {
368  uvProvided = false;
369  }
370  if (j == 2) {
371  normalProvided = false;
372  }
373  } else {
374  data[i][j] = Integer.parseInt(split2[j]);
375  }
376  }
377  }
378  int v1 = vertexIndex(data[0][0]);
379  int uv1 = -1;
380  int n1 = -1;
381  if (uvProvided && !flatXZ) {
382  uv1 = uvIndex(data[0][1]);
383  if (uv1 < 0) {
384  uvProvided = false;
385  }
386  }
387  if (normalProvided) {
388  n1 = normalIndex(data[0][2]);
389  if (n1 < 0) {
390  normalProvided = false;
391  }
392  }
393  for (int i = 1; i < data.length - 1; i++) {
394  int v2 = vertexIndex(data[i][0]);
395  int v3 = vertexIndex(data[i + 1][0]);
396  int uv2 = -1;
397  int uv3 = -1;
398  int n2 = -1;
399  int n3 = -1;
400  if (uvProvided && !flatXZ) {
401  uv2 = uvIndex(data[i][1]);
402  uv3 = uvIndex(data[i + 1][1]);
403  }
404  if (normalProvided) {
405  n2 = normalIndex(data[i][2]);
406  n3 = normalIndex(data[i + 1][2]);
407  }
408 
409  // log("v1 = " + v1 + ", v2 = " + v2 + ", v3 = " + v3);
410  // log("uv1 = " + uv1 + ", uv2 = " + uv2 + ", uv3 = " + uv3);
411  faces.add(v1);
412  faces.add(uv1);
413  faces.add(v2);
414  faces.add(uv2);
415  faces.add(v3);
416  faces.add(uv3);
417  faceNormals.add(n1);
418  faceNormals.add(n2);
419  faceNormals.add(n3);
420 
421  smoothingGroups.add(currentSmoothGroup);
422  }
423  } else if (line.startsWith("s ")) {
424  if (line.substring(2).equals("off")) {
425  currentSmoothGroup = 0;
426  } else {
427  currentSmoothGroup = Integer.parseInt(line.substring(2));
428  }
429  } else if (line.startsWith("mtllib ")) {
430  // setting materials lib
431  String[] split = line.substring("mtllib ".length()).trim().split(" +");
432 
433  if (mtlInputStream == null) {
434  for (String filename : split) {
435 
436  MtlReader mtlReader = new MtlReader(filename, objFileUrl);
437 
438  materialLibrary.add(mtlReader.getMaterials());
439  }
440  } else {
441  if (split.length > 1) {
442  log("WARNING: more than one mtllib not supported if reading from streams! Using only one mtllib.");
443  MtlReader mtlReader = new MtlReader(mtlInputStream);
444  materialLibrary.add(mtlReader.getMaterials());
445  }
446  }
447  } else if (line.startsWith("usemtl ")) {
448  addMesh(key);
449  // setting new material for next mesh
450  String materialName = line.substring("usemtl ".length());
451  for (Map<String, Material> mm : materialLibrary) {
452  Material m = mm.get(materialName);
453  if (m != null) {
454  material = m;
455  break;
456  }
457  }
458  } else if (line.isEmpty() || line.startsWith("#")) {
459  // comments and empty lines are ignored
460  } else if (line.startsWith("vn ")) {
461  String[] split = line.substring(2).trim().split(" +");
462  float x = Float.parseFloat(split[0]);
463  float y = Float.parseFloat(split[1]);
464  float z = Float.parseFloat(split[2]);
465  normals.add(x);
466  normals.add(y);
467  normals.add(z);
468  } else {
469  log("line skipped: " + line);
470  }
471  } catch (Exception ex) {
472  Logger.getLogger(MtlReader.class.getName()).log(Level.SEVERE, "Failed to parse line:" + line, ex);
473  }
474  }
475  addMesh(key);
476 
477  log(
478  "Totally loaded " + (vertexes.size() / 3.) + " vertexes, "
479  + (uvs.size() / 2.) + " uvs, "
480  + (faces.size() / 6.) + " faces, "
481  + smoothingGroups.size() + " smoothing groups.");
482  }
483 
489  private void addMesh(String key) {
490  if (facesStart >= faces.size()) {
491  // we're only interested in faces
493  return;
494  }
495  Map<Integer, Integer> vertexMap = new HashMap<>(vertexes.size() / 2);
496  Map<Integer, Integer> uvMap = new HashMap<>(uvs.size() / 2);
497  Map<Integer, Integer> normalMap = new HashMap<>(normals.size() / 2);
498  FloatArrayList newVertexes = new FloatArrayList(vertexes.size() / 2);
499  FloatArrayList newUVs = new FloatArrayList(uvs.size() / 2);
500  FloatArrayList newNormals = new FloatArrayList(normals.size() / 2);
501  boolean useNormals = true;
502 
503  for (int i = facesStart; i < faces.size(); i += 2) {
504  int vi = faces.get(i);
505  Integer nvi = vertexMap.get(vi);
506  if (nvi == null) {
507  nvi = newVertexes.size() / 3;
508  vertexMap.put(vi, nvi);
509  newVertexes.add(vertexes.get(vi * 3));
510  newVertexes.add(vertexes.get(vi * 3 + 1));
511  newVertexes.add(vertexes.get(vi * 3 + 2));
512  }
513  faces.set(i, nvi);
514 
515  int uvi = faces.get(i + 1);
516  Integer nuvi = uvMap.get(uvi);
517  if (nuvi == null) {
518  nuvi = newUVs.size() / 2;
519  uvMap.put(uvi, nuvi);
520  if (uvi >= 0) {
521  newUVs.add(uvs.get(uvi * 2));
522  newUVs.add(uvs.get(uvi * 2 + 1));
523  } else {
524  newUVs.add(0f);
525  newUVs.add(0f);
526  }
527  }
528  faces.set(i + 1, nuvi);
529 
530  if (useNormals) {
531  int ni = faceNormals.get(i / 2);
532  Integer nni = normalMap.get(ni);
533  if (nni == null) {
534  nni = newNormals.size() / 3;
535  normalMap.put(ni, nni);
536  if (ni >= 0 && normals.size() >= (ni + 1) * 3) {
537  newNormals.add(normals.get(ni * 3));
538  newNormals.add(normals.get(ni * 3 + 1));
539  newNormals.add(normals.get(ni * 3 + 2));
540  } else {
541  useNormals = false;
542  newNormals.add(0f);
543  newNormals.add(0f);
544  newNormals.add(0f);
545  }
546  }
547  faceNormals.set(i / 2, nni);
548  }
549  }
550 
551  TriangleMesh mesh = new TriangleMesh();
552  mesh.getPoints().setAll(newVertexes.toFloatArray());
553  mesh.getTexCoords().setAll(newUVs.toFloatArray());
554  mesh.getFaces().setAll(((IntegerArrayList) faces.subList(facesStart, faces.size())).toIntArray());
555 
556  // Use normals if they are provided
557  if (useNormals) {
558  int[] newFaces = ((IntegerArrayList) faces.subList(facesStart, faces.size())).toIntArray();
559  int[] newFaceNormals = ((IntegerArrayList) faceNormals.subList(facesNormalStart, faceNormals.size())).toIntArray();
560  int[] smGroups = SmoothingGroups.calcSmoothGroups(mesh, newFaces, newFaceNormals, newNormals.toFloatArray());
561  mesh.getFaceSmoothingGroups().setAll(smGroups);
562  } else {
563  mesh.getFaceSmoothingGroups().setAll(((IntegerArrayList) smoothingGroups.subList(smoothingGroupsStart, smoothingGroups.size())).toIntArray());
564  }
565 
566  int keyIndex = 2;
567  String keyBase = key;
568  while (meshes.get(key) != null) {
569  key = keyBase + " (" + keyIndex++ + ")";
570  }
571  meshes.put(key, mesh);
572  materials.put(key, material);
573 
574  log(
575  "Added mesh '" + key + "' of " + mesh.getPoints().size() / mesh.getPointElementSize() + " vertexes, "
576  + mesh.getTexCoords().size() / mesh.getTexCoordElementSize() + " uvs, "
577  + mesh.getFaces().size() / mesh.getFaceElementSize() + " faces, "
578  + mesh.getFaceSmoothingGroups().size() + " smoothing groups.");
579  log("material diffuse color = " + ((PhongMaterial) material).getDiffuseColor());
580  log("material diffuse map = " + ((PhongMaterial) material).getDiffuseMap());
581 
582  facesStart = faces.size();
585  }
586 
592  public static void setFlatXZ(boolean flatXZ) {
594  }
595 }
void read(InputStream objInputStream, InputStream mtlInputStream)
final List< Map< String, Material > > materialLibrary