<commit_before>package jme3tools.optimize;

import com.jme3.asset.AssetManager;
import com.jme3.material.MatParamTexture;
import com.jme3.material.Material;
import com.jme3.math.Matrix4f;
import com.jme3.math.Transform;
import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh.Mode;
import com.jme3.scene.*;
import com.jme3.scene.VertexBuffer.Format;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.VertexBuffer.Usage;
import com.jme3.scene.mesh.IndexBuffer;
import com.jme3.texture.Texture;
import com.jme3.util.BufferUtils;
import com.jme3.util.IntMap.Entry;
import com.jme3.util.TangentBinormalGenerator;
import java.nio.Buffer;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import jme3tools.optimize.TextureAtlas.TextureAtlasTile;

public class GeometryBatchFactory {

    private static final Logger logger = Logger.getLogger(GeometryBatchFactory.class.getName());

    private static void doTransformVerts(FloatBuffer inBuf, int offset, FloatBuffer outBuf, Matrix4f transform) {
        Vector3f pos = new Vector3f();

        // offset is given in element units
        // convert to be in component units
        offset *= 3;

        for (int i = 0; i < inBuf.capacity() / 3; i++) {
            pos.x = inBuf.get(i * 3 + 0);
            pos.y = inBuf.get(i * 3 + 1);
            pos.z = inBuf.get(i * 3 + 2);

            transform.mult(pos, pos);

            outBuf.put(offset + i * 3 + 0, pos.x);
            outBuf.put(offset + i * 3 + 1, pos.y);
            outBuf.put(offset + i * 3 + 2, pos.z);
        }
    }

    private static void doTransformNorms(FloatBuffer inBuf, int offset, FloatBuffer outBuf, Matrix4f transform) {
        Vector3f norm = new Vector3f();

        // offset is given in element units
        // convert to be in component units
        offset *= 3;

        for (int i = 0; i < inBuf.capacity() / 3; i++) {
            norm.x = inBuf.get(i * 3 + 0);
            norm.y = inBuf.get(i * 3 + 1);
            norm.z = inBuf.get(i * 3 + 2);

            transform.multNormal(norm, norm);

            outBuf.put(offset + i * 3 + 0, norm.x);
            outBuf.put(offset + i * 3 + 1, norm.y);
            outBuf.put(offset + i * 3 + 2, norm.z);
        }
    }

    /**
     * Merges all geometries in the collection into
     * the output mesh. Creates a new material using the TextureAtlas.
     * 
     * @param geometries
     * @param outMesh
     */
    public static void mergeGeometries(Collection<Geometry> geometries, Mesh outMesh) {
        mergeGeometries(geometries, outMesh, null);
    }

    /**
     * Merges all geometries in the collection into
     * the output mesh. Creates a new material using the TextureAtlas.
     * 
     * @param geometries
     * @param outMesh
     * @param atlas the TextureAtlas to use
     */
    public static void mergeGeometries(Collection<Geometry> geometries, Mesh outMesh, TextureAtlas atlas) {
        int[] compsForBuf = new int[VertexBuffer.Type.values().length];
        Format[] formatForBuf = new Format[compsForBuf.length];

        int totalVerts = 0;
        int totalTris = 0;
        int totalLodLevels = 0;

        Mode mode = null;
        for (Geometry geom : geometries) {
            totalVerts += geom.getVertexCount();
            totalTris += geom.getTriangleCount();
            totalLodLevels = Math.min(totalLodLevels, geom.getMesh().getNumLodLevels());

            Mode listMode;
            int components;
            switch (geom.getMesh().getMode()) {
                case Points:
                    listMode = Mode.Points;
                    components = 1;
                    break;
                case LineLoop:
                case LineStrip:
                case Lines:
                    listMode = Mode.Lines;
                    components = 2;
                    break;
                case TriangleFan:
                case TriangleStrip:
                case Triangles:
                    listMode = Mode.Triangles;
                    components = 3;
                    break;
                default:
                    throw new UnsupportedOperationException();
            }

            for (Entry<VertexBuffer> entry : geom.getMesh().getBuffers()) {
                compsForBuf[entry.getKey()] = entry.getValue().getNumComponents();
                formatForBuf[entry.getKey()] = entry.getValue().getFormat();
            }

            if (mode != null && mode != listMode) {
                throw new UnsupportedOperationException("Cannot combine different"
                        + " primitive types: " + mode + " != " + listMode);
            }
            mode = listMode;
            compsForBuf[Type.Index.ordinal()] = components;
        }

        outMesh.setMode(mode);
        if (totalVerts >= 65536) {
            // make sure we create an UnsignedInt buffer so
            // we can fit all of the meshes
            formatForBuf[Type.Index.ordinal()] = Format.UnsignedInt;
        } else {
            formatForBuf[Type.Index.ordinal()] = Format.UnsignedShort;
        }

        // generate output buffers based on retrieved info
        for (int i = 0; i < compsForBuf.length; i++) {
            if (compsForBuf[i] == 0) {
                continue;
            }

            Buffer data;
            if (i == Type.Index.ordinal()) {
                data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalTris);
            } else {
                data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalVerts);
            }

