com.ardor3d.extension.model.collada.jdom.ColladaMeshUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.ardor3d.extension.model.collada.jdom.ColladaMeshUtils.java

Source

/**
 * Copyright (c) 2008-2012 Ardor Labs, Inc.
 *
 * This file is part of Ardor3D.
 *
 * Ardor3D is free software: you can redistribute it and/or modify it 
 * under the terms of its license which may be found in the accompanying
 * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
 */

package com.ardor3d.extension.model.collada.jdom;

import java.nio.FloatBuffer;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.logging.Logger;

import org.jdom2.Element;

import com.ardor3d.extension.model.collada.jdom.ColladaInputPipe.Type;
import com.ardor3d.extension.model.collada.jdom.data.DataCache;
import com.ardor3d.extension.model.collada.jdom.data.MeshVertPairs;
import com.ardor3d.renderer.IndexMode;
import com.ardor3d.scenegraph.IndexBufferData;
import com.ardor3d.scenegraph.Line;
import com.ardor3d.scenegraph.Mesh;
import com.ardor3d.scenegraph.Node;
import com.ardor3d.scenegraph.Point;
import com.ardor3d.scenegraph.Spatial;
import com.ardor3d.util.geom.BufferUtils;
import com.ardor3d.util.geom.GeometryTool;
import com.ardor3d.util.geom.GeometryTool.MatchCondition;
import com.ardor3d.util.geom.VertMap;

/**
 * Methods for parsing Collada data related to meshes.
 */
public class ColladaMeshUtils {
    private static final Logger logger = Logger.getLogger(ColladaMeshUtils.class.getName());

    private final DataCache _dataCache;
    private final ColladaDOMUtil _colladaDOMUtil;
    private final ColladaMaterialUtils _colladaMaterialUtils;
    private final boolean _optimizeMeshes;
    private final EnumSet<MatchCondition> _optimizeSettings;
    private final GeometryTool _geometryTool;

    public ColladaMeshUtils(final DataCache dataCache, final ColladaDOMUtil colladaDOMUtil,
            final ColladaMaterialUtils colladaMaterialUtils, final boolean optimizeMeshes,
            final EnumSet<MatchCondition> optimizeSettings) {
        this(dataCache, colladaDOMUtil, colladaMaterialUtils, optimizeMeshes, optimizeSettings, new GeometryTool());
    }

    public ColladaMeshUtils(final DataCache dataCache, final ColladaDOMUtil colladaDOMUtil,
            final ColladaMaterialUtils colladaMaterialUtils, final boolean optimizeMeshes,
            final EnumSet<MatchCondition> optimizeSettings, final GeometryTool geometryTool) {
        _dataCache = dataCache;
        _colladaDOMUtil = colladaDOMUtil;
        _colladaMaterialUtils = colladaMaterialUtils;
        _optimizeMeshes = optimizeMeshes;
        _optimizeSettings = EnumSet.copyOf(optimizeSettings);
        _geometryTool = geometryTool;
    }

    /**
     * Builds geometry from an instance_geometry element.
     * 
     * @param instanceGeometry
     * @return our Spatial
     */
    public Spatial getGeometryMesh(final Element instanceGeometry) {
        final Element geometry = _colladaDOMUtil.findTargetWithId(instanceGeometry.getAttributeValue("url"));

        if (geometry != null) {
            return buildMesh(geometry);
        }
        return null;
    }

