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

Java tutorial

Introduction

Here is the source code for com.ardor3d.extension.model.collada.jdom.ColladaImporter.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.io.IOException;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.logging.Logger;

import org.jdom2.Attribute;
import org.jdom2.DataConversionException;
import org.jdom2.DefaultJDOMFactory;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMFactory;
import org.jdom2.Namespace;
import org.jdom2.Text;
import org.jdom2.input.SAXBuilder;
import org.jdom2.input.sax.SAXHandler;
import org.jdom2.input.sax.SAXHandlerFactory;
import org.xml.sax.SAXException;

import com.ardor3d.extension.animation.skeletal.Joint;
import com.ardor3d.extension.model.collada.jdom.data.AssetData;
import com.ardor3d.extension.model.collada.jdom.data.ColladaStorage;
import com.ardor3d.extension.model.collada.jdom.data.DataCache;
import com.ardor3d.extension.model.collada.jdom.plugin.ColladaExtraPlugin;
import com.ardor3d.scenegraph.MeshData;
import com.ardor3d.scenegraph.Node;
import com.ardor3d.util.geom.GeometryTool;
import com.ardor3d.util.geom.GeometryTool.MatchCondition;
import com.ardor3d.util.resource.RelativeResourceLocator;
import com.ardor3d.util.resource.ResourceLocator;
import com.ardor3d.util.resource.ResourceLocatorTool;
import com.ardor3d.util.resource.ResourceSource;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;

/**
 * Main class for importing Collada files.
 * <p>
 * Example usages:
 * <li>new ColladaImporter().load(resource);</li>
 * <li>new ColladaImporter().loadTextures(false).modelLocator(locator).load(resource);</li>
 * </p>
 */
public class ColladaImporter {
    private boolean _loadTextures = true;
    private boolean _flipTransparency = false;
    private boolean _loadAnimations = true;
    private ResourceLocator _textureLocator;
    private ResourceLocator _modelLocator;
    private boolean _compressTextures = false;
    private boolean _optimizeMeshes = false;
    private final EnumSet<MatchCondition> _optimizeSettings = EnumSet.of(MatchCondition.UVs, MatchCondition.Normal,
            MatchCondition.Color);
    private Map<String, Joint> _externalJointMapping;
    private final List<ColladaExtraPlugin> _extraPlugins = Lists.newArrayList();

    public boolean isLoadTextures() {
        return _loadTextures;
    }

    public ColladaImporter setLoadTextures(final boolean loadTextures) {
        _loadTextures = loadTextures;
        return this;
    }

    public boolean isCompressTextures() {
        return _compressTextures;
    }

    public ColladaImporter setCompressTextures(final boolean compressTextures) {
        _compressTextures = compressTextures;
        return this;
    }

    public boolean isLoadAnimations() {
        return _loadAnimations;
    }

    public ColladaImporter setLoadAnimations(final boolean loadAnimations) {
        _loadAnimations = loadAnimations;
        return this;
    }

    public boolean isFlipTransparency() {
        return _flipTransparency;
    }

    public void addExtraPlugin(final ColladaExtraPlugin plugin) {
        _extraPlugins.add(plugin);
    }

    public void clearExtraPlugins() {
        _extraPlugins.clear();
    }

    /**
     * @param flipTransparency
     *            if true, invert the value of any "transparency" entries found - required for some exporters.
     * @return this importer, for chaining
     */
    public ColladaImporter setFlipTransparency(final boolean flipTransparency) {
        _flipTransparency = flipTransparency;
        return this;
    }

    public ResourceLocator getTextureLocator() {
        return _textureLocator;
    }

    public ColladaImporter setTextureLocator(final ResourceLocator textureLocator) {
        _textureLocator = textureLocator;
        return this;
    }

    public ColladaImporter setExternalJoints(final Map<String, Joint> map) {
        _externalJointMapping = map;
        return this;
    }

    public Map<String, Joint> getExternalJoints() {
        return _externalJointMapping;
    }

    public ResourceLocator getModelLocator() {
        return _modelLocator;
    }

    public ColladaImporter setModelLocator(final ResourceLocator modelLocator) {
        _modelLocator = modelLocator;
        return this;
    }