            VertexBuffer vb = new VertexBuffer(Type.values()[i]);
            vb.setupData(Usage.Static, compsForBuf[i], formatForBuf[i], data);
            outMesh.setBuffer(vb);
        }

        int globalVertIndex = 0;
        int globalTriIndex = 0;

        for (Geometry geom : geometries) {
            Mesh inMesh = geom.getMesh();
            geom.computeWorldMatrix();
            Matrix4f worldMatrix = geom.getWorldMatrix();

            int geomVertCount = inMesh.getVertexCount();
            int geomTriCount = inMesh.getTriangleCount();

            for (int bufType = 0; bufType < compsForBuf.length; bufType++) {
                VertexBuffer inBuf = inMesh.getBuffer(Type.values()[bufType]);
                VertexBuffer outBuf = outMesh.getBuffer(Type.values()[bufType]);

                if (inBuf == null || outBuf == null) {
                    continue;
                }

                if (Type.Index.ordinal() == bufType) {
                    int components = compsForBuf[bufType];

                    IndexBuffer inIdx = inMesh.getIndicesAsList();
                    IndexBuffer outIdx = outMesh.getIndexBuffer();

                    for (int tri = 0; tri < geomTriCount; tri++) {
                        for (int comp = 0; comp < components; comp++) {
                            int idx = inIdx.get(tri * components + comp) + globalVertIndex;
                            outIdx.put((globalTriIndex + tri) * components + comp, idx);
                        }
                    }
                } else if (Type.Position.ordinal() == bufType) {
                    FloatBuffer inPos = (FloatBuffer) inBuf.getData();
                    FloatBuffer outPos = (FloatBuffer) outBuf.getData();
                    doTransformVerts(inPos, globalVertIndex, outPos, worldMatrix);
                } else if (Type.Normal.ordinal() == bufType || Type.Tangent.ordinal() == bufType) {
                    FloatBuffer inPos = (FloatBuffer) inBuf.getData();
                    FloatBuffer outPos = (FloatBuffer) outBuf.getData();
                    doTransformNorms(inPos, globalVertIndex, outPos, worldMatrix);
                } else if (Type.TexCoord.ordinal() == bufType && atlas != null) {
                    Texture tex = getMaterialTexture(geom, "DiffuseMap");
                    if (tex == null) {
                        tex = getMaterialTexture(geom, "ColorMap");

                    }
                    if (tex != null) {
                        TextureAtlasTile tile = atlas.getAtlasTile(tex);
                        if (tile != null) {
                            FloatBuffer inPos = (FloatBuffer) inBuf.getData();
                            FloatBuffer outPos = (FloatBuffer) outBuf.getData();
                            tile.transformTextureCoords(inPos, globalVertIndex, outPos);
                        }
                    }
                } else {
                    for (int vert = 0; vert < geomVertCount; vert++) {
                        int curGlobalVertIndex = globalVertIndex + vert;
                        inBuf.copyElement(vert, outBuf, curGlobalVertIndex);
                    }
                }
            }

            globalVertIndex += geomVertCount;
            globalTriIndex += geomTriCount;
        }
    }

    public static void makeLods(Collection<Geometry> geometries, Mesh outMesh) {
        int lodLevels = 0;
        int[] lodSize = null;
        int index = 0;
        for (Geometry g : geometries) {
            if (lodLevels == 0) {
                lodLevels = g.getMesh().getNumLodLevels();
            }
            if (lodSize == null) {
                lodSize = new int[lodLevels];
            }
            for (int i = 0; i < lodLevels; i++) {
                lodSize[i] += g.getMesh().getLodLevel(i).getData().capacity();
                //if( i == 0) System.out.println(index + " " +lodSize[i]);
            }
            index++;
        }
        int[][] lodData = new int[lodLevels][];
        for (int i = 0; i < lodLevels; i++) {
            lodData[i] = new int[lodSize[i]];
        }
        VertexBuffer[] lods = new VertexBuffer[lodLevels];
        int bufferPos[] = new int[lodLevels];
        //int index = 0;
        int numOfVertices = 0;
        int curGeom = 0;
        for (Geometry g : geometries) {
            if (numOfVertices == 0) {
                numOfVertices = g.getVertexCount();
            }
            for (int i = 0; i < lodLevels; i++) {
                ShortBuffer buffer = (ShortBuffer) g.getMesh().getLodLevel(i).getData();
                buffer.rewind();
                //System.out.println("buffer: " + buffer.capacity() + " limit: " + lodSize[i] + " " + index);
                for (int j = 0; j < buffer.capacity(); j++) {
                    lodData[i][bufferPos[i] + j] = buffer.get() + numOfVertices * curGeom;
                    //bufferPos[i]++;
                }
                bufferPos[i] += buffer.capacity();
            }
            curGeom++;
        }
        for (int i = 0; i < lodLevels; i++) {
            lods[i] = new VertexBuffer(Type.Index);
            lods[i].setupData(Usage.Dynamic, 1, Format.UnsignedInt, BufferUtils.createIntBuffer(lodData[i]));
        }
        System.out.println(lods.length);
        outMesh.setLodLevels(lods);
    }

    public static List<Geometry> makeBatches(Collection<Geometry> geometries) {
        return makeBatches(geometries, false);
    }

    /**
     * Batches a collection of Geometries so that all with the same material get combined.
     * @param geometries The Geometries to combine
     * @return A List of newly created Geometries, each with a  distinct material
     */
    public static List<Geometry> makeBatches(Collection<Geometry> geometries, boolean useLods) {
        ArrayList<Geometry> retVal = new ArrayList<Geometry>();
        HashMap<Material, List<Geometry>> matToGeom = new HashMap<Material, List<Geometry>>();

        for (Geometry geom : geometries) {
            List<Geometry> outList = matToGeom.get(geom.getMaterial());
            if (outList == null) {
                outList = new ArrayList<Geometry>();
                matToGeom.put(geom.getMaterial(), outList);
            }
            outList.add(geom);
        }

        int batchNum = 0;
        for (Map.Entry<Material, List<Geometry>> entry : matToGeom.entrySet()) {
            Material mat = entry.getKey();
            List<Geometry> geomsForMat = entry.getValue();
            Mesh mesh = new Mesh();
            mergeGeometries(geomsForMat, mesh);
            // lods
            if (useLods) {
                makeLods(geomsForMat, mesh);
            }
            mesh.updateCounts();
            mesh.updateBound();

            Geometry out = new Geometry("batch[" + (batchNum++) + "]", mesh);
            out.setMaterial(mat);
            retVal.add(out);
        }

        return retVal;
    }

    public static Geometry makeAtlasBatch(Spatial spat, AssetManager mgr, int atlasSize) {
        List<Geometry> geometries = new ArrayList<Geometry>();
        gatherGeoms(spat, geometries);
        //TODO: specular etc. maps, needs to use main atlas for locations
        TextureAtlas atlas = new TextureAtlas(atlasSize, atlasSize);
        for (Geometry geometry : geometries) {
            Texture diffuse = getMaterialTexture(geometry, "DiffuseMap");
            Texture normal = getMaterialTexture(geometry, "NormalMap");
            Texture specular = getMaterialTexture(geometry, "SpecularMap");
            if (diffuse == null) {
                diffuse = getMaterialTexture(geometry, "ColorMap");

            }
            if (diffuse != null && diffuse.getKey() != null) {
                String keyName = diffuse.getKey().getName();
                if (!atlas.addTexture(diffuse, "DiffuseMap")) {
                    logger.log(Level.WARNING, "Adding diffuse texture {0} to atlas failed, atlas full?", keyName);
                }
                if (normal != null && normal.getKey() != null) {
                    atlas.addTexture(diffuse, "NormalMap", keyName);
                }
                if (specular != null && specular.getKey() != null) {
                    atlas.addTexture(specular, "SpecularMap", keyName);
                }
            }
        }
        Geometry geom = new Geometry();
        Mesh mesh = new Mesh();
        mergeGeometries(geometries, mesh, atlas);
        mesh.updateCounts();
        mesh.updateBound();
        geom.setMesh(mesh);

        Material mat = new Material(mgr, "Common/MatDefs/Light/Lighting.j3md");
        Texture diffuseMap = atlas.getAtlasTexture("DiffuseMap");
        Texture normalMap = atlas.getAtlasTexture("NormalMap");
        Texture specularMap = atlas.getAtlasTexture("SpecularMap");
        if (diffuseMap != null) {
            mat.setTexture("DiffuseMap", diffuseMap);
        }
        if (normalMap != null) {
            mat.setTexture("NormalMap", normalMap);
        }
        if (specularMap != null) {
            mat.setTexture("SpecularMap", specularMap);
        }
        mat.setFloat("Shininess", 16.0f);
        
        geom.setMaterial(mat);
        return geom;
    }

    private static Texture getMaterialTexture(Geometry geometry, String mapName) {
        Material mat = geometry.getMaterial();
        if (mat == null || mat.getParam(mapName) == null || !(mat.getParam(mapName) instanceof MatParamTexture)) {
            return null;
        }
        MatParamTexture param = (MatParamTexture) mat.getParam(mapName);
        Texture texture = param.getTextureValue();
        if (texture == null) {
            return null;
        }
        return texture;
    }

    private static void gatherGeoms(Spatial scene, List<Geometry> geoms) {
        if (scene instanceof Node) {
            Node node = (Node) scene;
            for (Spatial child : node.getChildren()) {
                gatherGeoms(child, geoms);
            }<commit_msg>- make GeometryBatchFactory check for diffuse texture before applying normal textures - make GeometryBatchFactory throw exception when atlas is full<commit_after>package jme3tools.optimize;

import com.jme3.asset.AssetManager;
import com.jme3.material.MatParamTexture;
import com.jme3.material.Material;
import com.jme3.math.Matrix4f;
import com.jme3.math.Transform;
import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh.Mode;
import com.jme3.scene.*;
import com.jme3.scene.VertexBuffer.Format;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.VertexBuffer.Usage;
import com.jme3.scene.mesh.IndexBuffer;
import com.jme3.texture.Texture;
import com.jme3.util.BufferUtils;
import com.jme3.util.IntMap.Entry;
import com.jme3.util.TangentBinormalGenerator;
import java.nio.Buffer;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import jme3tools.optimize.TextureAtlas.TextureAtlasTile;

public class GeometryBatchFactory {

    private static final Logger logger = Logger.getLogger(GeometryBatchFactory.class.getName());

    private static void doTransformVerts(FloatBuffer inBuf, int offset, FloatBuffer outBuf, Matrix4f transform) {
        Vector3f pos = new Vector3f();

        // offset is given in element units
        // convert to be in component units
        offset *= 3;

        for (int i = 0; i < inBuf.capacity() / 3; i++) {
            pos.x = inBuf.get(i * 3 + 0);
            pos.y = inBuf.get(i * 3 + 1);
            pos.z = inBuf.get(i * 3 + 2);

            transform.mult(pos, pos);

            outBuf.put(offset + i * 3 + 0, pos.x);
            outBuf.put(offset + i * 3 + 1, pos.y);
            outBuf.put(offset + i * 3 + 2, pos.z);
        }
    }

    private static void doTransformNorms(FloatBuffer inBuf, int offset, FloatBuffer outBuf, Matrix4f transform) {
        Vector3f norm = new Vector3f();

        // offset is given in element units
        // convert to be in component units
        offset *= 3;

        for (int i = 0; i < inBuf.capacity() / 3; i++) {
            norm.x = inBuf.get(i * 3 + 0);
            norm.y = inBuf.get(i * 3 + 1);
            norm.z = inBuf.get(i * 3 + 2);

            transform.multNormal(norm, norm);

            outBuf.put(offset + i * 3 + 0, norm.x);
            outBuf.put(offset + i * 3 + 1, norm.y);
            outBuf.put(offset + i * 3 + 2, norm.z);
        }
    }

    /**
     * Merges all geometries in the collection into
     * the output mesh. Creates a new material using the TextureAtlas.
     * 
     * @param geometries
     * @param outMesh
     */
    public static void mergeGeometries(Collection<Geometry> geometries, Mesh outMesh) {
        mergeGeometries(geometries, outMesh, null);
    }

    /**
     * Merges all geometries in the collection into
     * the output mesh. Creates a new material using the TextureAtlas.
     * 
     * @param geometries
     * @param outMesh
     * @param atlas the TextureAtlas to use
     */
    public static void mergeGeometries(Collection<Geometry> geometries, Mesh outMesh, TextureAtlas atlas) {
        int[] compsForBuf = new int[VertexBuffer.Type.values().length];
        Format[] formatForBuf = new Format[compsForBuf.length];

        int totalVerts = 0;
        int totalTris = 0;
        int totalLodLevels = 0;

        Mode mode = null;
        for (Geometry geom : geometries) {
            totalVerts += geom.getVertexCount();
            totalTris += geom.getTriangleCount();
            totalLodLevels = Math.min(totalLodLevels, geom.getMesh().getNumLodLevels());

            Mode listMode;
            int components;
            switch (geom.getMesh().getMode()) {
                case Points:
                    listMode = Mode.Points;
                    components = 1;
                    break;
                case LineLoop:
                case LineStrip:
                case Lines:
                    listMode = Mode.Lines;
                    components = 2;
                    break;
                case TriangleFan:
                case TriangleStrip:
                case Triangles:
                    listMode = Mode.Triangles;
                    components = 3;
                    break;
                default:
                    throw new UnsupportedOperationException();
            }

            for (Entry<VertexBuffer> entry : geom.getMesh().getBuffers()) {
                compsForBuf[entry.getKey()] = entry.getValue().getNumComponents();
                formatForBuf[entry.getKey()] = entry.getValue().getFormat();
            }

            if (mode != null && mode != listMode) {
                throw new UnsupportedOperationException("Cannot combine different"
                        + " primitive types: " + mode + " != " + listMode);
            }
            mode = listMode;
            compsForBuf[Type.Index.ordinal()] = components;
        }

        outMesh.setMode(mode);
        if (totalVerts >= 65536) {
            // make sure we create an UnsignedInt buffer so
            // we can fit all of the meshes
            formatForBuf[Type.Index.ordinal()] = Format.UnsignedInt;
        } else {
            formatForBuf[Type.Index.ordinal()] = Format.UnsignedShort;
        }

        // generate output buffers based on retrieved info
        for (int i = 0; i < compsForBuf.length; i++) {
            if (compsForBuf[i] == 0) {
                continue;
            }

            Buffer data;
            if (i == Type.Index.ordinal()) {
                data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalTris);
            } else {
                data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalVerts);
            }

            VertexBuffer vb = new VertexBuffer(Type.values()[i]);
            vb.setupData(Usage.Static, compsForBuf[i], formatForBuf[i], data);
            outMesh.setBuffer(vb);
        }

        int globalVertIndex = 0;
        int globalTriIndex = 0;

        for (Geometry geom : geometries) {
            Mesh inMesh = geom.getMesh();
            geom.computeWorldMatrix();
            Matrix4f worldMatrix = geom.getWorldMatrix();

            int geomVertCount = inMesh.getVertexCount();
            int geomTriCount = inMesh.getTriangleCount();

            for (int bufType = 0; bufType < compsForBuf.length; bufType++) {
                VertexBuffer inBuf = inMesh.getBuffer(Type.values()[bufType]);
                VertexBuffer outBuf = outMesh.getBuffer(Type.values()[bufType]);

                if (inBuf == null || outBuf == null) {
                    continue;
                }

                if (Type.Index.ordinal() == bufType) {
                    int components = compsForBuf[bufType];

                    IndexBuffer inIdx = inMesh.getIndicesAsList();
                    IndexBuffer outIdx = outMesh.getIndexBuffer();

                    for (int tri = 0; tri < geomTriCount; tri++) {
                        for (int comp = 0; comp < components; comp++) {
                            int idx = inIdx.get(tri * components + comp) + globalVertIndex;
                            outIdx.put((globalTriIndex + tri) * components + comp, idx);
                        }
                    }
                } else if (Type.Position.ordinal() == bufType) {
                    FloatBuffer inPos = (FloatBuffer) inBuf.getData();
                    FloatBuffer outPos = (FloatBuffer) outBuf.getData();
                    doTransformVerts(inPos, globalVertIndex, outPos, worldMatrix);
                } else if (Type.Normal.ordinal() == bufType || Type.Tangent.ordinal() == bufType) {
                    FloatBuffer inPos = (FloatBuffer) inBuf.getData();
                    FloatBuffer outPos = (FloatBuffer) outBuf.getData();
                    doTransformNorms(inPos, globalVertIndex, outPos, worldMatrix);
                } else if (Type.TexCoord.ordinal() == bufType && atlas != null) {
                    Texture tex = getMaterialTexture(geom, "DiffuseMap");
                    if (tex == null) {
                        tex = getMaterialTexture(geom, "ColorMap");

                    }
                    if (tex != null) {
                        TextureAtlasTile tile = atlas.getAtlasTile(tex);
                        if (tile != null) {
                            FloatBuffer inPos = (FloatBuffer) inBuf.getData();
                            FloatBuffer outPos = (FloatBuffer) outBuf.getData();
                            tile.transformTextureCoords(inPos, globalVertIndex, outPos);
                        }
                    }
                } else {
                    for (int vert = 0; vert < geomVertCount; vert++) {
                        int curGlobalVertIndex = globalVertIndex + vert;
                        inBuf.copyElement(vert, outBuf, curGlobalVertIndex);
                    }
                }
            }

            globalVertIndex += geomVertCount;
            globalTriIndex += geomTriCount;
        }
    }

    public static void makeLods(Collection<Geometry> geometries, Mesh outMesh) {
        int lodLevels = 0;
        int[] lodSize = null;
        int index = 0;
        for (Geometry g : geometries) {
            if (lodLevels == 0) {
                lodLevels = g.getMesh().getNumLodLevels();
            }
            if (lodSize == null) {
                lodSize = new int[lodLevels];
            }
            for (int i = 0; i < lodLevels; i++) {
                lodSize[i] += g.getMesh().getLodLevel(i).getData().capacity();
                //if( i == 0) System.out.println(index + " " +lodSize[i]);
            }
            index++;
        }
        int[][] lodData = new int[lodLevels][];
        for (int i = 0; i < lodLevels; i++) {
            lodData[i] = new int[lodSize[i]];
        }
        VertexBuffer[] lods = new VertexBuffer[lodLevels];
        int bufferPos[] = new int[lodLevels];
        //int index = 0;
        int numOfVertices = 0;
        int curGeom = 0;
        for (Geometry g : geometries) {
            if (numOfVertices == 0) {
                numOfVertices = g.getVertexCount();
            }
            for (int i = 0; i < lodLevels; i++) {
                ShortBuffer buffer = (ShortBuffer) g.getMesh().getLodLevel(i).getData();
                buffer.rewind();
                //System.out.println("buffer: " + buffer.capacity() + " limit: " + lodSize[i] + " " + index);
                for (int j = 0; j < buffer.capacity(); j++) {
                    lodData[i][bufferPos[i] + j] = buffer.get() + numOfVertices * curGeom;
                    //bufferPos[i]++;
                }
                bufferPos[i] += buffer.capacity();
            }
            curGeom++;
        }
        for (int i = 0; i < lodLevels; i++) {
            lods[i] = new VertexBuffer(Type.Index);
            lods[i].setupData(Usage.Dynamic, 1, Format.UnsignedInt, BufferUtils.createIntBuffer(lodData[i]));
        }
        System.out.println(lods.length);
        outMesh.setLodLevels(lods);
    }

    public static List<Geometry> makeBatches(Collection<Geometry> geometries) {
        return makeBatches(geometries, false);
    }

    /**
     * Batches a collection of Geometries so that all with the same material get combined.
     * @param geometries The Geometries to combine
     * @return A List of newly created Geometries, each with a  distinct material
     */
    public static List<Geometry> makeBatches(Collection<Geometry> geometries, boolean useLods) {
        ArrayList<Geometry> retVal = new ArrayList<Geometry>();
        HashMap<Material, List<Geometry>> matToGeom = new HashMap<Material, List<Geometry>>();

        for (Geometry geom : geometries) {
            List<Geometry> outList = matToGeom.get(geom.getMaterial());
            if (outList == null) {
                outList = new ArrayList<Geometry>();
                matToGeom.put(geom.getMaterial(), outList);
            }
            outList.add(geom);
        }

        int batchNum = 0;
        for (Map.Entry<Material, List<Geometry>> entry : matToGeom.entrySet()) {
            Material mat = entry.getKey();
            List<Geometry> geomsForMat = entry.getValue();
            Mesh mesh = new Mesh();
            mergeGeometries(geomsForMat, mesh);
            // lods
            if (useLods) {
                makeLods(geomsForMat, mesh);
            }
            mesh.updateCounts();
            mesh.updateBound();

            Geometry out = new Geometry("batch[" + (batchNum++) + "]", mesh);
            out.setMaterial(mat);
            retVal.add(out);
        }

        return retVal;
    }

    public static Geometry makeAtlasBatch(Spatial spat, AssetManager mgr, int atlasSize) {
        List<Geometry> geometries = new ArrayList<Geometry>();
        gatherGeoms(spat, geometries);
        //TODO: specular etc. maps, needs to use main atlas for locations
        TextureAtlas atlas = new TextureAtlas(atlasSize, atlasSize);
        for (Geometry geometry : geometries) {
            Texture diffuse = getMaterialTexture(geometry, "DiffuseMap");
            Texture normal = getMaterialTexture(geometry, "NormalMap");
            Texture specular = getMaterialTexture(geometry, "SpecularMap");
            if (diffuse == null) {
                diffuse = getMaterialTexture(geometry, "ColorMap");

            }
            if (diffuse != null && diffuse.getKey() != null) {
                String keyName = diffuse.getKey().getName();
                if (!atlas.addTexture(diffuse, "DiffuseMap")) {
                    throw new IllegalStateException("Adding diffuse texture" + keyName + "to atlas failed, atlas full.");
                } else {
                    if (normal != null && normal.getKey() != null) {
                        atlas.addTexture(diffuse, "NormalMap", keyName);
                    }
                    if (specular != null && specular.getKey() != null) {
                        atlas.addTexture(specular, "SpecularMap", keyName);
                    }
                }
            }
        }
        Geometry geom = new Geometry();
        Mesh mesh = new Mesh();
        mergeGeometries(geometries, mesh, atlas);
        mesh.updateCounts();
        mesh.updateBound();
        geom.setMesh(mesh);

        Material mat = new Material(mgr, "Common/MatDefs/Light/Lighting.j3md");
        Texture diffuseMap = atlas.getAtlasTexture("DiffuseMap");
        Texture normalMap = atlas.getAtlasTexture("NormalMap");
        Texture specularMap = atlas.getAtlasTexture("SpecularMap");
        if (diffuseMap != null) {
            mat.setTexture("DiffuseMap", diffuseMap);
        }
        if (normalMap != null) {
            mat.setTexture("NormalMap", normalMap);
        }
        if (specularMap != null) {
            mat.setTexture("SpecularMap", specularMap);
        }
        mat.setFloat("Shininess", 16.0f);

        geom.setMaterial(mat);
        return geom;
    }

    private static Texture getMaterialTexture(Geometry geometry, String mapName) {
        Material mat = geometry.getMaterial();
        if (mat == null || mat.getParam(mapName) == null || !(mat.getParam(mapName) instanceof MatParamTexture)) {
            return null;
        }
        MatParamTexture param = (MatParamTexture) mat.getParam(mapName);
        Texture texture = param.getTextureValue();
        if (texture == null) {
            return null;
        }
        return texture;
    }

    private static void gatherGeoms(Spatial scene, List<Geometry> geoms) {
        if (scene instanceof Node) {
            Node node = (Node) scene;
            for (Spatial child : node.getChildren()) {
                gatherGeoms(child, geoms);
            }
{'fraction_non_alphanumeric': 0.11082400229687052, 'fraction_numerical': 0.006093087057772673, 'mean_word_length': 1.7717064544650751, 'pattern_counts': {'":': 0, '<': 71, '<?xml version=': 0, '>': 49, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7588381', 'n_tokens_mistral': 9076, 'n_tokens_neox': 8478, 'n_words': 2067}