    /**
     * Builds a mesh from a Collada geometry element. Currently supported mesh types: mesh, polygons, polylist,
     * triangles, lines. Not supported yet: linestrips, trifans, tristrips. If no meshtype is found, a pointcloud is
     * built.
     * 
     * @param colladaGeometry
     * @return a Node containing all of the Ardor3D meshes we've parsed from this geometry element.
     */
    public Node buildMesh(final Element colladaGeometry) {
        if (colladaGeometry.getChild("mesh") != null) {
            final Element cMesh = colladaGeometry.getChild("mesh");
            final Node meshNode = new Node(colladaGeometry.getAttributeValue("name", colladaGeometry.getName()));

            // Grab all mesh types (polygons, triangles, etc.)
            // Create each as an Ardor3D Mesh, and attach to node
            boolean hasChild = false;
            if (cMesh.getChild("polygons") != null) {
                for (final Element p : cMesh.getChildren("polygons")) {
                    final Mesh child = buildMeshPolygons(colladaGeometry, p);
                    if (child != null) {
                        if (child.getName() == null) {
                            child.setName(meshNode.getName() + "_polygons");
                        }
                        meshNode.attachChild(child);
                        hasChild = true;
                    }
                }
            }
            if (cMesh.getChild("polylist") != null) {
                for (final Element p : cMesh.getChildren("polylist")) {
                    final Mesh child = buildMeshPolylist(colladaGeometry, p);
                    if (child != null) {
                        if (child.getName() == null) {
                            child.setName(meshNode.getName() + "_polylist");
                        }
                        meshNode.attachChild(child);
                        hasChild = true;
                    }
                }
            }
            if (cMesh.getChild("triangles") != null) {
                for (final Element t : cMesh.getChildren("triangles")) {
                    final Mesh child = buildMeshTriangles(colladaGeometry, t);
                    if (child != null) {
                        if (child.getName() == null) {
                            child.setName(meshNode.getName() + "_triangles");
                        }
                        meshNode.attachChild(child);
                        hasChild = true;
                    }
                }
            }
            if (cMesh.getChild("lines") != null) {
                for (final Element l : cMesh.getChildren("lines")) {
                    final Line child = buildMeshLines(colladaGeometry, l);
                    if (child != null) {
                        if (child.getName() == null) {
                            child.setName(meshNode.getName() + "_lines");
                        }
                        meshNode.attachChild(child);
                        hasChild = true;
                    }
                }
            }
            if (cMesh.getChild("linestrips") != null) {
                logger.warning("<linestrips> not currently supported.");
                hasChild = true;
                // TODO: Add support
            }
            if (cMesh.getChild("trifans") != null) {
                logger.warning("<trifan> not currently supported.");
                hasChild = true;
                // TODO: Add support
            }
            if (cMesh.getChild("tristrips") != null) {
                logger.warning("<tristrip> not currently supported.");
                hasChild = true;
                // TODO: Add support
            }

            // If we did not find a valid child, the spec says to add verts as a "cloud of points"
            if (!hasChild) {
                logger.warning("No valid child found, creating 'cloud of points'");
                final Point points = buildPoints(colladaGeometry, cMesh);
                if (points != null) {
                    if (points.getName() == null) {
                        points.setName(meshNode.getName() + "_points");
                    }
                    meshNode.attachChild(points);
                }
            }

            return meshNode;
        }
        return null;
    }

    private Point buildPoints(final Element colladaGeometry, final Element mesh) {
        if (mesh == null || mesh.getChild("vertices") == null
                || mesh.getChild("vertices").getChild("input") == null) {
            return null;
        }
        final Point points = new Point();
        points.setName(mesh.getAttributeValue("name", mesh.getName()));

        // Find POSITION vertices source
        final Element source = _colladaDOMUtil.getPositionSource(mesh.getChild("vertices"));
        if (source == null) {
            return null;
        }

        if (source.getChild("float_array") != null) {
            // Turn into Floatbuffer if we have float array data
            final Element floatArray = source.getChild("float_array");
            if ("0".equals(floatArray.getAttributeValue("count"))) {
                return null;
            }
            final FloatBuffer vertices = BufferUtils.createFloatBuffer(_colladaDOMUtil.parseFloatArray(floatArray));
            // Add to points
            points.getMeshData().setVertexBuffer(vertices);
        } else if (source.getChild("int_array") != null) {
            // Turn into Floatbuffer if we have int array data
            final Element intArray = source.getChild("int_array");
            if ("0".equals(intArray.getAttributeValue("count"))) {
                return null;
            }
            final int[] data = _colladaDOMUtil.parseIntArray(intArray);
            final FloatBuffer vertices = BufferUtils.createFloatBuffer(data.length);
            for (final int i : data) {
                vertices.put(i);
            }
            // Add to points
            points.getMeshData().setVertexBuffer(vertices);
        }

        // Add to vert mapping
        final int[] indices = new int[points.getMeshData().getVertexCount()];
        for (int i = 0; i < indices.length; i++) {
            indices[i] = i;
        }
        final MeshVertPairs mvp = new MeshVertPairs(points, indices);
        _dataCache.getVertMappings().put(colladaGeometry, mvp);

        if (_optimizeMeshes) {
            final VertMap map = _geometryTool.minimizeVerts(points, _optimizeSettings);
            _dataCache.setMeshVertMap(points, map);
        }

        // Update bound
        points.updateModelBound();

        // return
        return points;
    }