    public boolean isOptimizeMeshes() {
        return _optimizeMeshes;
    }

    public void setOptimizeMeshes(final boolean optimizeMeshes) {
        _optimizeMeshes = optimizeMeshes;
    }

    public Set<MatchCondition> getOptimizeSettings() {
        return ImmutableSet.copyOf(_optimizeSettings);
    }

    public void setOptimizeSettings(final MatchCondition... optimizeSettings) {
        _optimizeSettings.clear();
        for (final MatchCondition cond : optimizeSettings) {
            _optimizeSettings.add(cond);
        }
    }

    /**
     * Reads a Collada file from the given resource and returns it as a ColladaStorage object.
     * 
     * @param resource
     *            the name of the resource to find. ResourceLocatorTool will be used with TYPE_MODEL to find the
     *            resource.
     * @return a ColladaStorage data object containing the Collada scene and other useful elements.
     * @throws IOException
     *             if the resource can not be located or loaded for some reason.
     */
    public ColladaStorage load(final String resource) throws IOException {
        final ResourceSource source;
        if (_modelLocator == null) {
            source = ResourceLocatorTool.locateResource(ResourceLocatorTool.TYPE_MODEL, resource);
        } else {
            source = _modelLocator.locateResource(resource);
        }

        if (source == null) {
            throw new IOException("Unable to locate '" + resource + "'");
        }

        return load(source);
    }

    /**
     * Reads a Collada file from the given resource and returns it as a ColladaStorage object.
     * 
     * @param resource
     *            the name of the resource to find.
     * @return a ColladaStorage data object containing the Collada scene and other useful elements.
     * @throws IOException
     *             if the resource can not be loaded for some reason.
     */
    public ColladaStorage load(final ResourceSource resource) throws IOException {
        return load(resource, new GeometryTool());
    }

    /**
     * Reads a Collada file from the given resource and returns it as a ColladaStorage object.
     * 
     * @param resource
     *            the name of the resource to find.
     * @param geometryTool
     *            the geometry tool used to minimize the vertex count.
     * @return a ColladaStorage data object containing the Collada scene and other useful elements.
     * @throws IOException
     *             if the resource can not be loaded for some reason.
     */
    public ColladaStorage load(final ResourceSource resource, final GeometryTool geometryTool) throws IOException {
        final ColladaStorage colladaStorage = new ColladaStorage();
        final DataCache dataCache = new DataCache();
        if (_externalJointMapping != null) {
            dataCache.getExternalJointMapping().putAll(_externalJointMapping);
        }
        final ColladaDOMUtil colladaDOMUtil = new ColladaDOMUtil(dataCache);
        final ColladaMaterialUtils colladaMaterialUtils = new ColladaMaterialUtils(this, dataCache, colladaDOMUtil);
        final ColladaMeshUtils colladaMeshUtils = new ColladaMeshUtils(dataCache, colladaDOMUtil,
                colladaMaterialUtils, _optimizeMeshes, _optimizeSettings, geometryTool);
        final ColladaAnimUtils colladaAnimUtils = new ColladaAnimUtils(colladaStorage, dataCache, colladaDOMUtil,
                colladaMeshUtils);
        final ColladaNodeUtils colladaNodeUtils = new ColladaNodeUtils(dataCache, colladaDOMUtil,
                colladaMaterialUtils, colladaMeshUtils, colladaAnimUtils);

        try {
            // Pull in the DOM tree of the Collada resource.
            final Element collada = readCollada(resource, dataCache);

            // if we don't specify a texture locator, add a temporary texture locator at the location of this model
            // resource..
            final boolean addLocator = _textureLocator == null;

            final RelativeResourceLocator loc;
            if (addLocator) {
                loc = new RelativeResourceLocator(resource);
                ResourceLocatorTool.addResourceLocator(ResourceLocatorTool.TYPE_TEXTURE, loc);
            } else {
                loc = null;
            }

            final AssetData assetData = colladaNodeUtils.parseAsset(collada.getChild("asset"));

            // Collada may or may not have a scene, so this can return null.
            final Node scene = colladaNodeUtils.getVisualScene(collada);

            if (_loadAnimations) {
                colladaAnimUtils.parseLibraryAnimations(collada);
            }

            // reattach attachments to scene
            if (scene != null) {
                colladaNodeUtils.reattachAttachments(scene);
            }

            // set our scene into storage
            colladaStorage.setScene(scene);

            // set our asset data into storage
            colladaStorage.setAssetData(assetData);

            // drop our added locator if needed.
            if (addLocator) {
                ResourceLocatorTool.removeResourceLocator(ResourceLocatorTool.TYPE_TEXTURE, loc);
            }

            // copy across our mesh colors - only for objects with multiple channels
            final Multimap<MeshData, FloatBuffer> colors = ArrayListMultimap.create();
            final Multimap<MeshData, FloatBuffer> temp = dataCache.getParsedVertexColors();
            for (final MeshData key : temp.keySet()) {
                // only copy multiple channels since their data is lost
                final Collection<FloatBuffer> val = temp.get(key);
                if (val != null && val.size() > 1) {
                    colors.putAll(key, val);
                }
            }
            colladaStorage.setParsedVertexColors(colors);

            // copy across our mesh material info
            colladaStorage.setMeshMaterialInfo(dataCache.getMeshMaterialMap());
            colladaStorage.setMaterialMap(dataCache.getMaterialInfoMap());

            // return storage
            return colladaStorage;
        } catch (final Exception e) {
            throw new IOException("Unable to load collada resource from URL: " + resource, e);
        }
    }

