STL import

Hello everyone!

I am developing an app for my 3D printer and I need to display and manipulate stl objects. I am trying jMonkey now. So here are some questions :

Is there a different, better way to import STL files than to directly manipulate mesh vertex buffers (i got the triangle data : vertex and normals)?

And if there isn’t - why this does not work (my triangle does not render)?

[java]
// Don’t concern yourself with this - this works well
STLInterface s = new STLInterface();
s.loadSTLfile(“resources/cube.stl”);
FilterPostProcessor fpp=new FilterPostProcessor(assetManager);
BloomFilter bloom = new BloomFilter(BloomFilter.GlowMode.Objects);
fpp.addFilter(bloom);
viewPort.addProcessor(fpp);

	ArrayList<Triangle> tr = new ArrayList<Triangle>();
	Vector3f[] normals = new Vector3f[3];
	Vector3f[] verti = new Vector3f[3];

	Vector2f[] texCoord = new Vector2f[3];
	int[] index = new int[3];

	ArrayList<Geometry> geoms = new ArrayList<Geometry>();
	
	int i = 0;
	int k = 0;
	int z = 0;
	for(Facet x : s.facets)
	{

// Here i load the triangle data to vertex buffers
Triangle t = new Triangle(x.v[0],x.v[1],x.v[2]);
normals[0] = x.n;
normals[1] = x.n;
normals[2] = x.n;

		tr.add(t);
		
		index[0] =  2;
		index[1] =  1;
		index[2] =  0;
		
		
		verti[0] = x.v[0];
		verti[1] = x.v[1];
		verti[2] = x.v[2];
		
            // I have no idea what i am doing here - helpful advice/link explaining uv coordinates and texture coord buffer is greatly needed 
	texCoord[0] = new Vector2f(1,0);
		texCoord[1] = new Vector2f(0,1);
		texCoord[2] = new Vector2f(1,1);
		

    	Mesh mymesh = new Mesh();
    	//mymesh.setMode(Mesh.Mode.Triangles);
		mymesh.setBuffer(Type.Position, 3,BufferUtils.createFloatBuffer(verti));
    	mymesh.setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(texCoord));

		mymesh.setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(normals));
    	mymesh.setBuffer(Type.Index, 3, BufferUtils.createIntBuffer(index));

    	Geometry geom = new Geometry("Triangle", mymesh);
        geoms.add(geom);
        break;

// I just add one triangle for testing
}

    Mesh outMesh = new Mesh();

    GeometryBatchFactory.mergeGeometries(geoms, outMesh);
	
    Geometry geom = new Geometry("", outMesh);// create cube geometry from the shape

    TangentBinormalGenerator.generate(outMesh);

// This is a sphere from examples to test if the material renders - and it does
Sphere rock = new Sphere(32,32, 2f);
Geometry shiny_rock = new Geometry(“Shiny rock”, rock);
//rock.setTextureMode(Sphere.TextureMode.Projected); // better quality on spheres
TangentBinormalGenerator.generate(rock); // for lighting effect
Material mat_lit = new Material(assetManager, “Common/MatDefs/Light/Lighting.j3md”);
mat_lit.setTexture(“DiffuseMap”, assetManager.loadTexture(“Textures/Terrain/Pond/Pond.jpg”));
mat_lit.setTexture(“NormalMap”, assetManager.loadTexture(“Textures/Terrain/Pond/Pond_normal.png”));
mat_lit.setBoolean(“UseMaterialColors”,true);
mat_lit.setColor(“Specular”,ColorRGBA.White);
mat_lit.setColor(“Diffuse”,ColorRGBA.White);
mat_lit.setFloat(“Shininess”, 5f); // [1,128]
shiny_rock.setMaterial(mat_lit);
shiny_rock.setLocalTranslation(10,10,10); // Move it a bit
shiny_rock.rotate(1.6f, 0, 0); // Rotate it a bit
rootNode.attachChild(shiny_rock);

    geom.setMaterial(mat_lit);
    geom.move(-20f, -20f, -20f);

    rootNode.attachChild(geom);              



    
    
    
    PointLight lamp_light = new PointLight();
    lamp_light.setColor(ColorRGBA.Yellow);
    lamp_light.setRadius(40000f);
    lamp_light.setPosition(new Vector3f(-1f,-1f,1f));
    rootNode.addLight(lamp_light);
    
    DirectionalLight sun = new DirectionalLight();
    sun.setDirection(new Vector3f(0,-20,-20).normalizeLocal());
    sun.setColor(ColorRGBA.Blue);
    rootNode.addLight(sun);
     
     [/java]  

If you help me and ever visit middle Europe i am buying you a keg of beer.

Piotr Lipert

cool, the ability to output to .STL from a standalone jME app would be pretty sweet.

You need to add
[java]mesh.updateBound();
[/java]

after the setBuffer calls

Unfortunately that didn’t help. Does someone have knowledge about how TexCoord buffer should look like?

http://hub.jmonkeyengine.org/forum/topic/custom-mesh-hex-and-texture-problem/

Ok, that really hit the spot. Thank you very much wezrule! Seems like blender export zeroes the normals. If someone wants working STL import code (with batching unfortunately) write me an email at piotr.lipert(at)gmaildotcom

This is my implementation of STL importer. It also uses java-ml library for points filtering.

package my.app;

import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.util.BufferUtils;