    public Mesh buildMeshPolygons(final Element colladaGeometry, final Element polys) {
        if (polys == null || polys.getChild("input") == null) {
            return null;
        }
        final Mesh polyMesh = new Mesh(extractName(colladaGeometry, polys));
        polyMesh.getMeshData().setIndexMode(IndexMode.Triangles);

        // Build and set RenderStates for our material
        _colladaMaterialUtils.applyMaterial(polys.getAttributeValue("material"), polyMesh);

        final LinkedList<ColladaInputPipe> pipes = new LinkedList<ColladaInputPipe>();
        final int maxOffset = extractPipes(polys, pipes);
        final int interval = maxOffset + 1;

        // use interval & sum of sizes of p entries to determine buffer sizes.
        int numEntries = 0;
        int numIndices = 0;
        for (final Element vals : polys.getChildren("p")) {
            final int length = _colladaDOMUtil.parseIntArray(vals).length;
            numEntries += length;
            numIndices += (length / interval - 2) * 3;
        }
        numEntries /= interval;

        // Construct nio buffers for specified inputs.
        for (final ColladaInputPipe pipe : pipes) {
            pipe.setupBuffer(numEntries, polyMesh.getMeshData(), _dataCache);
        }

        // Add to vert mapping
        final int[] indices = new int[numEntries];
        final MeshVertPairs mvp = new MeshVertPairs(polyMesh, indices);
        _dataCache.getVertMappings().put(colladaGeometry, mvp);

        // Prepare indices buffer
        final IndexBufferData<?> meshIndices = BufferUtils.createIndexBufferData(numIndices,
                polyMesh.getMeshData().getVertexCount() - 1);
        polyMesh.getMeshData().setIndices(meshIndices);

        // go through the polygon entries
        int firstIndex = 0, vecIndex;
        final int[] currentVal = new int[interval];
        for (final Element dia : polys.getChildren("p")) {
            // for each p, iterate using max offset
            final int[] vals = _colladaDOMUtil.parseIntArray(dia);

            final int first = firstIndex + 0;
            System.arraycopy(vals, 0, currentVal, 0, interval);
            vecIndex = processPipes(pipes, currentVal);
            if (vecIndex != Integer.MIN_VALUE) {
                indices[firstIndex + 0] = vecIndex;
            }

            int prev = firstIndex + 1;
            System.arraycopy(vals, interval, currentVal, 0, interval);
            vecIndex = processPipes(pipes, currentVal);
            if (vecIndex != Integer.MIN_VALUE) {
                indices[firstIndex + 1] = vecIndex;
            }

            // first add the first two entries to the buffers.

            // Now go through remaining entries and create a polygon as a triangle fan.
            for (int j = 2, max = vals.length / interval; j < max; j++) {
                // add first as index
                meshIndices.put(first);
                // add prev as index
                meshIndices.put(prev);

                // set prev to current
                prev = firstIndex + j;
                // add current to buffers
                System.arraycopy(vals, j * interval, currentVal, 0, interval);
                vecIndex = processPipes(pipes, currentVal);
                if (vecIndex != Integer.MIN_VALUE) {
                    indices[firstIndex + j] = vecIndex;
                }
                // add current as index
                meshIndices.put(prev);
            }
            firstIndex += vals.length / interval;
        }

        if (_optimizeMeshes) {
            final VertMap map = _geometryTool.minimizeVerts(polyMesh, _optimizeSettings);
            _dataCache.setMeshVertMap(polyMesh, map);
        }

        // update bounds
        polyMesh.updateModelBound();

        // return
        return polyMesh;
    }

