3D loaders - 2 questions

classic Classic list List threaded Threaded
14 messages Options
Reply | Threaded
Open this post in threaded view
|

3D loaders - 2 questions

andrewk1972
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.
Reply | Threaded
Open this post in threaded view
|

Re: 3D loaders - 2 questions

gouessej
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
Reply | Threaded
Open this post in threaded view
|

Re: 3D loaders - 2 questions

andrewk1972
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...



Reply | Threaded
Open this post in threaded view
|

Re: 3D loaders - 2 questions

gouessej
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
Reply | Threaded
Open this post in threaded view
|

Re: 3D loaders - 2 questions

andrewk1972
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/
Reply | Threaded
Open this post in threaded view
|

Re: 3D loaders - 2 questions

gouessej
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
Reply | Threaded
Open this post in threaded view
|

Re: 3D loaders - 2 questions

andrewk1972
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...
Reply | Threaded
Open this post in threaded view
|

Re: 3D loaders - 2 questions

jfpauly
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
Reply | Threaded
Open this post in threaded view
|

Re: 3D loaders - 2 questions

gouessej
Administrator
Can you share your patch here?
Julien Gouesse | Personal blog | Website
Reply | Threaded
Open this post in threaded view
|

Re: 3D loaders - 2 questions

jfpauly
*  _________________________________________________________________________
 * 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);











Reply | Threaded
Open this post in threaded view
|

Re: 3D loaders - 2 questions

jfpauly
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
_______________
Reply | Threaded
Open this post in threaded view
|

Re: 3D loaders - 2 questions

jfpauly
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.");
    }
_____________________________________________________________________________________________________________
       
Reply | Threaded
Open this post in threaded view
|

Re: 3D loaders - 2 questions

jfpauly
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;
    }
Reply | Threaded
Open this post in threaded view
|

Re: 3D loaders - 2 questions

gouessej
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