    /**
     * Reads the whole Collada DOM tree from the given resource and returns its root element. Exceptions may be thrown
     * by underlying tools; these will be wrapped in a RuntimeException and rethrown.
     * 
     * @param resource
     *            the ResourceSource to read the resource from
     * @return the Collada root element
     */
    private Element readCollada(final ResourceSource resource, final DataCache dataCache) {
        try {
            final JDOMFactory jdomFac = new ArdorFactory(dataCache);
            final SAXBuilder builder = new SAXBuilder(null, new SAXHandlerFactory() {
                @Override
                public SAXHandler createSAXHandler(final JDOMFactory factory) {
                    return new SAXHandler(jdomFac) {
                        @Override
                        public void startPrefixMapping(final String prefix, final String uri) throws SAXException {
                            // Just kill what's usually done here...
                        }
                    };
                }
            }, jdomFac);

            final Document doc = builder.build(resource.openStream());
            final Element collada = doc.getRootElement();

            // ColladaDOMUtil.stripNamespace(collada);

            return collada;
        } catch (final Exception e) {
            throw new RuntimeException("Unable to load collada resource from source: " + resource, e);
        }
    }

    private enum BufferType {
        None, Float, Double, Int, String, P
    }

    /**
     * A JDOMFactory that normalizes all text (strips extra whitespace etc), preparses all arrays and hashes all
     * elements based on their id/sid.
     */
    private static final class ArdorFactory extends DefaultJDOMFactory {
        private final Logger logger = Logger.getLogger(ArdorFactory.class.getName());

        private final DataCache dataCache;
        private Element currentElement;
        private BufferType bufferType = BufferType.None;
        private int count = 0;
        private final List<String> list = new ArrayList<String>();

        ArdorFactory(final DataCache dataCache) {
            this.dataCache = dataCache;
        }