    public Mesh buildMeshPolylist(final Element colladaGeometry, final Element polys) {
        if (polys == null || polys.getChild("input") == null) {
            return null;
        }
        final Mesh polyMesh = new Mesh(extractName(colladaGeometry, polys));
        polyMesh.getMeshData().setIndexMode(IndexMode.Triangles);

        // Build and set RenderStates for our material
        _colladaMaterialUtils.applyMaterial(polys.getAttributeValue("material"), polyMesh);

        final LinkedList<ColladaInputPipe> pipes = new LinkedList<ColladaInputPipe>();
        final int maxOffset = extractPipes(polys, pipes);
        final int interval = maxOffset + 1;

        // use interval & sum of sizes of vcount to determine buffer sizes.
        int numEntries = 0;
        int numIndices = 0;
        for (final int length : _colladaDOMUtil.parseIntArray(polys.getChild("vcount"))) {
            numEntries += length;
            numIndices += (length - 2) * 3;
        }

        // Construct nio buffers for specified inputs.
        for (final ColladaInputPipe pipe : pipes) {
            pipe.setupBuffer(numEntries, polyMesh.getMeshData(), _dataCache);
        }

        // Add to vert mapping
        final int[] indices = new int[numEntries];
        final MeshVertPairs mvp = new MeshVertPairs(polyMesh, indices);
        _dataCache.getVertMappings().put(colladaGeometry, mvp);

        // Prepare indices buffer
        final IndexBufferData<?> meshIndices = BufferUtils.createIndexBufferData(numIndices,
                polyMesh.getMeshData().getVertexCount() - 1);
        polyMesh.getMeshData().setIndices(meshIndices);

        // go through the polygon entries
        int firstIndex = 0;
        int vecIndex;
        final int[] vals = _colladaDOMUtil.parseIntArray(polys.getChild("p"));
        for (final int length : _colladaDOMUtil.parseIntArray(polys.getChild("vcount"))) {
            final int[] currentVal = new int[interval];

            // first add the first two entries to the buffers.
            final int first = firstIndex + 0;
            System.arraycopy(vals, (first * interval), currentVal, 0, interval);
            vecIndex = processPipes(pipes, currentVal);
            if (vecIndex != Integer.MIN_VALUE) {
                indices[firstIndex + 0] = vecIndex;
            }

            int prev = firstIndex + 1;
            System.arraycopy(vals, (prev * interval), currentVal, 0, interval);
            vecIndex = processPipes(pipes, currentVal);
            if (vecIndex != Integer.MIN_VALUE) {
                indices[firstIndex + 1] = vecIndex;
            }

            // Now go through remaining entries and create a polygon as a triangle fan.
            for (int j = 2, max = length; j < max; j++) {
                // add first as index
                meshIndices.put(first);
                // add prev as index
                meshIndices.put(prev);

                // set prev to current
                prev = firstIndex + j;
                // add current to buffers
                System.arraycopy(vals, (prev * interval), currentVal, 0, interval);
                vecIndex = processPipes(pipes, currentVal);
                if (vecIndex != Integer.MIN_VALUE) {
                    indices[firstIndex + j] = vecIndex;
                }
                // add current as index
                meshIndices.put(prev);
            }
            firstIndex += length;
        }

        if (_optimizeMeshes) {
            final VertMap map = _geometryTool.minimizeVerts(polyMesh, _optimizeSettings);
            _dataCache.setMeshVertMap(polyMesh, map);
        }

        // update bounds
        polyMesh.updateModelBound();

        // return
        return polyMesh;
    }

