Java3D specifies a Loader interface that can be implemented for any particular file format.
It also has a couple of builtin loaders for OBJ and LWO. 1. The built-in OBJ loader is pretty crappy and limited. I attempted to load 3 different models but only one loaded and displayed...the others generated an exception, eg. org.jogamp.java3d.loaders.ParsingErrorException: Unrecognized token, line 4 Doesn't say what the token is...nor how to skip it... 2. Does anyone know of other loaders that actually work? Especially for 3DS, OBJ, COB files...? Thanks. |
Administrator
|
Hello
Please can you try to load the faulty models with MeshLab or Blender? It would be nice if you could give us some URLs pointing to those files or if you could upload them somewhere. Do your COB files come from Caligari TrueSpace? When I have the necessary pieces of information, I'll be able to try to load your models with the most reliable loaders I know and maybe I'll create some sort of bridge to make them work with Java3D >= 1.7 (more future proof, I won't waste time with Java3D 1.6). Of course, if I find a quick fix for the build-in Java3D loader you use, I'll provide a patch to Phil and he'll review it. By the way, please post the full stack trace.
Julien Gouesse | Personal blog | Website
|
All the OBJ models I have can be imported and displayed in Blender.
A handful will load and display using the built-in OBJ loader. Most raise the exception: org.jogamp.java3d.loaders.ParsingErrorException: Unrecognized token, line <x> No other stack trace, so it appears the program terminates gracefully. This one works: https://free3d.com/3d-model/sh-60-seahawk-32184.html This one doesn't: https://free3d.com/3d-model/16g-motor-grader-20584.html If you want to check how well the built in OBJ loader, download some free models at: https://free3d.com/3d-models/ There are lots of them! Thanks for looking into this! You asked: "Do your COB files come from Caligari TrueSpace?" No idea. Got them from the web... |
Administrator
|
I get an HTTP error 504 when trying to download the first model.
Have you tried to load the second OBJ file after removing the 6 lines of comments? Actually, there are several formats called COB.
Julien Gouesse | Personal blog | Website
|
I got same error. Then I tried again, and it worked.
Try again. In any case, there are stacks of models there to try... https://free3d.com/3d-models/ |
Administrator
|
Please can you rename the MTL file and its mention in the OBJ file so that its name doesn't contain any space?
Julien Gouesse | Personal blog | Website
|
Tried that and it didn't make any difference...
Then I realised the "unrecognised token" error in the exception is the offending line in the OBJ file... It appears the "unrecognised token" is the "o" at the beginning of the line. So I commented out the line beginning with "o" and hey presto... The model displays! Note that I don't think the MTL file is being used. Indeed most of the models have no MTL file... |
I got the same problem some years ago with obj files.
I have changed two files in the package, Object file and ObjectFileMaterials. Now the loader works. These files are in a SourceForge project called Cliper. I suggest you dowload Cliper and use import with some files for test. If it works for you and you want to include the changed code in your own we could do : * you load the project Netbeans file and get the code. I tell you where changes are. * if you cannot use Netbeans I can send changed pieces of code. * I know that the best way is bringing the project to Git. I should do that, for sure. To load 3DS files I do the next steps : Concert 3ds to c3d using 3ds2java.exe. from Pieter Bos. 3ds file is first converted to c3d file and loaded. So, again, you can ask for changed code or wait for Cliper in Git I hope this helps. regards jf Pauly |
Administrator
|
Can you share your patch here?
Julien Gouesse | Personal blog | Website
|
* _________________________________________________________________________
* Changes to the ObjectFile class: * lines 422 > 427 added to create a default group to allow material change * readFile method has been changed : if an unknown token is encountered skip * it instead of crash * makeScene method : add code to allow changes to geometry and appearance _____________________________________________________ readGroup() * some files contains null groups. Create one to get material assigned if (st.sval.contains("null")) { curGroup = "group" + grpnum; grpnum++; } _________________________________________________________ readUsemtl() * make a group to get a separate shape3d. if (st.ttype == ObjectFileParser.TT_WORD) { // next two lines added : make a group to get a separate shape3d. jfp 02/2017 curGroup = "group" + grpnum; grpnum++; groupMaterials.put(curGroup, st.sval); // System.out.println(" Material Property " + st.sval + " assigned to group " + curGroup); } ______________________________________________________________ readFile() * Add o token (object) else if (st.sval.equals("o")) { // object name st.skipToNextLine(); * Add else statement to prevent crashes (unknown token) else { System.out.println(st.sval + " unsupported token in this release"); st.skipToNextLine(); //throw new ParsingErrorException( st.sval + " token unknown. Line " + st.lineno()); } ______________________________________________________________ makeScene() * Add capabilities for picking change and appearance change geometry * allow coordinates change // Put geometry into Shape3d Shape3D shape = new Shape3D(); // code change. jfp 25/02/2017. Add capabilities to the shape shape.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE); shape.setCapability(Shape3D.ALLOW_GEOMETRY_WRITE); shape.setCapability(Shape3D.ENABLE_PICK_REPORTING); shape.setCapability(Shape3D.ALLOW_PICKABLE_WRITE); //_______________________________________________ // issue 638; default to BY_COPY for consistency shape.setGeometry(gi.getGeometryArray(false, false, false)); // code change. jfp 25/02/2017. Add capabilities to the geometry GeometryArray geo = (GeometryArray) shape.getGeometry(); geo.setCapability(GeometryArray.ALLOW_COORDINATE_WRITE); geo.setCapability(GeometryArray.ALLOW_COORDINATE_READ); |
Code changed in ObjectFileMaterials class
___________________________________________ method assignMaterial() // Change code to always set specular and LightingEnable 03/02/2017 **** if (objFilMat.Ks != null) { // Set specular color. mat.setSpecularColor(objFilMat.Ks); } mat.setLightingEnable(true); // hard coded 03/02/2017 **** ____________________________________________________________________________ method readMapKd() * Change structure and remove code for files other than usual * change test for null texture image name public void readMapKd(ObjectFileParser objParse) { objParse.lowerCaseMode(false); // Filenames are case sensitive String tFile = null; // Get name of texture file (skip path) do { objParse.getToken(); if (objParse.ttype == ObjectFileParser.TT_WORD) { tFile = objParse.sval; } } while (objParse.ttype != ObjectFileParser.TT_EOL); objParse.lowerCaseMode(true); // some material files have lines with map_kd token but no filename. **** // the next test has been changed (null pointer exeception). **** if (tFile == null ) { objParse.skipToNextLine(); return; } TextureLoader t = null; if (fromUrl) { // inetrnet file try { t = new TextureLoader(new URL(basePath + tFile), "RGB", TextureLoader.GENERATE_MIPMAP, null); } catch (MalformedURLException ex) { Logger.getLogger(NewObjectFileMaterials.class.getName()).log(Level.SEVERE, null, ex); } } else { // local file String theFile = basePath + "\\" + tFile; File img = new File(theFile); if (img.exists()) { t = new TextureLoader(theFile, "RGB", TextureLoader.GENERATE_MIPMAP, null); } else { // wrong file name JOptionPane.showMessageDialog(this, theFile + " not found"); return; } } if (t == null) { objParse.skipToNextLine(); JOptionPane.showMessageDialog(this, "null textureLoader"); return; } Texture2D texture = (Texture2D) t.getTexture(); if (texture == null) { objParse.skipToNextLine(); JOptionPane.showMessageDialog(this, "null texture"); return; } cur.t = texture; } // End of readMapKd _________________________________________________________________________________ method readMaterialFile() * Add test to skip unknown tokens ...... else if (st.sval.equals("bump")) { st.skipToNextLine(); } else st.skipToNextLine(); // skip unknown token. Added 22/02/2017 _______________ |
Loading 3ds files with 3ds2java.exe. from Pieter Bos.
This is a two steps process: 1- 3ds file is converted to a c3d file. 2- cd3 is then loaded. The first step is made once only. Further loadings of the model are done from the c3d file. /** * 3ds files process* Theses files are first converted to c3d with 3ds2java.exe.* <br>The programù 3ds2java.exe must be in the same directory as the * 3ds file * <p> * To get texture files, they may be in a directory called textures, * or in the same directory. * <br>For instance : * <pre> * * /------------+ * | models | ________/------------+ * +------------+ | modelname | ______ modelname.3ds * +------------+ ______/------------+ * | textures | ___ xxx.JPG .... * +------------+ * * </pre> modelname.c3d is written in the same directory and can now be * imported */ Here is the code to process 3ds private void jMenuItem17ActionPerformed(java.awt.event.ActionEvent evt) { String path = tools.getFn(); if (path == null) { // user cancel return; } File file = new File(path); String stuName = file.getName(); // 3ds file name int point = stuName.lastIndexOf('.'); // point position String root = stuName.substring(0, point); // get left part //______Files controls_______________ // 3ds2java.exe is in /dist directory File file3ds = new File(univers.distFile + "\\" + "3ds2java.exe"); if (file3ds.exists() == false) { jTextArea1.setText("Please move 3ds2java.exe to /dist directory and try again"); return; } //_______________________________ // 3DS file is in /dist directory int slash = path.lastIndexOf("\\"); // right slash position String dir3ds = path.substring(0, slash); // get user file directory if (dir3ds.equals(univers.distFile) == false) { // file should be in same directory than 3ds2java.exe jTextArea1.setText("Please move " + stuName + " to /dist directory and try again"); return; } // end of controls_______________ String c3dName = root + ".c3d"; // build cd3 name String cdCmd = "cmd cd " + univers.distFile; // go to directory where 3ds2java should be String cmdStr = "3ds2java.exe " + stuName + " -o " + c3dName; // build 3ds2java input command Runtime runtime = Runtime.getRuntime(); // get runtime environment try { runtime.exec(cdCmd); // go directory Process process = runtime.exec(cmdStr); // run 3ds2java process.waitFor(); } catch (IOException | InterruptedException ex) { jTextArea1.setText("File error"); } try { loadC3d(univers.distFile + "\\" + c3dName); // 3DS to C3d convert will use 3ds2java output } catch (IOException ex) { Logger.getLogger(DrawPanel.class.getName()).log(Level.SEVERE, null, ex); } jTextArea1.setText(path + "loaded."); } ________________________________________________________________________________________________________ void loadC3d(String c3dName) throws IOException { Graf.graph2Tg(); // builds a graph to branch the model try { // call Loader3d class shown in next reply message Stud.readC3d(univers.animTg, c3dName); } catch (FileNotFoundException | IncorrectFormatException | NullPointerException e1) { messages(6); } univers.master.addChild(univers.pickGroup); jTextArea1.setText(c3dName + "loaded."); } _____________________________________________________________________________________________________________ |
This is the code which convert c3d to java3D. I cannot show only the code I added or changed . Sorry for my
lazyness about comments I should have made, for sure... /** * 3ds to java converter Copyright (C) 2003 Pieter Bos.___________________* <p>Changes (jfp 02/2017, 02/2019) * <p> * This library has been merged in Cliper with few changes. * <ul><li>MipMap option has been removed to be more simple because I had some * difficulties with textures. * <li>I use my own code to build Texture2D object * <li>Code has been added to scale models and allow shapes picking and * appearance to be changed * <li>readShape() has been changed to use GeometryArray.ALLOW_COORDINATE_WRITE * line 180 (03/02/2022) * <li>line 359 | access == F3DS added (url null if | access == F3DS not coded * (03/02/2022)) * </ul> * <p> * To get textures files, they may be in a directory called textures. * <br>For instance : * <pre> * /------------+ * | models | ________/------------+ * +------------+ | modelname | ______ modelname.c3d * +------------+ ______/------------+ * | textures | ___ xxx.JPG * +------------+ V * ..... * </pre> */ public class Loader3d extends javax.swing.JPanel { String textureDir; boolean moreData; BufferedReader file; LinkedList<Shape3D> shapeList; SomeTools tools; UniverseAndMaster univers; /** * Constructor * * @param universe simple universe for all classes */ public Loader3d(UniverseAndMaster universe) { univers = universe; shapeList = new LinkedList<>(); tools = new SomeTools(univers); } /** * open a c3d file. * <p> * C3D files are intermediate file format made by 3ds2java program which is * part of the original package. * * @param tg the tg to which shapes will be added * @param model Model name * @throws java.io.IOException pb in c3d */ public void readC3d(TransformGroup tg, String model) throws IOException { moreData = true; textureDir = model.substring(0, model.lastIndexOf('\\')) + "\\"; // search pictures in root try { // read file FileInputStream fileStream = new FileInputStream(model); InputStreamReader inputStream = new InputStreamReader(fileStream); file = new BufferedReader(inputStream, 50000); } catch (Exception e) { JOptionPane.showMessageDialog(this, "file " + model + " could not be read!", "Error", JOptionPane.ERROR_MESSAGE); return; } //______________________________ //read shapes until no more data //______________________________ while (moreData) { Shape3D shape = readShape(file); if (shape != null) { tg.addChild(shape); shapeList.push(shape); } else { moreData = false; } } resize(); //________ // cleanup| //________| try { file.close(); shapeList.clear(); } catch (IOException e) { JOptionPane.showMessageDialog(this, "file " + model + " could not be closed!", "Error", JOptionPane.ERROR_MESSAGE); } } /** * make a geometry and an appearance. * <p> * Compute model span to make it smaller (soyuz for instance) * * @param file to be processed * @return a shape3D */ public Shape3D readShape(BufferedReader file) { GeometryInfo geometry = new GeometryInfo(GeometryInfo.TRIANGLE_ARRAY); float[] coords, normals; Appearance appear; coords = readArray(file); // read coordinates_______ if (coords == null) { return null; } normals = readArray(file); // read normals___________ if (normals == null) { JOptionPane.showMessageDialog(this, "no normals!", "Error", JOptionPane.ERROR_MESSAGE); return null; } setTextureCoords(file, geometry); geometry.setNormals(normals); geometry.setCoordinates(coords); appear = readAppearance(file); GeometryArray result = geometry.getGeometryArray(); result.setCapability(GeometryArray.ALLOW_COORDINATE_WRITE); Shape3D shape = new Shape3D(result, appear); shape.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE); // to allow picking color hil$Ã _t GeometryArray geoArray = (GeometryArray) shape.getGeometry(); // Create Texture Coordinates if not already present if ((geoArray.getVertexFormat() & GeometryArray.TEXTURE_COORDINATE_2) == 0) { TexCoordGeneration tcg = new TexCoordGeneration(); appear.setTexCoordGeneration(tcg); } return shape; } /** * Center the object and resize. * <br>This code was based on objloader code from Sun. * <br>It has been changed in some ways. */ void resize() { Point3d center = new Point3d(); Point3d upPt = new Point3d(); Point3d loPt = new Point3d(); double spanBig = 0, span; int numShapes = shapeList.size(); float xShift = 0, yShift = 0, zShift = 0; // components for offset vector for (int i = 0; i < numShapes; i++) { // for all shapes get boundingSphere center and radius BoundingBox box = (BoundingBox) shapeList.get(i).getBounds(); box.getCenter(center); xShift = +(float) center.x; yShift = +(float) center.y; zShift = +(float) center.z; box.getLower(loPt); box.getUpper(upPt); span = loPt.distance(upPt); if (span > spanBig) { spanBig = span; } } xShift = xShift / numShapes; yShift = yShift / numShapes; zShift = zShift / numShapes; Vector3f offset = new Vector3f(-xShift, -yShift, -zShift); // vector to offset vertices coords for (int i = 0; i < numShapes; i++) { // apply scale and offset to vertices coords Shape3D shape = shapeList.get(i); // get a shape GeometryArray geoArray = (GeometryArray) shape.getGeometry(); // get it's Geometry int vtxCnt = geoArray.getVertexCount(); // vertices count Point3f[] coords3f = new Point3f[vtxCnt]; // array for vertices coordinates for (int j = 0; j < vtxCnt; j++) { // populate array coords3f[j] = new Point3f(); } geoArray.getCoordinates(0, coords3f); // put shape's geometry vertices in array for (int vtxCtr = 0; vtxCtr < vtxCnt; vtxCtr++) { // Change vertices coordinates and set in geoArray Point3f cur_vtx = coords3f[vtxCtr]; // get a vertex cur_vtx.add(cur_vtx, offset); // shift it cur_vtx.x /= spanBig; // scale it cur_vtx.y /= spanBig; cur_vtx.z /= spanBig; } geoArray.setCoordinates(0, coords3f); // update geometry with new values } // go to next shape //____________________________________________________________________ // find model shapes BoundingBox centers medium Y value to set graph | // transform and make model visible (sometimes too high) | //____________________________________________________________________| for (int i = 0; i < numShapes; i++) { // for all shapes get boundingSphere center and radius BoundingBox box = (BoundingBox) shapeList.get(i).getBounds(); box.getCenter(center); yShift = yShift + (float) center.y; } yShift = yShift / numShapes; tools.shiftCoord(Y, -yShift, 0); // 0 = add yShift to existing translation } // End of resize /** * read texture coords. * * @param file the file which is being read * @param geometry active geometry * @return number of Textures */ public int setTextureCoords(BufferedReader file, GeometryInfo geometry) { int numberOfTextures; try { String line = file.readLine(); numberOfTextures = Integer.parseInt(line); } catch (IOException | NumberFormatException e) { return 0; } //read all the textures__________________________________ if (numberOfTextures > 0) { geometry.setTextureCoordinateParams(numberOfTextures, 2); for (int i = 0; i < numberOfTextures; i++) { geometry.setTextureCoordinates(i, readArray(file)); } } return numberOfTextures; } /** * read lines in c3d file. * * @param file c3d file. * @return a float array */ public float[] readArray(BufferedReader file) { float[] values; try { String line = file.readLine(); if (line == null) { return null; } int numberOfValues = Integer.parseInt(line); values = new float[numberOfValues]; for (int i = 0; i < numberOfValues; i++) { line = file.readLine(); if (line == null) { return null; } values[i] = Float.parseFloat(line); } return values; } catch (IOException | NumberFormatException e) { return null; } } /** * get lights, textures,... * * @param file c3d file. * @return an appearance */ public Appearance readAppearance(BufferedReader file) { Appearance appear = new Appearance(); appear.setCapability(Appearance.ALLOW_TEXTURE_WRITE); appear.setCapability(Appearance.ALLOW_MATERIAL_WRITE); Color3f ambient, diffuse, specular; float shininess, transparency; int selfIllum; ambient = new Color3f(); diffuse = new Color3f(); specular = new Color3f(); shininess = 0; selfIllum = 0; transparency = 0; try { float[] temp = new float[3]; String line = file.readLine(); temp[0] = Float.parseFloat(line); line = file.readLine(); temp[1] = Float.parseFloat(line); line = file.readLine(); temp[2] = Float.parseFloat(line); ambient.set(temp); line = file.readLine(); selfIllum = Integer.parseInt(line); line = file.readLine(); temp[0] = Float.parseFloat(line); line = file.readLine(); temp[1] = Float.parseFloat(line); line = file.readLine(); temp[2] = Float.parseFloat(line); diffuse.set(temp); line = file.readLine(); temp[0] = Float.parseFloat(line); line = file.readLine(); temp[1] = Float.parseFloat(line); line = file.readLine(); temp[2] = Float.parseFloat(line); specular.set(temp); line = file.readLine(); shininess = Float.parseFloat(line); line = file.readLine(); transparency = Float.parseFloat(line); //if (numberOfTextures > 0) line = file.readLine(); if (!line.equals("")) { TextureLoader loader = null; //_____________________________________________ // local access. Build a loader with a filepath //_____________________________________________ line = textureDir + line; // path to texture image file File fic = new File(line); if (fic.exists()) { loader = new TextureLoader(line, this); } //________________ // build Texture2D //________________ if (loader != null) { ImageComponent2D image = loader.getImage(); Texture2D texture = new Texture2D(Texture2D.BASE_LEVEL, Texture2D.RGBA, image.getWidth(), image.getHeight()); texture.setCapability(Texture2D.ALLOW_IMAGE_WRITE); texture.setImage(0, image); if (appear.getCapability(Appearance.ALLOW_TEXTURE_WRITE) == false) { JOptionPane.showMessageDialog(this, "Appearance", "no capability to set texture", JOptionPane.ERROR_MESSAGE); return null; } appear.setTexture(texture); } } } catch (IOException | NumberFormatException | HeadlessException e) { JOptionPane.showMessageDialog(this, "no normals!", "Error", JOptionPane.ERROR_MESSAGE); } Material mat = new Material(ambient, new Color3f(selfIllum / 100, selfIllum / 100, selfIllum / 100), diffuse, specular, shininess); mat.setCapability(Material.ALLOW_COMPONENT_WRITE); appear.setMaterial(mat); if (transparency > 0) { TransparencyAttributes transpar = new TransparencyAttributes( TransparencyAttributes.BLENDED, transparency); appear.setTransparencyAttributes(transpar); } return appear; } |
Administrator
|
In reply to this post by jfpauly
By the way, have you ever considered contributing to Java3D itself? Thank you for the source code.
Julien Gouesse | Personal blog | Website
|
Free forum by Nabble | Edit this page |