        @Override
        public Text text(final int line, final int col, final String text) {
            try {
                switch (bufferType) {
                case Float: {
                    final String normalizedText = Text.normalizeString(text);
                    if (normalizedText.length() == 0) {
                        return new Text("");
                    }
                    final StringTokenizer tokenizer = new StringTokenizer(normalizedText, " ");
                    final float[] floatArray = new float[count];
                    for (int i = 0; i < count; i++) {
                        floatArray[i] = parseFloat(tokenizer.nextToken());
                    }

                    dataCache.getFloatArrays().put(currentElement, floatArray);

                    return new Text("");
                }
                case Double: {
                    final String normalizedText = Text.normalizeString(text);
                    if (normalizedText.length() == 0) {
                        return new Text("");
                    }
                    final StringTokenizer tokenizer = new StringTokenizer(normalizedText, " ");
                    final double[] doubleArray = new double[count];
                    for (int i = 0; i < count; i++) {
                        doubleArray[i] = Double.parseDouble(tokenizer.nextToken().replace(",", "."));
                    }

                    dataCache.getDoubleArrays().put(currentElement, doubleArray);

                    return new Text("");
                }
                case Int: {
                    final String normalizedText = Text.normalizeString(text);
                    if (normalizedText.length() == 0) {
                        return new Text("");
                    }
                    final StringTokenizer tokenizer = new StringTokenizer(normalizedText, " ");
                    final int[] intArray = new int[count];
                    int i = 0;
                    while (tokenizer.hasMoreTokens()) {
                        intArray[i++] = Integer.parseInt(tokenizer.nextToken());
                    }

                    dataCache.getIntArrays().put(currentElement, intArray);

                    return new Text("");
                }
                case P: {
                    list.clear();
                    final String normalizedText = Text.normalizeString(text);
                    if (normalizedText.length() == 0) {
                        return new Text("");
                    }
                    final StringTokenizer tokenizer = new StringTokenizer(normalizedText, " ");
                    while (tokenizer.hasMoreTokens()) {
                        list.add(tokenizer.nextToken());
                    }
                    final int listSize = list.size();
                    final int[] intArray = new int[listSize];
                    for (int i = 0; i < listSize; i++) {
                        intArray[i] = Integer.parseInt(list.get(i));
                    }

                    dataCache.getIntArrays().put(currentElement, intArray);

                    return new Text("");
                }
                default:
                    break;
                }
            } catch (final NoSuchElementException e) {
                throw new ColladaException(
                        "Number of values in collada array does not match its count attribute: " + count, e);
            }
            return new Text(Text.normalizeString(text));
        }

        @Override
        public void setAttribute(final Element parent, final Attribute a) {
            if ("id".equals(a.getName())) {
                if (dataCache.getIdCache().containsKey(a.getValue())) {
                    logger.warning("id already exists in id cache: " + a.getValue());
                }
                dataCache.getIdCache().put(a.getValue(), parent);
            } else if ("sid".equals(a.getName())) {
                dataCache.getSidCache().put(a.getValue(), parent);
            } else if ("count".equals(a.getName())) {
                try {
                    count = a.getIntValue();
                } catch (final DataConversionException e) {
                    e.printStackTrace();
                }
            }

            super.setAttribute(parent, a);
        }

        @Override
        public Element element(final int line, final int col, final String name, final Namespace namespace) {
            currentElement = super.element(line, col, name);
            handleTypes(name);
            return currentElement;
        }

        @Override
        public Element element(final int line, final int col, final String name, final String prefix,
                final String uri) {
            currentElement = super.element(line, col, name);
            handleTypes(name);
            return currentElement;
        }

        @Override
        public Element element(final int line, final int col, final String name, final String uri) {
            currentElement = super.element(line, col, name);
            handleTypes(name);
            return currentElement;
        }

        @Override
        public Element element(final int line, final int col, final String name) {
            currentElement = super.element(line, col, name);
            handleTypes(name);
            return currentElement;
        }

        private void handleTypes(final String name) {
            if ("float_array".equals(name)) {
                bufferType = BufferType.Float;
            } else if ("double_array".equals(name)) {
                bufferType = BufferType.Double;
            } else if ("int_array".equals(name)) {
                bufferType = BufferType.Int;
            } else if ("p".equals(name)) {
                bufferType = BufferType.P;
            } else {
                bufferType = BufferType.None;
            }
        }
    }

    /**
     * Parse a numeric value. Commas are replaced by dot automaticly. Also handle special values : INF, -INF, NaN
     * 
     * @param number
     *            string
     * @return float
     */
    public static float parseFloat(String candidate) {
        candidate = candidate.replace(',', '.');
        if (candidate.contains("-INF")) {
            return Float.NEGATIVE_INFINITY;
        } else if (candidate.contains("INF")) {
            return Float.POSITIVE_INFINITY;
        }
        return Float.parseFloat(candidate);
    }

    public boolean readExtra(final Element extra, final Object... params) {
        for (final ColladaExtraPlugin plugin : _extraPlugins) {
            if (plugin.processExtra(extra, params)) {
                return true;
            }
        }
        return false;
    }
}