    public Mesh buildMeshTriangles(final Element colladaGeometry, final Element tris) {
        if (tris == null || tris.getChild("input") == null || tris.getChild("p") == null) {
            return null;
        }
        final Mesh triMesh = new Mesh(extractName(colladaGeometry, tris));
        triMesh.getMeshData().setIndexMode(IndexMode.Triangles);

        // Build and set RenderStates for our material
        _colladaMaterialUtils.applyMaterial(tris.getAttributeValue("material"), triMesh);

        final LinkedList<ColladaInputPipe> pipes = new LinkedList<ColladaInputPipe>();
        final int maxOffset = extractPipes(tris, pipes);
        final int interval = maxOffset + 1;

        // use interval & size of p array to determine buffer sizes.
        final int[] vals = _colladaDOMUtil.parseIntArray(tris.getChild("p"));
        final int numEntries = vals.length / interval;

        // Construct nio buffers for specified inputs.
        for (final ColladaInputPipe pipe : pipes) {
            pipe.setupBuffer(numEntries, triMesh.getMeshData(), _dataCache);
        }

        // Add to vert mapping
        final int[] indices = new int[numEntries];
        final MeshVertPairs mvp = new MeshVertPairs(triMesh, indices);
        _dataCache.getVertMappings().put(colladaGeometry, mvp);

        // go through the p entry
        // for each p, iterate using max offset
        final int[] currentVal = new int[interval];

        // Go through entries and add to buffers.
        for (int j = 0, max = numEntries; j < max; j++) {
            // add entry to buffers
            System.arraycopy(vals, j * interval, currentVal, 0, interval);
            final int rVal = processPipes(pipes, currentVal);
            if (rVal != Integer.MIN_VALUE) {
                indices[j] = rVal;
            }
        }

        if (_optimizeMeshes) {
            final VertMap map = _geometryTool.minimizeVerts(triMesh, _optimizeSettings);
            _dataCache.setMeshVertMap(triMesh, map);
        }

        triMesh.updateModelBound();

        return triMesh;
    }

    public Line buildMeshLines(final Element colladaGeometry, final Element lines) {
        if (lines == null || lines.getChild("input") == null || lines.getChild("p") == null) {
            return null;
        }
        final Line lineMesh = new Line(extractName(colladaGeometry, lines));

        // Build and set RenderStates for our material
        _colladaMaterialUtils.applyMaterial(lines.getAttributeValue("material"), lineMesh);

        final LinkedList<ColladaInputPipe> pipes = new LinkedList<ColladaInputPipe>();
        final int maxOffset = extractPipes(lines, pipes);
        final int interval = maxOffset + 1;

        // use interval & size of p array to determine buffer sizes.
        final int[] vals = _colladaDOMUtil.parseIntArray(lines.getChild("p"));
        final int numEntries = vals.length / interval;

        // Construct nio buffers for specified inputs.
        for (final ColladaInputPipe pipe : pipes) {
            pipe.setupBuffer(numEntries, lineMesh.getMeshData(), _dataCache);
        }

        // Add to vert mapping
        final int[] indices = new int[numEntries];
        final MeshVertPairs mvp = new MeshVertPairs(lineMesh, indices);
        _dataCache.getVertMappings().put(colladaGeometry, mvp);

        // go through the p entry
        // for each p, iterate using max offset
        final int[] currentVal = new int[interval];

        // Go through entries and add to buffers.
        for (int j = 0, max = numEntries; j < max; j++) {
            // add entry to buffers
            System.arraycopy(vals, j * interval, currentVal, 0, interval);
            final int rVal = processPipes(pipes, currentVal);
            if (rVal != Integer.MIN_VALUE) {
                indices[j] = rVal;
            }
        }

        if (_optimizeMeshes) {
            final VertMap map = _geometryTool.minimizeVerts(lineMesh, _optimizeSettings);
            _dataCache.setMeshVertMap(lineMesh, map);
        }

        lineMesh.updateModelBound();

        return lineMesh;
    }