import net.sf.javaml.core.kdtree.KDTree;

public class StlImporter {
    
    static Pattern FACET_PAT = Pattern.compile("\\s*facet\\s+normal\\s+(.*?)\\s+(.*?)\\s+(.*)");
    static Pattern VERTEX_PAT = Pattern.compile("\\s*vertex\\s+(.*?)\\s+(.*?)\\s+(.*)");
    
    public static Mesh loadStl(String file) throws Exception {
        
        
        List<Vector3f> points = new ArrayList<>();
        List<Vector3f> normals = new ArrayList<>();

        try (BufferedReader br = new BufferedReader(new FileReader(file))) {
            String line;
            while ((line = br.readLine()) != null) {
                line = line.trim();
                Matcher facetMat = FACET_PAT.matcher(line);
                if (facetMat.find()) {
                    Vector3f norm = new Vector3f(Float.parseFloat(facetMat.group(1)), Float.parseFloat(facetMat.group(2)), Float.parseFloat(facetMat.group(3)) );
                    if (!Vector3f.isValidVector(norm) || norm.length() == 0) {
                        continue;
                    }
                    normals.add(norm);
                    
                    int i = 0;
                    while ((line = br.readLine()) != null) {
                        line = line.trim();
                        Matcher vertexMat = VERTEX_PAT.matcher(line);
                        if (vertexMat.find()) {
                            points.add(new Vector3f(
                                    Float.parseFloat(vertexMat.group(1)), 
                                    Float.parseFloat(vertexMat.group(2)), 
                                    Float.parseFloat(vertexMat.group(3)) 
                                    ));
                            i++;
                        } else if (i > 0) {
                            assert i == 3;
                            break;
                        }
                    }
                }
            }
        }
        
        assert points.size() % 3 == 0;
        assert points.size() / 3 == normals.size();

        List<Vector3f> pointsRefined = new ArrayList<>();
        int indexesSize = 0;
        int[] indexes = new int[1000 * 1024];
        
        KDTree kd = new KDTree(3);
        double[] key = new double[3];
        for (int i=0; i < points.size(); i++) {
            if (i < 1) {
                Vector3f pt = points.get(i);
                key[0] = pt.x;
                key[1] = pt.y;
                key[2] = pt.z;
                kd.insert(key, pointsRefined.size());
                indexes[indexesSize++] = pointsRefined.size();
                pointsRefined.add(pt);
            } else {
                Vector3f pt = points.get(i);
                key[0] = pt.x;
                key[1] = pt.y;
                key[2] = pt.z;
                int inx = (Integer) kd.nearest(key);
                Vector3f ptInx = pointsRefined.get(inx);
                if (pt.distanceSquared(ptInx) > 0.00001f) {
                    kd.insert(key, pointsRefined.size());
                    indexes[indexesSize++] = pointsRefined.size();
                    pointsRefined.add(pt);
                } else {
                    indexes[indexesSize++] = inx;
                }
            }
        }
        
        assert indexesSize == points.size();
        
        Map<Integer, List<Integer>> v2trMap = new HashMap<>();
        for (int i=0; i<indexesSize; i++) {
            List<Integer> triangles = v2trMap.get(indexes[i]);
            if (triangles == null) {
                triangles = new ArrayList<>();
                v2trMap.put(indexes[i], triangles);
            }
            triangles.add(i/3);
        }
        Vector3f[] vertexNormals = new Vector3f[pointsRefined.size()];
        for (int i=0; i < pointsRefined.size(); i++) {
            List<Integer> triangles = v2trMap.get(i);
            Vector3f avg = new Vector3f();
            for (Integer tinx : triangles) {
                Vector3f normal = normals.get(tinx);
                avg.addLocal(normal);
            }
            avg.divideLocal(triangles.size());
            vertexNormals[i] = avg;
        }
                
        float[] uv = new float[vertexNormals.length * 2];
        // define texcoords
        int uvInx = 0;
        for (Vector3f norm : vertexNormals) {
            
            assert -1f <= norm.x && norm.x <= 1f;
            assert -1f <= norm.y && norm.y <= 1f;
            assert -1f <= norm.z && norm.z <= 1f;
            
            double u = .5d + Math.atan2(norm.z, norm.x)/(Math.PI * 2d);
            double v = .5d - Math.asin(norm.y)/Math.PI;
            
            assert 0d <= u && u <= 1d;
            assert 0d <= v && v <= 1d;
            uv[uvInx++] = (float) u;
            uv[uvInx++] = (float) v;
        }
    
        Mesh mesh = new Mesh();
        indexes = Arrays.copyOfRange(indexes, 0, indexesSize);
        assert indexes.length == indexesSize;
        
        mesh.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(pointsRefined.toArray(new Vector3f[]{})));
        mesh.setBuffer(Type.Index, 3, BufferUtils.createIntBuffer(indexes));
        mesh.setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(vertexNormals));
        mesh.setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(uv));
        mesh.updateBound();
        assert mesh.getVertexCount() == pointsRefined.size();
        assert mesh.getTriangleCount() == indexesSize / 3;
        assert indexesSize % 3 == 0;
        
        return mesh;
    }
    
}

Hi

Your importer seems to support only ASCII files. Maybe you can port this one from JogAmp’s Ardor3D Continuation to JMonkeyEngine, it supports both binary and ASCII STL files:
https://github.com/gouessej/Ardor3D/issues/2