    /**
     * Extract our pipes from the given parent element.
     * 
     * @param inputsParent
     * @param pipesStore
     *            the store for our pipes
     * @return the max offset of our pipes.
     */
    private int extractPipes(final Element inputsParent, final LinkedList<ColladaInputPipe> pipesStore) {
        int maxOffset = 0;
        int texCount = 0;
        for (final Element input : inputsParent.getChildren("input")) {
            maxOffset = Math.max(maxOffset, _colladaDOMUtil.getAttributeIntValue(input, "offset", 0));
            try {
                final Type type = Type.valueOf(input.getAttributeValue("semantic"));
                if (type == Type.VERTEX) {
                    final Element vertexElement = _colladaDOMUtil
                            .findTargetWithId(input.getAttributeValue("source"));
                    for (final Element vertexInput : vertexElement.getChildren("input")) {
                        vertexInput.setAttribute("offset", input.getAttributeValue("offset"));
                        vertexInput.setAttribute("isVertexDefined", "true");
                        final ColladaInputPipe pipe = new ColladaInputPipe(_colladaDOMUtil, vertexInput);
                        if (pipe.getType() == Type.TEXCOORD) {
                            pipe.setTexCoord(texCount++);
                        }
                        pipesStore.add(pipe);
                    }
                } else {
                    final ColladaInputPipe pipe = new ColladaInputPipe(_colladaDOMUtil, input);
                    if (pipe.getType() == Type.TEXCOORD) {
                        pipe.setTexCoord(texCount++);
                    }
                    pipesStore.add(pipe);
                }
            } catch (final Exception ex) {
                logger.warning("Unknown input type: " + input.getAttributeValue("semantic"));
                continue;
            }
        }
        return maxOffset;
    }

    /**
     * Push the values at the given indices of currentVal onto the buffers defined in pipes.
     * 
     * @param pipes
     * @param currentVal
     * @return the vertex index referenced in the given indices based on the pipes. Integer.MIN_VALUE is returned if no
     *         vertex pipe is found.
     */
    private int processPipes(final LinkedList<ColladaInputPipe> pipes, final int[] currentVal) {
        // go through our pipes. use the indices in currentVal to pull the correct float val
        // from our source and set into our buffer.
        int rVal = Integer.MIN_VALUE;
        for (final ColladaInputPipe pipe : pipes) {
            pipe.pushValues(currentVal[pipe.getOffset()]);
            if (pipe.getType() == Type.POSITION) {
                rVal = currentVal[pipe.getOffset()];
            }
        }
        return rVal;
    }

    /**
     * Extract name from xml element, some exporters don't support 'name' attribute, so we better use the material
     * instead of a generic name.
     * 
     * @param element
     * @return value from 'name' or 'material' attribute
     */
    private String extractName(final Element colladaGeometry, final Element element) {
        // Try to get mesh name
        String name = element.getAttributeValue("name");
        if (name == null || name.isEmpty()) {
            // No mesh name found, try to get mesh id
            name = element.getAttributeValue("id");
        }
        if (name == null || name.isEmpty()) {
            // No mesh name or id found, try to get parent geometry name
            name = colladaGeometry.getAttributeValue("name");
            if (name == null || name.isEmpty()) {
                // No parent geometry name found, try to get geometry id (mandatory according to spec)
                name = colladaGeometry.getAttributeValue("id");
            }
            if (name == null) {
                name = "";
            }

            // Since we have retrieved the parent geometry name/id, we append the material(if any),
            // to make identification unique.
            final String materialName = element.getAttributeValue("material");
            if (materialName != null && !materialName.isEmpty()) {
                name += "[" + materialName + "]";
            }
        }

        return name;
    }
}