From 8f29f486761e465faffb7c706b04710080ac3691 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Thu, 29 Jan 2026 16:42:03 +0100 Subject: [PATCH 01/14] Add extension loader for Draco --- .../plugins/gltf/CustomContentManager.java | 1 + .../DracoMeshCompressionExtensionLoader.java | 564 ++++++++++++++++++ .../jme3/scene/plugins/gltf/GltfLoader.java | 52 +- .../jme3/scene/plugins/gltf/GltfUtils.java | 32 +- 4 files changed, 641 insertions(+), 8 deletions(-) create mode 100644 jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/DracoMeshCompressionExtensionLoader.java diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/CustomContentManager.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/CustomContentManager.java index 20f2c5e141..8606667eb5 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/CustomContentManager.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/CustomContentManager.java @@ -63,6 +63,7 @@ public class CustomContentManager { defaultExtensionLoaders.put("KHR_materials_unlit", UnlitExtensionLoader.class); defaultExtensionLoaders.put("KHR_texture_transform", TextureTransformExtensionLoader.class); defaultExtensionLoaders.put("KHR_materials_emissive_strength", PBREmissiveStrengthExtensionLoader.class); + defaultExtensionLoaders.put("KHR_draco_mesh_compression", DracoMeshCompressionExtensionLoader.class); } diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/DracoMeshCompressionExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/DracoMeshCompressionExtensionLoader.java new file mode 100644 index 0000000000..bfdaceeb7b --- /dev/null +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/DracoMeshCompressionExtensionLoader.java @@ -0,0 +1,564 @@ +/* + * Copyright (c) 2009-2026 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.gltf; + +import static com.jme3.scene.plugins.gltf.GltfUtils.assertNotNull; +import static com.jme3.scene.plugins.gltf.GltfUtils.getAsInt; +import static com.jme3.scene.plugins.gltf.GltfUtils.getAsInteger; +import static com.jme3.scene.plugins.gltf.GltfUtils.getAsString; +import static com.jme3.scene.plugins.gltf.GltfUtils.getNumberOfComponents; +import static com.jme3.scene.plugins.gltf.GltfUtils.getVertexBufferFormat; +import static com.jme3.scene.plugins.gltf.GltfUtils.getVertexBufferType; + +import java.io.IOException; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; +import java.util.Map.Entry; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.jme3.asset.AssetLoadException; +import com.jme3.plugins.json.JsonElement; +import com.jme3.plugins.json.JsonObject; +import com.jme3.renderer.opengl.GL; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.util.BufferUtils; + +import dev.fileformat.drako.Draco; +import dev.fileformat.drako.DracoMesh; +import dev.fileformat.drako.DrakoException; +import dev.fileformat.drako.PointAttribute; + +/** + * A class for handling the KHR_draco_mesh_compression extension + * when loading a glTF asset. + * + * It is registered as the handler for this extension in the glTF + * {@link CustomContentManager}. In the + * {@link GltfLoader#readMeshPrimitives(int)} method, the custom content handler + * will be called for each mesh primitive, and handle the + * KHR_draco_mesh_compression of the primitive by calling the + * {@link #handleExtension} method of this class. + * + * TODO_DRACO Strictly speaking, the loader should ignore any attribute + * definitions when the draco extension is present. Right now, this is called + * after the mesh was already filled with the vertex buffers that have been + * created by the default loading process. See the check for "bufferViewIndex == + * null" in VertexBufferPopulator. + */ +public class DracoMeshCompressionExtensionLoader implements ExtensionLoader { + + /** + * The logger used in this class + */ + private final static Logger logger = Logger.getLogger(DracoMeshCompressionExtensionLoader.class.getName()); + + /** + * The default log level + */ + private static final Level level = Level.INFO; + + /** + * + * + * {@inheritDoc} + */ + @Override + public Object handleExtension(GltfLoader loader, String parentName, JsonElement parent, JsonElement extension, + Object input) throws IOException { + + logger.log(level, "Decoding draco data"); + + JsonObject meshPrimitiveObject = parent.getAsJsonObject(); + JsonObject extensionObject = extension.getAsJsonObject(); + Mesh mesh = (Mesh) input; + + DracoMesh dracoMesh = readDracoMesh(loader, extension); + + // Fetch the indices, convert them into a vertex buffer, + // and replace the index vertex buffer of the mesh with + // the newly created buffer. + logger.log(level, "Decoding draco indices"); + int indices[] = dracoMesh.getIndices().toArray(); + int indicesAccessorIndex = getAsInt(meshPrimitiveObject, "mesh primitive", "indices"); + JsonObject indicesAccessor = loader.getAccessor(indicesAccessorIndex); + int indicesComponentType = getAsInt(indicesAccessor, "accessor " + indicesAccessorIndex, "componentType"); + VertexBuffer indicesVertexBuffer = createIndicesVertexBuffer(loader, indicesComponentType, indices); + mesh.clearBuffer(VertexBuffer.Type.Index); + mesh.setBuffer(indicesVertexBuffer); + + // Iterate over all attributes that are found in the + // "attributes" dictionary of the extension object. + // According to the specification, these must be + // a subset of the attributes of the mesh primitive. + JsonObject attributes = extensionObject.get("attributes").getAsJsonObject(); + JsonObject parentAttributes = meshPrimitiveObject.get("attributes").getAsJsonObject(); + for (Entry entry : attributes.entrySet()) { + String attributeName = entry.getKey(); + logger.log(level, "Decoding draco attribute " + attributeName); + + // The extension object stores the attribute ID, which + // is an identifier for the attribute in the decoded + // draco data. It is NOT an accessor index! + int attributeId = entry.getValue().getAsInt(); + PointAttribute pointAttribute = getAttribute(dracoMesh, attributeName, attributeId); + + logger.log(level, "attribute " + attributeName); + logger.log(level, "attributeId " + attributeId); + logger.log(level, "pointAttribute " + pointAttribute); + + // The mesh primitive stores the accessor index for + // each attribute + int attributeAccessorIndex = getAsInt(parentAttributes, attributeName + " attribute", attributeName); + JsonObject accessor = loader.getAccessor(attributeAccessorIndex); + + logger.log(level, "attributeAccessorIndex " + attributeAccessorIndex); + logger.log(level, "accessor " + accessor); + + // Replace the buffer in the mesh with a buffer that was + // created from the data that was fetched from the + // decoded draco PointAttribute + Type bufferType = getVertexBufferType(attributeName); + VertexBuffer attributeVertexBuffer = createAttributeVertexBuffer(attributeName, accessor, pointAttribute, + indices); + mesh.clearBuffer(bufferType); + mesh.setBuffer(attributeVertexBuffer); + } + + logger.log(level, "Decoding draco data DONE"); + return mesh; + } + + /** + * Read the draco data from the given extension, using + * openize-drako-java. + * + * @param loader The glTF loader + * @param extension The draco extension object that was found in a mesh + * primitive + * @return The Draco mesh + * @throws IOException If attempting to load the underlying buffer causes an IO + * error + */ + private static DracoMesh readDracoMesh(GltfLoader loader, JsonElement extension) throws IOException { + logger.log(level, "Decoding draco mesh"); + + JsonObject jsonObject = extension.getAsJsonObject(); + int bufferViewIndex = getAsInt(jsonObject, "Draco extension object", "bufferView"); + + ByteBuffer bufferViewData = obtainBufferViewData(loader, bufferViewIndex); + + byte bufferViewDataArray[] = new byte[bufferViewData.remaining()]; + bufferViewData.slice().get(bufferViewDataArray); + DracoMesh dracoMesh = null; + try { + dracoMesh = (DracoMesh) Draco.decode(bufferViewDataArray); + } catch (DrakoException e) { + throw new AssetLoadException("Could not decode Draco mesh from buffer view " + bufferViewIndex, e); + } + + logger.log(level, "Decoding draco mesh DONE"); + return dracoMesh; + } + + /** + * Create the indices vertex buffer that should go into the mesh, based on the + * given Draco-decoded indices + * + * @param loader The glTF loader + * @param accessorIndex The accessor index of the vertices + * @param indices The Draco-decoded indices + * @return The indices vertex buffer + * @throws AssetLoadException If the given component type is not + * GL_UNSIGNED_BYTE, + * GL_UNSIGNED_SHORT, or + * GL_UNSIGNED_INT + */ + VertexBuffer createIndicesVertexBuffer(GltfLoader loader, int componentType, int indices[]) { + Buffer data = null; + if (componentType == GL.GL_UNSIGNED_BYTE) { + data = createByteBuffer(indices); + } else if (componentType == GL.GL_UNSIGNED_SHORT) { + data = createShortBuffer(indices); + } else if (componentType == GL.GL_UNSIGNED_INT) { + data = BufferUtils.createIntBuffer(indices); + } else { + throw new AssetLoadException("The indices accessor must have a component type of " + GL.GL_UNSIGNED_BYTE + + ", " + GL.GL_UNSIGNED_SHORT + ", or " + GL.GL_UNSIGNED_INT + ", but has " + componentType); + } + VertexBuffer vb = new VertexBuffer(VertexBuffer.Type.Index); + VertexBuffer.Format format = getVertexBufferFormat(componentType); + int numComponents = 3; + vb.setupData(VertexBuffer.Usage.Dynamic, numComponents, format, data); + return vb; + } + + // TODO_DRACO Could go into GltfUtils + /** + * Determines the number of components per element for the given accessor, based + * on its type + * + * @param accessor The accessor + * @return The number of components + * @throws AssetLoadException If the accessor does not have a valid + * type property + */ + private static int getAccessorComponentCount(JsonObject accessor) { + String type = getAsString(accessor, "type"); + assertNotNull(type, "No type attribute defined for accessor"); + return getNumberOfComponents(type); + } + + // TODO_DRACO Could go into BufferUtils + /** + * Create a byte buffer containing the given values, cast to byte + * + * @param array The array + * @return The buffer + */ + private static Buffer createByteBuffer(int[] array) { + ByteBuffer buffer = BufferUtils.createByteBuffer(array.length); + for (int i = 0; i < array.length; i++) { + buffer.put(i, (byte) array[i]); + } + return buffer; + } + + // TODO_DRACO Could go into BufferUtils + /** + * Create a short buffer containing the given values, cast to short + * + * @param array The array + * @return The buffer + */ + private static Buffer createShortBuffer(int[] array) { + ShortBuffer buffer = BufferUtils.createShortBuffer(array.length); + for (int i = 0; i < array.length; i++) { + buffer.put(i, (short) array[i]); + } + return buffer; + } + + // TODO_DRACO Could fit into GltfLoader + /** + * Obtain the data for the specified buffer view of the given loader. + * + * This will return a slice of the data of the underlying buffer. Callers may + * not modify the returned data. + * + * @param loader The loader + * @param bufferViewIndex The buffer view index + * @return The buffer view data + * @throws IOException If attempting to load the underlying buffer causes + * an IO error + * @throws AssetLoadException If the specified index is not valid, or the buffer + * view did not define a valid buffer index or byte + * length + */ + private static ByteBuffer obtainBufferViewData(GltfLoader loader, int bufferViewIndex) throws IOException { + JsonObject bufferView = loader.getBufferView(bufferViewIndex); + int bufferIndex = getAsInt(bufferView, "bufferView", "buffer"); + assertNotNull(bufferIndex, "No buffer defined for bufferView " + bufferViewIndex); + + int byteOffset = getAsInteger(bufferView, "byteOffset", 0); + int byteLength = getAsInt(bufferView, "bufferView " + bufferViewIndex, "byteLength"); + + ByteBuffer bufferData = loader.readData(bufferIndex); + ByteBuffer bufferViewData = bufferData.slice(); + bufferViewData.limit(byteOffset + byteLength); + bufferViewData.position(byteOffset); + return bufferViewData; + } + + /** + * Obtains the point attribute with the given ID from the given draco mesh. + * + * @param dracoMesh The draco mesh + * @param gltfAttribute The glTF attribute name, like "POSITION" + * (only used for error messages) + * @param id The unique ID of the attribute, i.e. the value that was + * stored as the "POSITION": id in the draco + * extension JSON object. + * @return The point attribute + * @throws AssetLoadException If the attribute with the given ID cannot be found + */ + private static PointAttribute getAttribute(DracoMesh dracoMesh, String gltfAttribute, int id) { + for (int i = 0; i < dracoMesh.getNumAttributes(); i++) { + PointAttribute attribute = dracoMesh.attribute(i); + if (attribute.getUniqueId() == id) { + return attribute; + } + } + throw new AssetLoadException( + "Could not obtain attribute " + gltfAttribute + " with unique ID " + id + " from decoded Draco mesh"); + } + + /** + * Creates a vertex buffer for the specified attribute, according to the + * structure that is described by the given accessor JSON object, using the data + * that is obtained from the given Draco-decoded point attribute + * + * @param attributeName The attribute name + * @param accessor The accessor JSON object + * @param pointAttribute The Draco-decoded point attribute + * @param indices The indices, obtained from the draco mesh + * @return The vertex buffer + * @throws AssetLoadException If the given accessor does not have a component + * type that is valid for a vertex attribute + */ + private static VertexBuffer createAttributeVertexBuffer(String attributeName, JsonObject accessor, + PointAttribute pointAttribute, int indices[]) { + int count = getAsInt(accessor, "accessor", "count"); + int componentType = getAsInt(accessor, "accessor", "componentType"); + int componentCount = getAccessorComponentCount(accessor); + Type bufferType = getVertexBufferType(attributeName); + + if (componentType == GL.GL_BYTE || componentType == GL.GL_UNSIGNED_BYTE) { + ByteBuffer attributeData = readByteDracoAttribute(pointAttribute, indices, count, componentCount); + VertexBuffer attributeVertexBuffer = createByteAttributeVertexBuffer(accessor, bufferType, attributeData); + return attributeVertexBuffer; + } + if (componentType == GL.GL_SHORT || componentType == GL.GL_UNSIGNED_SHORT) { + ShortBuffer attributeData = readShortDracoAttribute(pointAttribute, indices, count, componentCount); + VertexBuffer attributeVertexBuffer = createShortAttributeVertexBuffer(accessor, bufferType, attributeData); + return attributeVertexBuffer; + } + if (componentType == GL.GL_FLOAT) { + FloatBuffer attributeData = readFloatDracoAttribute(pointAttribute, indices, count, componentCount); + VertexBuffer attributeVertexBuffer = createFloatAttributeVertexBuffer(accessor, bufferType, attributeData); + return attributeVertexBuffer; + } + throw new AssetLoadException("The accessor for attribute " + attributeName + " must have a component type of " + + GL.GL_BYTE + ", " + GL.GL_UNSIGNED_BYTE + ", " + GL.GL_SHORT + ", " + GL.GL_UNSIGNED_SHORT + ", " + + "or " + GL.GL_FLOAT + ", but has " + componentType); + } + + /** + * Read the data from the given point attribute, as byte values + * + * @param pointAttribute The Draco-decoded point attribute + * @param indices The indices, obtained from the draco mesh + * @param count The count, obtained from the accessor for this + * attribute + * @param componentCount The component count (number of components per element), + * obtained from the accessor type + * @return The resulting data, as a byte buffer + */ + private static ByteBuffer readByteDracoAttribute(PointAttribute pointAttribute, int indices[], int count, + int componentCount) { + int numFaces = indices.length / 3; + byte p[] = new byte[componentCount]; + ByteBuffer attributeData = BufferUtils.createByteBuffer(count * componentCount); + for (int i = 0; i < numFaces; i++) { + int j0 = indices[i * 3 + 0]; + int j1 = indices[i * 3 + 1]; + int j2 = indices[i * 3 + 2]; + + int mj0 = pointAttribute.mappedIndex(j0); + int mj1 = pointAttribute.mappedIndex(j1); + int mj2 = pointAttribute.mappedIndex(j2); + + pointAttribute.getValue(mj0, p); + int offset0 = j0 * componentCount; + for (int c = 0; c < componentCount; c++) { + attributeData.put(offset0 + c, p[c]); + } + pointAttribute.getValue(mj1, p); + int offset1 = j1 * componentCount; + for (int c = 0; c < componentCount; c++) { + attributeData.put(offset1 + c, p[c]); + } + pointAttribute.getValue(mj2, p); + int offset2 = j2 * componentCount; + for (int c = 0; c < componentCount; c++) { + attributeData.put(offset2 + c, p[c]); + } + } + return attributeData; + } + + /** + * Read the data from the given point attribute, as short values + * + * @param pointAttribute The Draco-decoded point attribute + * @param indices The indices, obtained from the draco mesh + * @param count The count, obtained from the accessor for this + * attribute + * @param componentCount The component count (number of components per element), + * obtained from the accessor type + * @return The resulting data, as a short buffer + */ + private static ShortBuffer readShortDracoAttribute(PointAttribute pointAttribute, int indices[], int count, + int componentCount) { + int numFaces = indices.length / 3; + short p[] = new short[componentCount]; + ShortBuffer attributeData = BufferUtils.createShortBuffer(count * componentCount); + for (int i = 0; i < numFaces; i++) { + int j0 = indices[i * 3 + 0]; + int j1 = indices[i * 3 + 1]; + int j2 = indices[i * 3 + 2]; + + int mj0 = pointAttribute.mappedIndex(j0); + int mj1 = pointAttribute.mappedIndex(j1); + int mj2 = pointAttribute.mappedIndex(j2); + + pointAttribute.getValue(mj0, p); + int offset0 = j0 * componentCount; + for (int c = 0; c < componentCount; c++) { + attributeData.put(offset0 + c, p[c]); + } + pointAttribute.getValue(mj1, p); + int offset1 = j1 * componentCount; + for (int c = 0; c < componentCount; c++) { + attributeData.put(offset1 + c, p[c]); + } + pointAttribute.getValue(mj2, p); + int offset2 = j2 * componentCount; + for (int c = 0; c < componentCount; c++) { + attributeData.put(offset2 + c, p[c]); + } + } + return attributeData; + } + + /** + * Read the data from the given point attribute, as float values + * + * @param pointAttribute The Draco-decoded point attribute + * @param indices The indices, obtained from the draco mesh + * @param count The count, obtained from the accessor for this + * attribute + * @param componentCount The component count (number of components per element), + * obtained from the accessor type + * @return The resulting data, as a float buffer + */ + private static FloatBuffer readFloatDracoAttribute(PointAttribute pointAttribute, int indices[], int count, + int componentCount) { + int numFaces = indices.length / 3; + float p[] = new float[componentCount]; + FloatBuffer attributeData = BufferUtils.createFloatBuffer(count * componentCount); + for (int i = 0; i < numFaces; i++) { + int j0 = indices[i * 3 + 0]; + int j1 = indices[i * 3 + 1]; + int j2 = indices[i * 3 + 2]; + + int mj0 = pointAttribute.mappedIndex(j0); + int mj1 = pointAttribute.mappedIndex(j1); + int mj2 = pointAttribute.mappedIndex(j2); + + pointAttribute.getValue(mj0, p); + int offset0 = j0 * componentCount; + for (int c = 0; c < componentCount; c++) { + attributeData.put(offset0 + c, p[c]); + } + pointAttribute.getValue(mj1, p); + int offset1 = j1 * componentCount; + for (int c = 0; c < componentCount; c++) { + attributeData.put(offset1 + c, p[c]); + } + pointAttribute.getValue(mj2, p); + int offset2 = j2 * componentCount; + for (int c = 0; c < componentCount; c++) { + attributeData.put(offset2 + c, p[c]); + } + } + return attributeData; + } + + /** + * Create the vertex buffer for the given byte attribute data + * + * @param accessor The accessor that describes the component type and type + * + * @param bufferType The buffer type + * @param attributeData The attribute data + * @return The vertex buffer + */ + private static VertexBuffer createByteAttributeVertexBuffer(JsonObject accessor, VertexBuffer.Type bufferType, + ByteBuffer attributeData) { + int componentType = getAsInt(accessor, "accessor", "componentType"); + VertexBuffer vb = new VertexBuffer(bufferType); + VertexBuffer.Format format = getVertexBufferFormat(componentType); + int numComponents = getAccessorComponentCount(accessor); + vb.setupData(VertexBuffer.Usage.Dynamic, numComponents, format, attributeData); + return vb; + } + + /** + * Create the vertex buffer for the given short attribute data + * + * @param accessor The accessor that describes the component type and type + * + * @param bufferType The buffer type + * @param attributeData The attribute data + * @return The vertex buffer + */ + private static VertexBuffer createShortAttributeVertexBuffer(JsonObject accessor, VertexBuffer.Type bufferType, + ShortBuffer attributeData) { + int componentType = getAsInt(accessor, "accessor", "componentType"); + VertexBuffer vb = new VertexBuffer(bufferType); + VertexBuffer.Format format = getVertexBufferFormat(componentType); + int numComponents = getAccessorComponentCount(accessor); + vb.setupData(VertexBuffer.Usage.Dynamic, numComponents, format, attributeData); + return vb; + } + + /** + * Create the vertex buffer for the given float attribute data + * + * @param accessor The accessor that describes the component type and type + * + * @param bufferType The buffer type + * @param attributeData The attribute data + * @return The vertex buffer + */ + private static VertexBuffer createFloatAttributeVertexBuffer(JsonObject accessor, VertexBuffer.Type bufferType, + FloatBuffer attributeData) { + int componentType = getAsInt(accessor, "accessor", "componentType"); + VertexBuffer vb = new VertexBuffer(bufferType); + VertexBuffer.Format format = getVertexBufferFormat(componentType); + int numComponents = getAccessorComponentCount(accessor); + vb.setupData(VertexBuffer.Usage.Dynamic, numComponents, format, attributeData); + return vb; + } + +} diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java index 0c8448e226..08638010d4 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java @@ -622,9 +622,59 @@ public Buffer viewBuffer(Integer bufferViewIndex, int byteOffset, int count, } + /** + * Returns the JSON object that represents the buffer view with the specified + * index in the glTF JSON. + * + * @param index The buffer view index + * @return The buffer view as a JSON object + * @throws AssetLoadException If the index is negative or not smaller than the + * number of buffer views in the glTF JSON + */ + JsonObject getBufferView(int index) { + assertNotNull(bufferViews, "No buffer views when trying to access buffer view with index " + index); + validateIndex("bufferView", index, bufferViews.size()); + JsonObject bufferView = bufferViews.get(index).getAsJsonObject(); + return bufferView; + } + + /** + * Returns the JSON object that represents the accessor with the specified index + * in the glTF JSON. + * + * @param index The accessor index + * @return The accessor as a JSON object + * @throws AssetLoadException If the index is negative or not smaller than the + * number of accessors in the glTF JSON + */ + JsonObject getAccessor(int index) { + assertNotNull(accessors, "No accessors when trying to access accessor with index " + index); + validateIndex("accessor", index, accessors.size()); + JsonObject accessor = accessors.get(index).getAsJsonObject(); + return accessor; + } + + /** + * Ensure that the given index is valid for the specified size, and throw an + * exception of this is not the case. + * + * @param name The name of the index + * @param index The index + * @param size The size + * @throws AssetLoadException If the index is negative or not smaller than the + * size + */ + private static void validateIndex(String name, int index, int size) { + if (index < 0 || index >= size) { + throw new AssetLoadException( + "The " + name + " index must be positive and smaller than " + size + ", but is " + index); + } + } + public ByteBuffer readData(int bufferIndex) throws IOException { assertNotNull(buffers, "No buffer defined"); - + validateIndex("buffer", bufferIndex, buffers.size()); + JsonObject buffer = buffers.get(bufferIndex).getAsJsonObject(); String uri = getAsString(buffer, "uri"); Integer bufferLength = getAsInteger(buffer, "byteLength"); diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java index 7c6ee853f1..5ca33a33fa 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java @@ -36,10 +36,10 @@ import com.jme3.plugins.json.JsonObject; import com.jme3.asset.AssetInfo; import com.jme3.asset.AssetLoadException; -import com.jme3.export.binary.ByteUtils; import com.jme3.math.*; import com.jme3.plugins.json.Json; import com.jme3.plugins.json.JsonParser; +import com.jme3.renderer.opengl.GL; import com.jme3.scene.*; import com.jme3.texture.Texture; import com.jme3.util.*; @@ -105,17 +105,17 @@ public static Mesh.Mode getMeshMode(Integer mode) { public static VertexBuffer.Format getVertexBufferFormat(int componentType) { switch (componentType) { - case 5120: + case GL.GL_BYTE: return VertexBuffer.Format.Byte; - case 5121: + case GL.GL_UNSIGNED_BYTE: return VertexBuffer.Format.UnsignedByte; - case 5122: + case GL.GL_SHORT: return VertexBuffer.Format.Short; - case 5123: + case GL.GL_UNSIGNED_SHORT: return VertexBuffer.Format.UnsignedShort; - case 5125: + case GL.GL_UNSIGNED_INT: return VertexBuffer.Format.UnsignedInt; - case 5126: + case GL.GL_FLOAT: return VertexBuffer.Format.Float; default: throw new AssetLoadException("Illegal component type: " + componentType); @@ -724,6 +724,24 @@ public static Integer getAsInteger(JsonObject parent, String name) { return el == null ? null : el.getAsInt(); } + /** + * Returns the specified element from the given parent as an int, + * throwing an exception if it is not present. + * + * @param parent The parent element + * @param parentName The parent name + * @param name The name of the element + * @return The value, as an int + * @throws AssetLoadException If the element is not present + */ + public static int getAsInt(JsonObject parent, String parentName, String name) { + JsonElement el = parent.get(name); + if (el == null) { + throw new AssetLoadException("No " + name + " defined for " + parentName); + } + return el.getAsInt(); + } + public static Integer getAsInteger(JsonObject parent, String name, int defaultValue) { JsonElement el = parent.get(name); return el == null ? defaultValue : el.getAsInt(); From c63a548cc5a859bff569555f7e49baf8a5d0518f Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Thu, 29 Jan 2026 16:42:34 +0100 Subject: [PATCH 02/14] Add Draco glTF loading tests --- .../java/jme3test/model/TestGltfLoading.java | 62 ++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java b/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java index 4ece681a65..e1a08119b0 100644 --- a/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java +++ b/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java @@ -45,6 +45,7 @@ import com.jme3.scene.control.Control; import com.jme3.scene.debug.custom.ArmatureDebugAppState; import com.jme3.scene.plugins.gltf.GltfModelKey; + import jme3test.model.anim.EraseTimer; import java.util.*; @@ -134,8 +135,65 @@ public void simpleInitApp() { //loadModel("Models/gltf/crab/scene.gltf", Vector3f.ZERO, 1); //loadModel("Models/gltf/manta/scene.gltf", Vector3f.ZERO, 0.2f); //loadModel("Models/gltf/bone/scene.gltf", Vector3f.ZERO, 0.1f); -// loadModel("Models/gltf/box/box.gltf", Vector3f.ZERO, 1); - loadModel("Models/gltf/duck/Duck.gltf", new Vector3f(0, 1, 0), 1); + //loadModel("Models/gltf/box/box.gltf", Vector3f.ZERO, 1); + //loadModel("Models/gltf/duck/Duck.gltf", new Vector3f(0, 1, 0), 1); + + + // =================================================================== + // TODO_DRACO Draco test start + // The following test assumes that the "Models" directory from + // https://github.com/KhronosGroup/glTF-Sample-Assets/tree/main/Models + // is copied into the + // jme3-testdata\src\main\resources\Models\gltf\ + // directory (or at least the following list of models) + + // Comment in/out the model to test + String dracoTestModel = null; + //dracoTestModel = "Avocado"; + //dracoTestModel = "BarramundiFish"; + dracoTestModel = "BoomBox"; + //dracoTestModel = "BrainStem"; + //dracoTestModel = "CesiumMan"; + //dracoTestModel = "CesiumMilkTruck"; + //dracoTestModel = "Corset"; + //dracoTestModel = "Lantern"; + //dracoTestModel = "MorphPrimitivesTest"; + //dracoTestModel = "RiggedFigure"; + //dracoTestModel = "RiggedSimple"; + //dracoTestModel = "SunglassesKhronos"; + //dracoTestModel = "VirtualCity"; + //dracoTestModel = "WaterBottle"; + + boolean testDraco = true; + // Uncomment this to not load the Draco-compressed + // version, but the glTF-Binary version of the model + //testDraco = false; + String dracoTestFlavor = null; + String dracoTestExtension = null; + if (testDraco) + { + dracoTestFlavor = "glTF-Draco"; + dracoTestExtension = "gltf"; + } + else + { + dracoTestFlavor = "glTF-Binary"; + dracoTestExtension = "glb"; + } + // Assemble a path like + // "Models/gltf/Models/BoomBox/glTF-Draco/BoomBox.gltf" + String dracoTestPath = "Models/gltf/Models/" + dracoTestModel + "/" + dracoTestFlavor + "/" + dracoTestModel + "." + dracoTestExtension; + + System.out.println("Running Draco test with "+dracoTestPath); + loadModel(dracoTestPath, new Vector3f(0, 0, 0), 20.0f); + + // TODO_DRACO Draco test end + // =================================================================== + + + //loadModel("Models/gltf/BoomBox/glTF-Draco/BoomBox.gltf", new Vector3f(0, 0, 0), 20); + //loadModel("Models/gltf/BoomBox/glTF-Binary/BoomBox.glb", new Vector3f(0, 0, 0), 1); + // loadModel("Models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf", Vector3f.ZERO, 1); // loadModel("Models/gltf/hornet/scene.gltf", new Vector3f(0, -0.5f, 0), 0.4f); //// loadModel("Models/gltf/adamHead/adamHead.gltf", Vector3f.ZERO, 0.6f); From 77d611f47a60b221d54678f2d5cddc250aa55de1 Mon Sep 17 00:00:00 2001 From: Riccardo Balbo Date: Thu, 29 Jan 2026 17:27:11 +0100 Subject: [PATCH 03/14] add draco dependency --- jme3-plugins/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-plugins/build.gradle b/jme3-plugins/build.gradle index e84f234a68..2d2ca68b9d 100644 --- a/jme3-plugins/build.gradle +++ b/jme3-plugins/build.gradle @@ -11,7 +11,7 @@ sourceSets { dependencies { api project(':jme3-core') - + implementation "dev.fileformat:drako:1.4.2" implementation project(':jme3-plugins-json') implementation project(':jme3-plugins-json-gson') testRuntimeOnly project(':jme3-desktop') From 37d1d82883f021474be84369bccb85ab3b4f2f2f Mon Sep 17 00:00:00 2001 From: Riccardo Balbo Date: Thu, 29 Jan 2026 18:34:37 +0100 Subject: [PATCH 04/14] format --- .../DracoMeshCompressionExtensionLoader.java | 278 ++++++++++-------- 1 file changed, 156 insertions(+), 122 deletions(-) diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/DracoMeshCompressionExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/DracoMeshCompressionExtensionLoader.java index bfdaceeb7b..64c7cc322c 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/DracoMeshCompressionExtensionLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/DracoMeshCompressionExtensionLoader.java @@ -63,28 +63,25 @@ import dev.fileformat.drako.PointAttribute; /** - * A class for handling the KHR_draco_mesh_compression extension - * when loading a glTF asset. + * A class for handling the KHR_draco_mesh_compression extension when loading a glTF asset. * - * It is registered as the handler for this extension in the glTF - * {@link CustomContentManager}. In the - * {@link GltfLoader#readMeshPrimitives(int)} method, the custom content handler - * will be called for each mesh primitive, and handle the - * KHR_draco_mesh_compression of the primitive by calling the + * It is registered as the handler for this extension in the glTF {@link CustomContentManager}. In the + * {@link GltfLoader#readMeshPrimitives(int)} method, the custom content handler will be called for each mesh + * primitive, and handle the KHR_draco_mesh_compression of the primitive by calling the * {@link #handleExtension} method of this class. * - * TODO_DRACO Strictly speaking, the loader should ignore any attribute - * definitions when the draco extension is present. Right now, this is called - * after the mesh was already filled with the vertex buffers that have been - * created by the default loading process. See the check for "bufferViewIndex == - * null" in VertexBufferPopulator. + * TODO_DRACO Strictly speaking, the loader should ignore any attribute definitions when the draco extension + * is present. Right now, this is called after the mesh was already filled with the vertex buffers that have + * been created by the default loading process. See the check for "bufferViewIndex == null" in + * VertexBufferPopulator. */ public class DracoMeshCompressionExtensionLoader implements ExtensionLoader { /** * The logger used in this class */ - private final static Logger logger = Logger.getLogger(DracoMeshCompressionExtensionLoader.class.getName()); + private final static Logger logger = Logger + .getLogger(DracoMeshCompressionExtensionLoader.class.getName()); /** * The default log level @@ -94,8 +91,8 @@ public class DracoMeshCompressionExtensionLoader implements ExtensionLoader { /** *
    *
  • The parentName will be "primitive"
  • - *
  • The parent" will be the JSON element that represents the - * mesh primitive from the glTF JSON.
  • + *
  • The parent" will be the JSON element that represents the mesh primitive from the glTF + * JSON.
  • *
  • The extension will be the JSON element that represents the * KHR_draco_mesh_compression extension object.
  • *
@@ -103,8 +100,8 @@ public class DracoMeshCompressionExtensionLoader implements ExtensionLoader { * {@inheritDoc} */ @Override - public Object handleExtension(GltfLoader loader, String parentName, JsonElement parent, JsonElement extension, - Object input) throws IOException { + public Object handleExtension(GltfLoader loader, String parentName, JsonElement parent, + JsonElement extension, Object input) throws IOException { logger.log(level, "Decoding draco data"); @@ -121,7 +118,8 @@ public Object handleExtension(GltfLoader loader, String parentName, JsonElement int indices[] = dracoMesh.getIndices().toArray(); int indicesAccessorIndex = getAsInt(meshPrimitiveObject, "mesh primitive", "indices"); JsonObject indicesAccessor = loader.getAccessor(indicesAccessorIndex); - int indicesComponentType = getAsInt(indicesAccessor, "accessor " + indicesAccessorIndex, "componentType"); + int indicesComponentType = getAsInt(indicesAccessor, "accessor " + indicesAccessorIndex, + "componentType"); VertexBuffer indicesVertexBuffer = createIndicesVertexBuffer(loader, indicesComponentType, indices); mesh.clearBuffer(VertexBuffer.Type.Index); mesh.setBuffer(indicesVertexBuffer); @@ -148,7 +146,8 @@ public Object handleExtension(GltfLoader loader, String parentName, JsonElement // The mesh primitive stores the accessor index for // each attribute - int attributeAccessorIndex = getAsInt(parentAttributes, attributeName + " attribute", attributeName); + int attributeAccessorIndex = getAsInt(parentAttributes, attributeName + " attribute", + attributeName); JsonObject accessor = loader.getAccessor(attributeAccessorIndex); logger.log(level, "attributeAccessorIndex " + attributeAccessorIndex); @@ -158,8 +157,8 @@ public Object handleExtension(GltfLoader loader, String parentName, JsonElement // created from the data that was fetched from the // decoded draco PointAttribute Type bufferType = getVertexBufferType(attributeName); - VertexBuffer attributeVertexBuffer = createAttributeVertexBuffer(attributeName, accessor, pointAttribute, - indices); + VertexBuffer attributeVertexBuffer = createAttributeVertexBuffer(attributeName, accessor, + pointAttribute, indices); mesh.clearBuffer(bufferType); mesh.setBuffer(attributeVertexBuffer); } @@ -169,15 +168,15 @@ public Object handleExtension(GltfLoader loader, String parentName, JsonElement } /** - * Read the draco data from the given extension, using - * openize-drako-java. + * Read the draco data from the given extension, using openize-drako-java. * - * @param loader The glTF loader - * @param extension The draco extension object that was found in a mesh - * primitive + * @param loader + * The glTF loader + * @param extension + * The draco extension object that was found in a mesh primitive * @return The Draco mesh - * @throws IOException If attempting to load the underlying buffer causes an IO - * error + * @throws IOException + * If attempting to load the underlying buffer causes an IO error */ private static DracoMesh readDracoMesh(GltfLoader loader, JsonElement extension) throws IOException { logger.log(level, "Decoding draco mesh"); @@ -193,7 +192,8 @@ private static DracoMesh readDracoMesh(GltfLoader loader, JsonElement extension) try { dracoMesh = (DracoMesh) Draco.decode(bufferViewDataArray); } catch (DrakoException e) { - throw new AssetLoadException("Could not decode Draco mesh from buffer view " + bufferViewIndex, e); + throw new AssetLoadException("Could not decode Draco mesh from buffer view " + bufferViewIndex, + e); } logger.log(level, "Decoding draco mesh DONE"); @@ -201,17 +201,18 @@ private static DracoMesh readDracoMesh(GltfLoader loader, JsonElement extension) } /** - * Create the indices vertex buffer that should go into the mesh, based on the - * given Draco-decoded indices + * Create the indices vertex buffer that should go into the mesh, based on the given Draco-decoded indices * - * @param loader The glTF loader - * @param accessorIndex The accessor index of the vertices - * @param indices The Draco-decoded indices + * @param loader + * The glTF loader + * @param accessorIndex + * The accessor index of the vertices + * @param indices + * The Draco-decoded indices * @return The indices vertex buffer - * @throws AssetLoadException If the given component type is not - * GL_UNSIGNED_BYTE, - * GL_UNSIGNED_SHORT, or - * GL_UNSIGNED_INT + * @throws AssetLoadException + * If the given component type is not GL_UNSIGNED_BYTE, + * GL_UNSIGNED_SHORT, or GL_UNSIGNED_INT */ VertexBuffer createIndicesVertexBuffer(GltfLoader loader, int componentType, int indices[]) { Buffer data = null; @@ -222,8 +223,9 @@ VertexBuffer createIndicesVertexBuffer(GltfLoader loader, int componentType, int } else if (componentType == GL.GL_UNSIGNED_INT) { data = BufferUtils.createIntBuffer(indices); } else { - throw new AssetLoadException("The indices accessor must have a component type of " + GL.GL_UNSIGNED_BYTE - + ", " + GL.GL_UNSIGNED_SHORT + ", or " + GL.GL_UNSIGNED_INT + ", but has " + componentType); + throw new AssetLoadException("The indices accessor must have a component type of " + + GL.GL_UNSIGNED_BYTE + ", " + GL.GL_UNSIGNED_SHORT + ", or " + GL.GL_UNSIGNED_INT + + ", but has " + componentType); } VertexBuffer vb = new VertexBuffer(VertexBuffer.Type.Index); VertexBuffer.Format format = getVertexBufferFormat(componentType); @@ -234,13 +236,13 @@ VertexBuffer createIndicesVertexBuffer(GltfLoader loader, int componentType, int // TODO_DRACO Could go into GltfUtils /** - * Determines the number of components per element for the given accessor, based - * on its type + * Determines the number of components per element for the given accessor, based on its type * - * @param accessor The accessor + * @param accessor + * The accessor * @return The number of components - * @throws AssetLoadException If the accessor does not have a valid - * type property + * @throws AssetLoadException + * If the accessor does not have a valid type property */ private static int getAccessorComponentCount(JsonObject accessor) { String type = getAsString(accessor, "type"); @@ -252,7 +254,8 @@ private static int getAccessorComponentCount(JsonObject accessor) { /** * Create a byte buffer containing the given values, cast to byte * - * @param array The array + * @param array + * The array * @return The buffer */ private static Buffer createByteBuffer(int[] array) { @@ -267,7 +270,8 @@ private static Buffer createByteBuffer(int[] array) { /** * Create a short buffer containing the given values, cast to short * - * @param array The array + * @param array + * The array * @return The buffer */ private static Buffer createShortBuffer(int[] array) { @@ -282,19 +286,22 @@ private static Buffer createShortBuffer(int[] array) { /** * Obtain the data for the specified buffer view of the given loader. * - * This will return a slice of the data of the underlying buffer. Callers may - * not modify the returned data. + * This will return a slice of the data of the underlying buffer. Callers may not modify the returned + * data. * - * @param loader The loader - * @param bufferViewIndex The buffer view index + * @param loader + * The loader + * @param bufferViewIndex + * The buffer view index * @return The buffer view data - * @throws IOException If attempting to load the underlying buffer causes - * an IO error - * @throws AssetLoadException If the specified index is not valid, or the buffer - * view did not define a valid buffer index or byte - * length + * @throws IOException + * If attempting to load the underlying buffer causes an IO error + * @throws AssetLoadException + * If the specified index is not valid, or the buffer view did not define a valid buffer index + * or byte length */ - private static ByteBuffer obtainBufferViewData(GltfLoader loader, int bufferViewIndex) throws IOException { + private static ByteBuffer obtainBufferViewData(GltfLoader loader, int bufferViewIndex) + throws IOException { JsonObject bufferView = loader.getBufferView(bufferViewIndex); int bufferIndex = getAsInt(bufferView, "bufferView", "buffer"); assertNotNull(bufferIndex, "No buffer defined for bufferView " + bufferViewIndex); @@ -312,14 +319,16 @@ private static ByteBuffer obtainBufferViewData(GltfLoader loader, int bufferView /** * Obtains the point attribute with the given ID from the given draco mesh. * - * @param dracoMesh The draco mesh - * @param gltfAttribute The glTF attribute name, like "POSITION" - * (only used for error messages) - * @param id The unique ID of the attribute, i.e. the value that was - * stored as the "POSITION": id in the draco - * extension JSON object. + * @param dracoMesh + * The draco mesh + * @param gltfAttribute + * The glTF attribute name, like "POSITION" (only used for error messages) + * @param id + * The unique ID of the attribute, i.e. the value that was stored as the + * "POSITION": id in the draco extension JSON object. * @return The point attribute - * @throws AssetLoadException If the attribute with the given ID cannot be found + * @throws AssetLoadException + * If the attribute with the given ID cannot be found */ private static PointAttribute getAttribute(DracoMesh dracoMesh, String gltfAttribute, int id) { for (int i = 0; i < dracoMesh.getNumAttributes(); i++) { @@ -328,25 +337,29 @@ private static PointAttribute getAttribute(DracoMesh dracoMesh, String gltfAttri return attribute; } } - throw new AssetLoadException( - "Could not obtain attribute " + gltfAttribute + " with unique ID " + id + " from decoded Draco mesh"); + throw new AssetLoadException("Could not obtain attribute " + gltfAttribute + " with unique ID " + id + + " from decoded Draco mesh"); } /** - * Creates a vertex buffer for the specified attribute, according to the - * structure that is described by the given accessor JSON object, using the data - * that is obtained from the given Draco-decoded point attribute + * Creates a vertex buffer for the specified attribute, according to the structure that is described by + * the given accessor JSON object, using the data that is obtained from the given Draco-decoded point + * attribute * - * @param attributeName The attribute name - * @param accessor The accessor JSON object - * @param pointAttribute The Draco-decoded point attribute - * @param indices The indices, obtained from the draco mesh + * @param attributeName + * The attribute name + * @param accessor + * The accessor JSON object + * @param pointAttribute + * The Draco-decoded point attribute + * @param indices + * The indices, obtained from the draco mesh * @return The vertex buffer - * @throws AssetLoadException If the given accessor does not have a component - * type that is valid for a vertex attribute + * @throws AssetLoadException + * If the given accessor does not have a component type that is valid for a vertex attribute */ private static VertexBuffer createAttributeVertexBuffer(String attributeName, JsonObject accessor, - PointAttribute pointAttribute, int indices[]) { + PointAttribute pointAttribute, int indices[]) { int count = getAsInt(accessor, "accessor", "count"); int componentType = getAsInt(accessor, "accessor", "componentType"); int componentCount = getAccessorComponentCount(accessor); @@ -354,37 +367,45 @@ private static VertexBuffer createAttributeVertexBuffer(String attributeName, Js if (componentType == GL.GL_BYTE || componentType == GL.GL_UNSIGNED_BYTE) { ByteBuffer attributeData = readByteDracoAttribute(pointAttribute, indices, count, componentCount); - VertexBuffer attributeVertexBuffer = createByteAttributeVertexBuffer(accessor, bufferType, attributeData); + VertexBuffer attributeVertexBuffer = createByteAttributeVertexBuffer(accessor, bufferType, + attributeData); return attributeVertexBuffer; } if (componentType == GL.GL_SHORT || componentType == GL.GL_UNSIGNED_SHORT) { - ShortBuffer attributeData = readShortDracoAttribute(pointAttribute, indices, count, componentCount); - VertexBuffer attributeVertexBuffer = createShortAttributeVertexBuffer(accessor, bufferType, attributeData); + ShortBuffer attributeData = readShortDracoAttribute(pointAttribute, indices, count, + componentCount); + VertexBuffer attributeVertexBuffer = createShortAttributeVertexBuffer(accessor, bufferType, + attributeData); return attributeVertexBuffer; } if (componentType == GL.GL_FLOAT) { - FloatBuffer attributeData = readFloatDracoAttribute(pointAttribute, indices, count, componentCount); - VertexBuffer attributeVertexBuffer = createFloatAttributeVertexBuffer(accessor, bufferType, attributeData); + FloatBuffer attributeData = readFloatDracoAttribute(pointAttribute, indices, count, + componentCount); + VertexBuffer attributeVertexBuffer = createFloatAttributeVertexBuffer(accessor, bufferType, + attributeData); return attributeVertexBuffer; } - throw new AssetLoadException("The accessor for attribute " + attributeName + " must have a component type of " - + GL.GL_BYTE + ", " + GL.GL_UNSIGNED_BYTE + ", " + GL.GL_SHORT + ", " + GL.GL_UNSIGNED_SHORT + ", " - + "or " + GL.GL_FLOAT + ", but has " + componentType); + throw new AssetLoadException( + "The accessor for attribute " + attributeName + " must have a component type of " + GL.GL_BYTE + + ", " + GL.GL_UNSIGNED_BYTE + ", " + GL.GL_SHORT + ", " + GL.GL_UNSIGNED_SHORT + ", " + + "or " + GL.GL_FLOAT + ", but has " + componentType); } /** * Read the data from the given point attribute, as byte values * - * @param pointAttribute The Draco-decoded point attribute - * @param indices The indices, obtained from the draco mesh - * @param count The count, obtained from the accessor for this - * attribute - * @param componentCount The component count (number of components per element), - * obtained from the accessor type + * @param pointAttribute + * The Draco-decoded point attribute + * @param indices + * The indices, obtained from the draco mesh + * @param count + * The count, obtained from the accessor for this attribute + * @param componentCount + * The component count (number of components per element), obtained from the accessor type * @return The resulting data, as a byte buffer */ private static ByteBuffer readByteDracoAttribute(PointAttribute pointAttribute, int indices[], int count, - int componentCount) { + int componentCount) { int numFaces = indices.length / 3; byte p[] = new byte[componentCount]; ByteBuffer attributeData = BufferUtils.createByteBuffer(count * componentCount); @@ -419,16 +440,18 @@ private static ByteBuffer readByteDracoAttribute(PointAttribute pointAttribute, /** * Read the data from the given point attribute, as short values * - * @param pointAttribute The Draco-decoded point attribute - * @param indices The indices, obtained from the draco mesh - * @param count The count, obtained from the accessor for this - * attribute - * @param componentCount The component count (number of components per element), - * obtained from the accessor type + * @param pointAttribute + * The Draco-decoded point attribute + * @param indices + * The indices, obtained from the draco mesh + * @param count + * The count, obtained from the accessor for this attribute + * @param componentCount + * The component count (number of components per element), obtained from the accessor type * @return The resulting data, as a short buffer */ - private static ShortBuffer readShortDracoAttribute(PointAttribute pointAttribute, int indices[], int count, - int componentCount) { + private static ShortBuffer readShortDracoAttribute(PointAttribute pointAttribute, int indices[], + int count, int componentCount) { int numFaces = indices.length / 3; short p[] = new short[componentCount]; ShortBuffer attributeData = BufferUtils.createShortBuffer(count * componentCount); @@ -463,16 +486,18 @@ private static ShortBuffer readShortDracoAttribute(PointAttribute pointAttribute /** * Read the data from the given point attribute, as float values * - * @param pointAttribute The Draco-decoded point attribute - * @param indices The indices, obtained from the draco mesh - * @param count The count, obtained from the accessor for this - * attribute - * @param componentCount The component count (number of components per element), - * obtained from the accessor type + * @param pointAttribute + * The Draco-decoded point attribute + * @param indices + * The indices, obtained from the draco mesh + * @param count + * The count, obtained from the accessor for this attribute + * @param componentCount + * The component count (number of components per element), obtained from the accessor type * @return The resulting data, as a float buffer */ - private static FloatBuffer readFloatDracoAttribute(PointAttribute pointAttribute, int indices[], int count, - int componentCount) { + private static FloatBuffer readFloatDracoAttribute(PointAttribute pointAttribute, int indices[], + int count, int componentCount) { int numFaces = indices.length / 3; float p[] = new float[componentCount]; FloatBuffer attributeData = BufferUtils.createFloatBuffer(count * componentCount); @@ -507,14 +532,17 @@ private static FloatBuffer readFloatDracoAttribute(PointAttribute pointAttribute /** * Create the vertex buffer for the given byte attribute data * - * @param accessor The accessor that describes the component type and type + * @param accessor + * The accessor that describes the component type and type * - * @param bufferType The buffer type - * @param attributeData The attribute data + * @param bufferType + * The buffer type + * @param attributeData + * The attribute data * @return The vertex buffer */ - private static VertexBuffer createByteAttributeVertexBuffer(JsonObject accessor, VertexBuffer.Type bufferType, - ByteBuffer attributeData) { + private static VertexBuffer createByteAttributeVertexBuffer(JsonObject accessor, + VertexBuffer.Type bufferType, ByteBuffer attributeData) { int componentType = getAsInt(accessor, "accessor", "componentType"); VertexBuffer vb = new VertexBuffer(bufferType); VertexBuffer.Format format = getVertexBufferFormat(componentType); @@ -526,14 +554,17 @@ private static VertexBuffer createByteAttributeVertexBuffer(JsonObject accessor, /** * Create the vertex buffer for the given short attribute data * - * @param accessor The accessor that describes the component type and type + * @param accessor + * The accessor that describes the component type and type * - * @param bufferType The buffer type - * @param attributeData The attribute data + * @param bufferType + * The buffer type + * @param attributeData + * The attribute data * @return The vertex buffer */ - private static VertexBuffer createShortAttributeVertexBuffer(JsonObject accessor, VertexBuffer.Type bufferType, - ShortBuffer attributeData) { + private static VertexBuffer createShortAttributeVertexBuffer(JsonObject accessor, + VertexBuffer.Type bufferType, ShortBuffer attributeData) { int componentType = getAsInt(accessor, "accessor", "componentType"); VertexBuffer vb = new VertexBuffer(bufferType); VertexBuffer.Format format = getVertexBufferFormat(componentType); @@ -545,14 +576,17 @@ private static VertexBuffer createShortAttributeVertexBuffer(JsonObject accessor /** * Create the vertex buffer for the given float attribute data * - * @param accessor The accessor that describes the component type and type + * @param accessor + * The accessor that describes the component type and type * - * @param bufferType The buffer type - * @param attributeData The attribute data + * @param bufferType + * The buffer type + * @param attributeData + * The attribute data * @return The vertex buffer */ - private static VertexBuffer createFloatAttributeVertexBuffer(JsonObject accessor, VertexBuffer.Type bufferType, - FloatBuffer attributeData) { + private static VertexBuffer createFloatAttributeVertexBuffer(JsonObject accessor, + VertexBuffer.Type bufferType, FloatBuffer attributeData) { int componentType = getAsInt(accessor, "accessor", "componentType"); VertexBuffer vb = new VertexBuffer(bufferType); VertexBuffer.Format format = getVertexBufferFormat(componentType); From cf47e2154f75ebf386be561cbdb33b7337ccc92b Mon Sep 17 00:00:00 2001 From: Riccardo Balbo Date: Thu, 29 Jan 2026 18:34:48 +0100 Subject: [PATCH 05/14] example refactoring --- .../java/jme3test/model/TestGltfLoading.java | 331 +++++++----------- 1 file changed, 134 insertions(+), 197 deletions(-) diff --git a/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java b/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java index e1a08119b0..ff73a6e6c9 100644 --- a/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java +++ b/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java @@ -32,47 +32,50 @@ package jme3test.model; import com.jme3.anim.AnimComposer; -import com.jme3.anim.SkinningControl; import com.jme3.app.*; import com.jme3.asset.plugins.FileLocator; import com.jme3.asset.plugins.UrlLocator; +import com.jme3.bounding.BoundingBox; import com.jme3.input.KeyInput; import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.KeyTrigger; import com.jme3.math.*; import com.jme3.renderer.Limits; import com.jme3.scene.*; -import com.jme3.scene.control.Control; import com.jme3.scene.debug.custom.ArmatureDebugAppState; import com.jme3.scene.plugins.gltf.GltfModelKey; - import jme3test.model.anim.EraseTimer; +import java.io.File; import java.util.*; public class TestGltfLoading extends SimpleApplication { - final private Node autoRotate = new Node("autoRotate"); - final private List assets = new ArrayList<>(); + private final Node autoRotate = new Node("autoRotate"); + private final List assets = new ArrayList<>(); private Node probeNode; private float time = 0; private int assetIndex = 0; private boolean useAutoRotate = false; private final static String indentString = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; - final private int duration = 1; + private final int duration = 1; private boolean playAnim = true; + private ChaseCameraAppState chaseCam; + + private final Queue anims = new LinkedList<>(); + private AnimComposer composer; public static void main(String[] args) { TestGltfLoading app = new TestGltfLoading(); app.start(); } - /* - WARNING this test case can't work without the assets, and considering their size, they are not pushed into the repo - you can find them here : - https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0 - https://sketchfab.com/features/gltf - You have to copy them in Model/gltf folder in the jme3-testdata project. + /** + * WARNING This test case will try to load models from $HOME/glTF-Sample-Models, if the models is not + * found there, it will automatically try to load it from the repository + * https://github.com/KhronosGroup/glTF-Sample-Models . + * + * Depending on the your connection speed and github rate limiting, this can be quite slow. */ @Override public void simpleInitApp() { @@ -81,12 +84,14 @@ public void simpleInitApp() { getStateManager().attach(armatureDebugappState); setTimer(new EraseTimer()); - String folder = System.getProperty("user.home"); - assetManager.registerLocator(folder, FileLocator.class); - assetManager.registerLocator("https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/refs/heads/main/", UrlLocator.class); + String folder = System.getProperty("user.home") + "/glTF-Sample-Models"; + if (new File(folder).exists()) { + assetManager.registerLocator(folder, FileLocator.class); + } + assetManager.registerLocator( + "https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/refs/heads/main/", + UrlLocator.class); - // cam.setLocation(new Vector3f(4.0339394f, 2.645184f, 6.4627485f)); - // cam.setRotation(new Quaternion(-0.013950467f, 0.98604023f, -0.119502485f, -0.11510504f)); cam.setFrustumPerspective(45f, (float) cam.getWidth() / cam.getHeight(), 0.1f, 100f); renderer.setDefaultAnisotropicFilter(Math.min(renderer.getLimits().get(Limits.TextureAnisotropy), 8)); setPauseOnLostFocus(false); @@ -99,138 +104,63 @@ public void simpleInitApp() { probeNode = (Node) assetManager.loadModel("Scenes/defaultProbe.j3o"); autoRotate.attachChild(probeNode); -// DirectionalLight dl = new DirectionalLight(); -// dl.setDirection(new Vector3f(-1f, -1.0f, -1f).normalizeLocal()); -// dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f)); -// rootNode.addLight(dl); - -// DirectionalLight dl2 = new DirectionalLight(); -// dl2.setDirection(new Vector3f(1f, 1.0f, 1f).normalizeLocal()); -// dl2.setColor(new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f)); -// rootNode.addLight(dl2); - -// PointLight pl = new PointLight(new Vector3f(5.0f, 5.0f, 5.0f), ColorRGBA.White, 30); -// rootNode.addLight(pl); -// PointLight pl1 = new PointLight(new Vector3f(-5.0f, -5.0f, -5.0f), ColorRGBA.White.mult(0.5f), 50); -// rootNode.addLight(pl1); - - //loadModel("Models/gltf/polly/project_polly.gltf", new Vector3f(0, 0, 0), 0.5f); - //loadModel("Models/gltf/zophrac/scene.gltf", new Vector3f(0, 0, 0), 0.01f); - // loadModel("Models/gltf/scifigirl/scene.gltf", new Vector3f(0, -1, 0), 0.1f); - //loadModel("Models/gltf/man/scene.gltf", new Vector3f(0, -1, 0), 0.1f); - //loadModel("Models/gltf/torus/scene.gltf", new Vector3f(0, -1, 0), 0.1f); - //loadModel("Models/gltf/morph/scene.gltf", new Vector3f(0, 0, 0), 0.2f); -// loadModel("Models/gltf/AnimatedMorphCube/glTF/AnimatedMorphCube.gltf", new Vector3f(0, 0, 0), 1f); -// loadModel("Models/gltf/SimpleMorph/glTF/SimpleMorph.gltf", new Vector3f(0, 0, 0), 0.1f); - //loadModel("Models/gltf/nier/scene.gltf", new Vector3f(0, -1.5f, 0), 0.01f); - //loadModel("Models/gltf/izzy/scene.gltf", new Vector3f(0, -1, 0), 0.01f); - //loadModel("Models/gltf/darth/scene.gltf", new Vector3f(0, -1, 0), 0.01f); - //loadModel("Models/gltf/mech/scene.gltf", new Vector3f(0, -1, 0), 0.01f); - //loadModel("Models/gltf/elephant/scene.gltf", new Vector3f(0, -1, 0), 0.01f); - //loadModel("Models/gltf/buffalo/scene.gltf", new Vector3f(0, -1, 0), 0.1f); - //loadModel("Models/gltf/war/scene.gltf", new Vector3f(0, -1, 0), 0.1f); - //loadModel("Models/gltf/ganjaarl/scene.gltf", new Vector3f(0, -1, 0), 0.01f); - //loadModel("Models/gltf/hero/scene.gltf", new Vector3f(0, -1, 0), 0.1f); - //loadModel("Models/gltf/mercy/scene.gltf", new Vector3f(0, -1, 0), 0.01f); - //loadModel("Models/gltf/crab/scene.gltf", Vector3f.ZERO, 1); - //loadModel("Models/gltf/manta/scene.gltf", Vector3f.ZERO, 0.2f); - //loadModel("Models/gltf/bone/scene.gltf", Vector3f.ZERO, 0.1f); - //loadModel("Models/gltf/box/box.gltf", Vector3f.ZERO, 1); - //loadModel("Models/gltf/duck/Duck.gltf", new Vector3f(0, 1, 0), 1); - - - // =================================================================== - // TODO_DRACO Draco test start - // The following test assumes that the "Models" directory from - // https://github.com/KhronosGroup/glTF-Sample-Assets/tree/main/Models - // is copied into the - // jme3-testdata\src\main\resources\Models\gltf\ - // directory (or at least the following list of models) - - // Comment in/out the model to test - String dracoTestModel = null; - //dracoTestModel = "Avocado"; - //dracoTestModel = "BarramundiFish"; - dracoTestModel = "BoomBox"; - //dracoTestModel = "BrainStem"; - //dracoTestModel = "CesiumMan"; - //dracoTestModel = "CesiumMilkTruck"; - //dracoTestModel = "Corset"; - //dracoTestModel = "Lantern"; - //dracoTestModel = "MorphPrimitivesTest"; - //dracoTestModel = "RiggedFigure"; - //dracoTestModel = "RiggedSimple"; - //dracoTestModel = "SunglassesKhronos"; - //dracoTestModel = "VirtualCity"; - //dracoTestModel = "WaterBottle"; - - boolean testDraco = true; - // Uncomment this to not load the Draco-compressed - // version, but the glTF-Binary version of the model - //testDraco = false; - String dracoTestFlavor = null; - String dracoTestExtension = null; - if (testDraco) - { - dracoTestFlavor = "glTF-Draco"; - dracoTestExtension = "gltf"; - } - else - { - dracoTestFlavor = "glTF-Binary"; - dracoTestExtension = "glb"; - } - // Assemble a path like - // "Models/gltf/Models/BoomBox/glTF-Draco/BoomBox.gltf" - String dracoTestPath = "Models/gltf/Models/" + dracoTestModel + "/" + dracoTestFlavor + "/" + dracoTestModel + "." + dracoTestExtension; - - System.out.println("Running Draco test with "+dracoTestPath); - loadModel(dracoTestPath, new Vector3f(0, 0, 0), 20.0f); - - // TODO_DRACO Draco test end - // =================================================================== - - - //loadModel("Models/gltf/BoomBox/glTF-Draco/BoomBox.gltf", new Vector3f(0, 0, 0), 20); - //loadModel("Models/gltf/BoomBox/glTF-Binary/BoomBox.glb", new Vector3f(0, 0, 0), 1); - -// loadModel("Models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf", Vector3f.ZERO, 1); -// loadModel("Models/gltf/hornet/scene.gltf", new Vector3f(0, -0.5f, 0), 0.4f); -//// loadModel("Models/gltf/adamHead/adamHead.gltf", Vector3f.ZERO, 0.6f); - //loadModel("Models/gltf/busterDrone/busterDrone.gltf", new Vector3f(0, 0f, 0), 0.8f); -// loadModel("Models/gltf/AnimatedCube/glTF/AnimatedCube.gltf", Vector3f.ZERO, 0.5f); -// loadModel("Models/gltf/BoxAnimated/glTF/BoxAnimated.gltf", new Vector3f(0, 0f, 0), 0.8f); -// loadModel("Models/gltf/RiggedSimple/glTF/RiggedSimple.gltf", new Vector3f(0, -0.3f, 0), 0.2f); -// loadModel("Models/gltf/RiggedFigure/glTF/RiggedFigure.gltf", new Vector3f(0, -1f, 0), 1f); -// loadModel("Models/gltf/CesiumMan/glTF/CesiumMan.gltf", new Vector3f(0, -1, 0), 1f); -// loadModel("Models/gltf/BrainStem/glTF/BrainStem.gltf", new Vector3f(0, -1, 0), 1f); - //loadModel("Models/gltf/Jaime/Jaime.gltf", new Vector3f(0, -1, 0), 1f); - // loadModel("Models/gltf/GiantWorm/GiantWorm.gltf", new Vector3f(0, -1, 0), 1f); - //loadModel("Models/gltf/RiggedFigure/WalkingLady.gltf", new Vector3f(0, -0.f, 0), 1f); - //loadModel("Models/gltf/Monster/Monster.gltf", Vector3f.ZERO, 0.03f); - -// loadModel("Models/gltf/Corset/glTF/Corset.gltf", new Vector3f(0, -1, 0), 20f); -// loadModel("Models/gltf/BoxInterleaved/glTF/BoxInterleaved.gltf", new Vector3f(0, 0, 0), 1f); - - // From url locator - - // loadModel("Models/AnimatedColorsCube/glTF/AnimatedColorsCube.gltf", new Vector3f(0, 0f, 0), 0.1f); - // loadModel("Models/AntiqueCamera/glTF/AntiqueCamera.gltf", new Vector3f(0, 0, 0), 0.1f); - // loadModel("Models/AnimatedMorphCube/glTF/AnimatedMorphCube.gltf", new Vector3f(0, 0, 0), 0.1f); - // loadModel("Models/AnimatedMorphCube/glTF-Binary/AnimatedMorphCube.glb", new Vector3f(0, 0, 0), 0.1f); + chaseCam = new ChaseCameraAppState(); + getStateManager().attach(chaseCam); - probeNode.attachChild(assets.get(0)); + loadModelSample("BoomBox", "gltf"); - ChaseCameraAppState chaseCam = new ChaseCameraAppState(); - chaseCam.setTarget(probeNode); - getStateManager().attach(chaseCam); - chaseCam.setInvertHorizontalAxis(true); - chaseCam.setInvertVerticalAxis(true); - chaseCam.setZoomSpeed(0.5f); - chaseCam.setMinVerticalRotation(-FastMath.HALF_PI); - chaseCam.setRotationSpeed(3); - chaseCam.setDefaultDistance(3); - chaseCam.setDefaultVerticalRotation(0.3f); + // loadModelSample("Duck", "gltf"); + // loadModelSample("Duck", "glb"); + // loadModelSample("ABeautifulGame", "gltf"); + // loadModelSample("Avocado", "glb"); + // loadModelSample("Avocado", "gltf"); + // loadModelSample("CesiumMilkTruck", "glb"); + // loadModelSample("VirtualCity", "glb"); + // loadModelSample("BrainStem", "glb"); + // loadModelSample("Lantern", "glb"); + // loadModelSample("RiggedFigure", "glb"); + // loadModelSample("SciFiHelmet", "gltf"); + // loadModelSample("DamagedHelmet", "gltf"); + // loadModelSample("AnimatedCube", "gltf"); + // loadModelSample("AntiqueCamera", "glb"); + // loadModelSample("AnimatedMorphCube", "glb"); + + // DRACO SAMPLES + + // loadModelSample("Avocado", "draco"); + + // FIXME: wrong texture coords? + // loadModelSample("BarramundiFish", "draco"); + + // loadModelSample("BoomBox", "draco"); + + // FIXME: bad skinning? + // loadModelSample("BrainStem", "draco"); + + // FIXME: wrong offsets? + // loadModelSample("CesiumMilkTruck", "draco"); + + // FIXME: FAILS WITH INDEX OUT OF BOUND EXCEPTION + // loadModelSample("VirtualCity", "draco"); + + // loadModelSample("Corset", "draco"); + + // FIXME: unclear + // loadModelSample("Lantern", "draco"); + + // loadModelSample("MorphPrimitivesTest", "draco"); + + // FIXME: skinning? + // loadModelSample("RiggedFigure", "draco"); + + // FIXME: skinning? + // loadModelSample("RiggedSimple", "draco"); + + // FIXME: "dracoMesh" is null + // loadModelSample("SunglassesKhronos", "draco"); + // loadModelSample("WaterBottle", "draco"); + + probeNode.attachChild(assets.get(0)); inputManager.addMapping("autorotate", new KeyTrigger(KeyInput.KEY_SPACE)); inputManager.addListener(new ActionListener() { @@ -271,36 +201,63 @@ public void onAction(String name, boolean isPressed, float tpf) { dumpScene(rootNode, 0); - // stateManager.attach(new DetailedProfilerState()); + // stateManager.attach(new DetailedProfilerState()); } - private T findControl(Spatial s, Class controlClass) { - T ctrl = s.getControl(controlClass); - if (ctrl != null) { - return ctrl; + private void loadModelSample(String name, String type) { + String path = "Models/" + name; + String ext = "gltf"; + switch (type) { + case "draco": + path += "/glTF-Draco/"; + ext = "gltf"; + break; + case "glb": + path += "/glTF-Binary/"; + ext = "glb"; + break; + default: + path += "/glTF/"; + ext = "gltf"; + break; } - if (s instanceof Node) { - Node n = (Node) s; - for (Spatial spatial : n.getChildren()) { - ctrl = findControl(spatial, controlClass); - if (ctrl != null) { - return ctrl; - } - } + path += name + "." + ext; + + Spatial s = loadModel(path, new Vector3f(0, 0, 0), 1f); + + BoundingBox bbox = (BoundingBox) s.getWorldBound(); + + float maxExtent = Math.max(bbox.getXExtent(), Math.max(bbox.getYExtent(), bbox.getZExtent())); + if (maxExtent < 10f) { + s.scale(10f / maxExtent); + maxExtent = 10f; } - return null; + float distance = 50f; + + chaseCam.setTarget(s); + chaseCam.setInvertHorizontalAxis(true); + chaseCam.setInvertVerticalAxis(true); + chaseCam.setZoomSpeed(0.5f); + chaseCam.setMinVerticalRotation(-FastMath.HALF_PI); + chaseCam.setRotationSpeed(3); + chaseCam.setDefaultDistance(distance); + chaseCam.setMaxDistance(distance * 10); + chaseCam.setDefaultVerticalRotation(0.3f); + } - private void loadModel(String path, Vector3f offset, float scale) { - loadModel(path, offset, new Vector3f(scale, scale, scale)); + private Spatial loadModel(String path, Vector3f offset, float scale) { + return loadModel(path, offset, new Vector3f(scale, scale, scale)); } - private void loadModel(String path, Vector3f offset, Vector3f scale) { + + private Spatial loadModel(String path, Vector3f offset, Vector3f scale) { + System.out.println("Loading model: " + path); GltfModelKey k = new GltfModelKey(path); - //k.setKeepSkeletonPose(true); - long t = System.currentTimeMillis(); + // k.setKeepSkeletonPose(true); + long t = System.currentTimeMillis(); Spatial s = assetManager.loadModel(k); System.out.println("Load time : " + (System.currentTimeMillis() - t) + " ms"); - + s.scale(scale.x, scale.y, scale.z); s.move(offset); assets.add(s); @@ -308,29 +265,9 @@ private void loadModel(String path, Vector3f offset, Vector3f scale) { playFirstAnim(s); } - SkinningControl ctrl = findControl(s, SkinningControl.class); - - // ctrl.getSpatial().removeControl(ctrl); - if (ctrl == null) { - return; - } - //System.err.println(ctrl.getArmature().toString()); - //ctrl.setHardwareSkinningPreferred(false); - // getStateManager().getState(ArmatureDebugAppState.class).addArmatureFrom(ctrl); -// AnimControl aCtrl = findControl(s, AnimControl.class); -// //ctrl.getSpatial().removeControl(ctrl); -// if (aCtrl == null) { -// return; -// } -// if (aCtrl.getArmature() != null) { -// getStateManager().getState(SkeletonDebugAppState.class).addSkeleton(aCtrl.getArmature(), aCtrl.getSpatial(), true); -// } - + return s; } - final private Queue anims = new LinkedList<>(); - private AnimComposer composer; - private void playFirstAnim(Spatial s) { AnimComposer control = s.getControl(AnimComposer.class); @@ -375,25 +312,25 @@ public void simpleUpdate(float tpf) { return; } time += tpf; - // autoRotate.rotate(0, tpf * 0.5f, 0); + // autoRotate.rotate(0, tpf * 0.5f, 0); if (time > duration) { // morphIndex++; - // setMorphTarget(morphIndex); + // setMorphTarget(morphIndex); assets.get(assetIndex).removeFromParent(); assetIndex = (assetIndex + 1) % assets.size(); -// if (assetIndex == 0) { -// duration = 10; -// } + // if (assetIndex == 0) { + // duration = 10; + // } probeNode.attachChild(assets.get(assetIndex)); time = 0; } } private void dumpScene(Spatial s, int indent) { - System.err.println(indentString.substring(0, indent) + s.getName() + " (" + s.getClass().getSimpleName() + ") / " + - s.getLocalTransform().getTranslation().toString() + ", " + - s.getLocalTransform().getRotation().toString() + ", " + - s.getLocalTransform().getScale().toString()); + System.err.println(indentString.substring(0, indent) + s.getName() + " (" + + s.getClass().getSimpleName() + ") / " + s.getLocalTransform().getTranslation().toString() + + ", " + s.getLocalTransform().getRotation().toString() + ", " + + s.getLocalTransform().getScale().toString()); if (s instanceof Node) { Node n = (Node) s; for (Spatial spatial : n.getChildren()) { From fd070cf5f5404ee396ac0b2766f0a14285fac321 Mon Sep 17 00:00:00 2001 From: Riccardo Balbo Date: Thu, 29 Jan 2026 19:06:16 +0100 Subject: [PATCH 06/14] remove dependency from GL renderer --- .../DracoMeshCompressionExtensionLoader.java | 23 ++++++++++--------- .../scene/plugins/gltf/GltfConstants.java | 11 +++++++++ .../jme3/scene/plugins/gltf/GltfUtils.java | 12 +++++----- 3 files changed, 29 insertions(+), 17 deletions(-) create mode 100644 jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfConstants.java diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/DracoMeshCompressionExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/DracoMeshCompressionExtensionLoader.java index 64c7cc322c..97028441ad 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/DracoMeshCompressionExtensionLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/DracoMeshCompressionExtensionLoader.java @@ -51,7 +51,6 @@ import com.jme3.asset.AssetLoadException; import com.jme3.plugins.json.JsonElement; import com.jme3.plugins.json.JsonObject; -import com.jme3.renderer.opengl.GL; import com.jme3.scene.Mesh; import com.jme3.scene.VertexBuffer; import com.jme3.scene.VertexBuffer.Type; @@ -216,15 +215,16 @@ private static DracoMesh readDracoMesh(GltfLoader loader, JsonElement extension) */ VertexBuffer createIndicesVertexBuffer(GltfLoader loader, int componentType, int indices[]) { Buffer data = null; - if (componentType == GL.GL_UNSIGNED_BYTE) { + if (componentType == GltfConstants.GL_UNSIGNED_BYTE) { data = createByteBuffer(indices); - } else if (componentType == GL.GL_UNSIGNED_SHORT) { + } else if (componentType == GltfConstants.GL_UNSIGNED_SHORT) { data = createShortBuffer(indices); - } else if (componentType == GL.GL_UNSIGNED_INT) { + } else if (componentType == GltfConstants.GL_UNSIGNED_INT) { data = BufferUtils.createIntBuffer(indices); } else { throw new AssetLoadException("The indices accessor must have a component type of " - + GL.GL_UNSIGNED_BYTE + ", " + GL.GL_UNSIGNED_SHORT + ", or " + GL.GL_UNSIGNED_INT + + GltfConstants.GL_UNSIGNED_BYTE + ", " + GltfConstants.GL_UNSIGNED_SHORT + ", or " + + GltfConstants.GL_UNSIGNED_INT + ", but has " + componentType); } VertexBuffer vb = new VertexBuffer(VertexBuffer.Type.Index); @@ -365,20 +365,20 @@ private static VertexBuffer createAttributeVertexBuffer(String attributeName, Js int componentCount = getAccessorComponentCount(accessor); Type bufferType = getVertexBufferType(attributeName); - if (componentType == GL.GL_BYTE || componentType == GL.GL_UNSIGNED_BYTE) { + if (componentType == GltfConstants.GL_BYTE || componentType == GltfConstants.GL_UNSIGNED_BYTE) { ByteBuffer attributeData = readByteDracoAttribute(pointAttribute, indices, count, componentCount); VertexBuffer attributeVertexBuffer = createByteAttributeVertexBuffer(accessor, bufferType, attributeData); return attributeVertexBuffer; } - if (componentType == GL.GL_SHORT || componentType == GL.GL_UNSIGNED_SHORT) { + if (componentType == GltfConstants.GL_SHORT || componentType == GltfConstants.GL_UNSIGNED_SHORT) { ShortBuffer attributeData = readShortDracoAttribute(pointAttribute, indices, count, componentCount); VertexBuffer attributeVertexBuffer = createShortAttributeVertexBuffer(accessor, bufferType, attributeData); return attributeVertexBuffer; } - if (componentType == GL.GL_FLOAT) { + if (componentType == GltfConstants.GL_FLOAT) { FloatBuffer attributeData = readFloatDracoAttribute(pointAttribute, indices, count, componentCount); VertexBuffer attributeVertexBuffer = createFloatAttributeVertexBuffer(accessor, bufferType, @@ -386,9 +386,10 @@ private static VertexBuffer createAttributeVertexBuffer(String attributeName, Js return attributeVertexBuffer; } throw new AssetLoadException( - "The accessor for attribute " + attributeName + " must have a component type of " + GL.GL_BYTE - + ", " + GL.GL_UNSIGNED_BYTE + ", " + GL.GL_SHORT + ", " + GL.GL_UNSIGNED_SHORT + ", " - + "or " + GL.GL_FLOAT + ", but has " + componentType); + "The accessor for attribute " + attributeName + " must have a component type of " + + GltfConstants.GL_BYTE + ", " + GltfConstants.GL_UNSIGNED_BYTE + ", " + + GltfConstants.GL_SHORT + ", " + GltfConstants.GL_UNSIGNED_SHORT + ", " + "or " + + GltfConstants.GL_FLOAT + ", but has " + componentType); } /** diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfConstants.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfConstants.java new file mode 100644 index 0000000000..9daf714cee --- /dev/null +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfConstants.java @@ -0,0 +1,11 @@ +package com.jme3.scene.plugins.gltf; + +public class GltfConstants { + public static final int GL_BYTE = 0x1400; + public static final int GL_UNSIGNED_BYTE = 0x1401; + public static final int GL_SHORT = 0x1402; + public static final int GL_UNSIGNED_SHORT = 0x1403; + public static final int GL_UNSIGNED_INT = 0x1405; + public static final int GL_FLOAT = 0x1406; + +} diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java index 5ca33a33fa..755229d820 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java @@ -105,17 +105,17 @@ public static Mesh.Mode getMeshMode(Integer mode) { public static VertexBuffer.Format getVertexBufferFormat(int componentType) { switch (componentType) { - case GL.GL_BYTE: + case GltfConstants.GL_BYTE: return VertexBuffer.Format.Byte; - case GL.GL_UNSIGNED_BYTE: + case GltfConstants.GL_UNSIGNED_BYTE: return VertexBuffer.Format.UnsignedByte; - case GL.GL_SHORT: + case GltfConstants.GL_SHORT: return VertexBuffer.Format.Short; - case GL.GL_UNSIGNED_SHORT: + case GltfConstants.GL_UNSIGNED_SHORT: return VertexBuffer.Format.UnsignedShort; - case GL.GL_UNSIGNED_INT: + case GltfConstants.GL_UNSIGNED_INT: return VertexBuffer.Format.UnsignedInt; - case GL.GL_FLOAT: + case GltfConstants.GL_FLOAT: return VertexBuffer.Format.Float; default: throw new AssetLoadException("Illegal component type: " + componentType); From 618c9fa86c1197b80d6404699d33e8ae12fa9f83 Mon Sep 17 00:00:00 2001 From: Riccardo Balbo Date: Thu, 29 Jan 2026 23:03:15 +0100 Subject: [PATCH 07/14] use openize drako fork --- .../src/main/java/jme3test/model/TestGltfLoading.java | 3 --- jme3-plugins/build.gradle | 8 +++++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java b/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java index ff73a6e6c9..0255f7e85a 100644 --- a/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java +++ b/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java @@ -129,7 +129,6 @@ public void simpleInitApp() { // loadModelSample("Avocado", "draco"); - // FIXME: wrong texture coords? // loadModelSample("BarramundiFish", "draco"); // loadModelSample("BoomBox", "draco"); @@ -137,7 +136,6 @@ public void simpleInitApp() { // FIXME: bad skinning? // loadModelSample("BrainStem", "draco"); - // FIXME: wrong offsets? // loadModelSample("CesiumMilkTruck", "draco"); // FIXME: FAILS WITH INDEX OUT OF BOUND EXCEPTION @@ -145,7 +143,6 @@ public void simpleInitApp() { // loadModelSample("Corset", "draco"); - // FIXME: unclear // loadModelSample("Lantern", "draco"); // loadModelSample("MorphPrimitivesTest", "draco"); diff --git a/jme3-plugins/build.gradle b/jme3-plugins/build.gradle index 2d2ca68b9d..bb2fa9fa4a 100644 --- a/jme3-plugins/build.gradle +++ b/jme3-plugins/build.gradle @@ -9,9 +9,15 @@ sourceSets { } } +repositories{ + maven { + url "https://maven.rblb.it/jMonkeyEngine/openize-drako-java/" + } +} + dependencies { api project(':jme3-core') - implementation "dev.fileformat:drako:1.4.2" + implementation "org.jmonkeyengine:drako:1.4.3" implementation project(':jme3-plugins-json') implementation project(':jme3-plugins-json-gson') testRuntimeOnly project(':jme3-desktop') From d810d191f4c8e5343eb0b49e6f317272d9125347 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Sat, 31 Jan 2026 15:51:39 +0100 Subject: [PATCH 08/14] Always minimize visibility. Add comments. --- .../scene/plugins/gltf/GltfConstants.java | 78 +++++++++++++++++-- 1 file changed, 71 insertions(+), 7 deletions(-) diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfConstants.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfConstants.java index 9daf714cee..e9e4a5e853 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfConstants.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfConstants.java @@ -1,11 +1,75 @@ +/* + * Copyright (c) 2009-2026 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package com.jme3.scene.plugins.gltf; -public class GltfConstants { - public static final int GL_BYTE = 0x1400; - public static final int GL_UNSIGNED_BYTE = 0x1401; - public static final int GL_SHORT = 0x1402; - public static final int GL_UNSIGNED_SHORT = 0x1403; - public static final int GL_UNSIGNED_INT = 0x1405; - public static final int GL_FLOAT = 0x1406; +/** + * A package-private class summarizing GL constants that are used in the context of glTF loading. + */ +class GltfConstants { + /** + * GL_BYTE, 5120, 0x1400 + */ + static final int GL_BYTE = 0x1400; + + /** + * GL_UNSIGNED_BYTE, 5121, 0x1401 + */ + static final int GL_UNSIGNED_BYTE = 0x1401; + + /** + * GL_SHORT, 5122, 0x1402 + */ + static final int GL_SHORT = 0x1402; + + /** + * GL_UNSIGNED_SHORT, 5123, 0x1403 + */ + static final int GL_UNSIGNED_SHORT = 0x1403; + + /** + * GL_UNSIGNED_INT, 5125, 0x1405 + */ + static final int GL_UNSIGNED_INT = 0x1405; + + /** + * GL_FLOAT, 5126, 0x1406 + */ + static final int GL_FLOAT = 0x1406; + + /** + * Private constructor to prevent instantiation + */ + private GltfConstants() { + // Private constructor to prevent instantiation + } } From d7dadf42a641aeea59ed35cb07dd6581d2320b10 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Sat, 31 Jan 2026 15:51:49 +0100 Subject: [PATCH 09/14] Minor formatting --- .../com/jme3/scene/plugins/gltf/GltfUtils.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java index 755229d820..6b28840153 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java @@ -734,13 +734,13 @@ public static Integer getAsInteger(JsonObject parent, String name) { * @return The value, as an int * @throws AssetLoadException If the element is not present */ - public static int getAsInt(JsonObject parent, String parentName, String name) { - JsonElement el = parent.get(name); - if (el == null) { - throw new AssetLoadException("No " + name + " defined for " + parentName); - } - return el.getAsInt(); - } + public static int getAsInt(JsonObject parent, String parentName, String name) { + JsonElement el = parent.get(name); + if (el == null) { + throw new AssetLoadException("No " + name + " defined for " + parentName); + } + return el.getAsInt(); + } public static Integer getAsInteger(JsonObject parent, String name, int defaultValue) { JsonElement el = parent.get(name); From 079eeace38f2f283a8d9c785100ffed63d72f297 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Sat, 31 Jan 2026 15:53:02 +0100 Subject: [PATCH 10/14] Restore proper formatting --- .../DracoMeshCompressionExtensionLoader.java | 1037 ++++++++--------- 1 file changed, 518 insertions(+), 519 deletions(-) diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/DracoMeshCompressionExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/DracoMeshCompressionExtensionLoader.java index 97028441ad..f2ceeafb01 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/DracoMeshCompressionExtensionLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/DracoMeshCompressionExtensionLoader.java @@ -76,524 +76,523 @@ */ public class DracoMeshCompressionExtensionLoader implements ExtensionLoader { - /** - * The logger used in this class - */ - private final static Logger logger = Logger - .getLogger(DracoMeshCompressionExtensionLoader.class.getName()); - - /** - * The default log level - */ - private static final Level level = Level.INFO; - - /** - *
    - *
  • The parentName will be "primitive"
  • - *
  • The parent" will be the JSON element that represents the mesh primitive from the glTF - * JSON.
  • - *
  • The extension will be the JSON element that represents the - * KHR_draco_mesh_compression extension object.
  • - *
- * - * {@inheritDoc} - */ - @Override - public Object handleExtension(GltfLoader loader, String parentName, JsonElement parent, - JsonElement extension, Object input) throws IOException { - - logger.log(level, "Decoding draco data"); - - JsonObject meshPrimitiveObject = parent.getAsJsonObject(); - JsonObject extensionObject = extension.getAsJsonObject(); - Mesh mesh = (Mesh) input; - - DracoMesh dracoMesh = readDracoMesh(loader, extension); - - // Fetch the indices, convert them into a vertex buffer, - // and replace the index vertex buffer of the mesh with - // the newly created buffer. - logger.log(level, "Decoding draco indices"); - int indices[] = dracoMesh.getIndices().toArray(); - int indicesAccessorIndex = getAsInt(meshPrimitiveObject, "mesh primitive", "indices"); - JsonObject indicesAccessor = loader.getAccessor(indicesAccessorIndex); - int indicesComponentType = getAsInt(indicesAccessor, "accessor " + indicesAccessorIndex, - "componentType"); - VertexBuffer indicesVertexBuffer = createIndicesVertexBuffer(loader, indicesComponentType, indices); - mesh.clearBuffer(VertexBuffer.Type.Index); - mesh.setBuffer(indicesVertexBuffer); - - // Iterate over all attributes that are found in the - // "attributes" dictionary of the extension object. - // According to the specification, these must be - // a subset of the attributes of the mesh primitive. - JsonObject attributes = extensionObject.get("attributes").getAsJsonObject(); - JsonObject parentAttributes = meshPrimitiveObject.get("attributes").getAsJsonObject(); - for (Entry entry : attributes.entrySet()) { - String attributeName = entry.getKey(); - logger.log(level, "Decoding draco attribute " + attributeName); - - // The extension object stores the attribute ID, which - // is an identifier for the attribute in the decoded - // draco data. It is NOT an accessor index! - int attributeId = entry.getValue().getAsInt(); - PointAttribute pointAttribute = getAttribute(dracoMesh, attributeName, attributeId); - - logger.log(level, "attribute " + attributeName); - logger.log(level, "attributeId " + attributeId); - logger.log(level, "pointAttribute " + pointAttribute); - - // The mesh primitive stores the accessor index for - // each attribute - int attributeAccessorIndex = getAsInt(parentAttributes, attributeName + " attribute", - attributeName); - JsonObject accessor = loader.getAccessor(attributeAccessorIndex); - - logger.log(level, "attributeAccessorIndex " + attributeAccessorIndex); - logger.log(level, "accessor " + accessor); - - // Replace the buffer in the mesh with a buffer that was - // created from the data that was fetched from the - // decoded draco PointAttribute - Type bufferType = getVertexBufferType(attributeName); - VertexBuffer attributeVertexBuffer = createAttributeVertexBuffer(attributeName, accessor, - pointAttribute, indices); - mesh.clearBuffer(bufferType); - mesh.setBuffer(attributeVertexBuffer); - } - - logger.log(level, "Decoding draco data DONE"); - return mesh; - } - - /** - * Read the draco data from the given extension, using openize-drako-java. - * - * @param loader - * The glTF loader - * @param extension - * The draco extension object that was found in a mesh primitive - * @return The Draco mesh - * @throws IOException - * If attempting to load the underlying buffer causes an IO error - */ - private static DracoMesh readDracoMesh(GltfLoader loader, JsonElement extension) throws IOException { - logger.log(level, "Decoding draco mesh"); - - JsonObject jsonObject = extension.getAsJsonObject(); - int bufferViewIndex = getAsInt(jsonObject, "Draco extension object", "bufferView"); - - ByteBuffer bufferViewData = obtainBufferViewData(loader, bufferViewIndex); - - byte bufferViewDataArray[] = new byte[bufferViewData.remaining()]; - bufferViewData.slice().get(bufferViewDataArray); - DracoMesh dracoMesh = null; - try { - dracoMesh = (DracoMesh) Draco.decode(bufferViewDataArray); - } catch (DrakoException e) { - throw new AssetLoadException("Could not decode Draco mesh from buffer view " + bufferViewIndex, - e); - } - - logger.log(level, "Decoding draco mesh DONE"); - return dracoMesh; - } - - /** - * Create the indices vertex buffer that should go into the mesh, based on the given Draco-decoded indices - * - * @param loader - * The glTF loader - * @param accessorIndex - * The accessor index of the vertices - * @param indices - * The Draco-decoded indices - * @return The indices vertex buffer - * @throws AssetLoadException - * If the given component type is not GL_UNSIGNED_BYTE, - * GL_UNSIGNED_SHORT, or GL_UNSIGNED_INT - */ - VertexBuffer createIndicesVertexBuffer(GltfLoader loader, int componentType, int indices[]) { - Buffer data = null; - if (componentType == GltfConstants.GL_UNSIGNED_BYTE) { - data = createByteBuffer(indices); - } else if (componentType == GltfConstants.GL_UNSIGNED_SHORT) { - data = createShortBuffer(indices); - } else if (componentType == GltfConstants.GL_UNSIGNED_INT) { - data = BufferUtils.createIntBuffer(indices); - } else { - throw new AssetLoadException("The indices accessor must have a component type of " - + GltfConstants.GL_UNSIGNED_BYTE + ", " + GltfConstants.GL_UNSIGNED_SHORT + ", or " - + GltfConstants.GL_UNSIGNED_INT - + ", but has " + componentType); - } - VertexBuffer vb = new VertexBuffer(VertexBuffer.Type.Index); - VertexBuffer.Format format = getVertexBufferFormat(componentType); - int numComponents = 3; - vb.setupData(VertexBuffer.Usage.Dynamic, numComponents, format, data); - return vb; - } - - // TODO_DRACO Could go into GltfUtils - /** - * Determines the number of components per element for the given accessor, based on its type - * - * @param accessor - * The accessor - * @return The number of components - * @throws AssetLoadException - * If the accessor does not have a valid type property - */ - private static int getAccessorComponentCount(JsonObject accessor) { - String type = getAsString(accessor, "type"); - assertNotNull(type, "No type attribute defined for accessor"); - return getNumberOfComponents(type); - } - - // TODO_DRACO Could go into BufferUtils - /** - * Create a byte buffer containing the given values, cast to byte - * - * @param array - * The array - * @return The buffer - */ - private static Buffer createByteBuffer(int[] array) { - ByteBuffer buffer = BufferUtils.createByteBuffer(array.length); - for (int i = 0; i < array.length; i++) { - buffer.put(i, (byte) array[i]); - } - return buffer; - } - - // TODO_DRACO Could go into BufferUtils - /** - * Create a short buffer containing the given values, cast to short - * - * @param array - * The array - * @return The buffer - */ - private static Buffer createShortBuffer(int[] array) { - ShortBuffer buffer = BufferUtils.createShortBuffer(array.length); - for (int i = 0; i < array.length; i++) { - buffer.put(i, (short) array[i]); - } - return buffer; - } - - // TODO_DRACO Could fit into GltfLoader - /** - * Obtain the data for the specified buffer view of the given loader. - * - * This will return a slice of the data of the underlying buffer. Callers may not modify the returned - * data. - * - * @param loader - * The loader - * @param bufferViewIndex - * The buffer view index - * @return The buffer view data - * @throws IOException - * If attempting to load the underlying buffer causes an IO error - * @throws AssetLoadException - * If the specified index is not valid, or the buffer view did not define a valid buffer index - * or byte length - */ - private static ByteBuffer obtainBufferViewData(GltfLoader loader, int bufferViewIndex) - throws IOException { - JsonObject bufferView = loader.getBufferView(bufferViewIndex); - int bufferIndex = getAsInt(bufferView, "bufferView", "buffer"); - assertNotNull(bufferIndex, "No buffer defined for bufferView " + bufferViewIndex); - - int byteOffset = getAsInteger(bufferView, "byteOffset", 0); - int byteLength = getAsInt(bufferView, "bufferView " + bufferViewIndex, "byteLength"); - - ByteBuffer bufferData = loader.readData(bufferIndex); - ByteBuffer bufferViewData = bufferData.slice(); - bufferViewData.limit(byteOffset + byteLength); - bufferViewData.position(byteOffset); - return bufferViewData; - } - - /** - * Obtains the point attribute with the given ID from the given draco mesh. - * - * @param dracoMesh - * The draco mesh - * @param gltfAttribute - * The glTF attribute name, like "POSITION" (only used for error messages) - * @param id - * The unique ID of the attribute, i.e. the value that was stored as the - * "POSITION": id in the draco extension JSON object. - * @return The point attribute - * @throws AssetLoadException - * If the attribute with the given ID cannot be found - */ - private static PointAttribute getAttribute(DracoMesh dracoMesh, String gltfAttribute, int id) { - for (int i = 0; i < dracoMesh.getNumAttributes(); i++) { - PointAttribute attribute = dracoMesh.attribute(i); - if (attribute.getUniqueId() == id) { - return attribute; - } - } - throw new AssetLoadException("Could not obtain attribute " + gltfAttribute + " with unique ID " + id - + " from decoded Draco mesh"); - } - - /** - * Creates a vertex buffer for the specified attribute, according to the structure that is described by - * the given accessor JSON object, using the data that is obtained from the given Draco-decoded point - * attribute - * - * @param attributeName - * The attribute name - * @param accessor - * The accessor JSON object - * @param pointAttribute - * The Draco-decoded point attribute - * @param indices - * The indices, obtained from the draco mesh - * @return The vertex buffer - * @throws AssetLoadException - * If the given accessor does not have a component type that is valid for a vertex attribute - */ - private static VertexBuffer createAttributeVertexBuffer(String attributeName, JsonObject accessor, - PointAttribute pointAttribute, int indices[]) { - int count = getAsInt(accessor, "accessor", "count"); - int componentType = getAsInt(accessor, "accessor", "componentType"); - int componentCount = getAccessorComponentCount(accessor); - Type bufferType = getVertexBufferType(attributeName); - - if (componentType == GltfConstants.GL_BYTE || componentType == GltfConstants.GL_UNSIGNED_BYTE) { - ByteBuffer attributeData = readByteDracoAttribute(pointAttribute, indices, count, componentCount); - VertexBuffer attributeVertexBuffer = createByteAttributeVertexBuffer(accessor, bufferType, - attributeData); - return attributeVertexBuffer; - } - if (componentType == GltfConstants.GL_SHORT || componentType == GltfConstants.GL_UNSIGNED_SHORT) { - ShortBuffer attributeData = readShortDracoAttribute(pointAttribute, indices, count, - componentCount); - VertexBuffer attributeVertexBuffer = createShortAttributeVertexBuffer(accessor, bufferType, - attributeData); - return attributeVertexBuffer; - } - if (componentType == GltfConstants.GL_FLOAT) { - FloatBuffer attributeData = readFloatDracoAttribute(pointAttribute, indices, count, - componentCount); - VertexBuffer attributeVertexBuffer = createFloatAttributeVertexBuffer(accessor, bufferType, - attributeData); - return attributeVertexBuffer; - } - throw new AssetLoadException( - "The accessor for attribute " + attributeName + " must have a component type of " - + GltfConstants.GL_BYTE + ", " + GltfConstants.GL_UNSIGNED_BYTE + ", " - + GltfConstants.GL_SHORT + ", " + GltfConstants.GL_UNSIGNED_SHORT + ", " + "or " - + GltfConstants.GL_FLOAT + ", but has " + componentType); - } - - /** - * Read the data from the given point attribute, as byte values - * - * @param pointAttribute - * The Draco-decoded point attribute - * @param indices - * The indices, obtained from the draco mesh - * @param count - * The count, obtained from the accessor for this attribute - * @param componentCount - * The component count (number of components per element), obtained from the accessor type - * @return The resulting data, as a byte buffer - */ - private static ByteBuffer readByteDracoAttribute(PointAttribute pointAttribute, int indices[], int count, - int componentCount) { - int numFaces = indices.length / 3; - byte p[] = new byte[componentCount]; - ByteBuffer attributeData = BufferUtils.createByteBuffer(count * componentCount); - for (int i = 0; i < numFaces; i++) { - int j0 = indices[i * 3 + 0]; - int j1 = indices[i * 3 + 1]; - int j2 = indices[i * 3 + 2]; - - int mj0 = pointAttribute.mappedIndex(j0); - int mj1 = pointAttribute.mappedIndex(j1); - int mj2 = pointAttribute.mappedIndex(j2); - - pointAttribute.getValue(mj0, p); - int offset0 = j0 * componentCount; - for (int c = 0; c < componentCount; c++) { - attributeData.put(offset0 + c, p[c]); - } - pointAttribute.getValue(mj1, p); - int offset1 = j1 * componentCount; - for (int c = 0; c < componentCount; c++) { - attributeData.put(offset1 + c, p[c]); - } - pointAttribute.getValue(mj2, p); - int offset2 = j2 * componentCount; - for (int c = 0; c < componentCount; c++) { - attributeData.put(offset2 + c, p[c]); - } - } - return attributeData; - } - - /** - * Read the data from the given point attribute, as short values - * - * @param pointAttribute - * The Draco-decoded point attribute - * @param indices - * The indices, obtained from the draco mesh - * @param count - * The count, obtained from the accessor for this attribute - * @param componentCount - * The component count (number of components per element), obtained from the accessor type - * @return The resulting data, as a short buffer - */ - private static ShortBuffer readShortDracoAttribute(PointAttribute pointAttribute, int indices[], - int count, int componentCount) { - int numFaces = indices.length / 3; - short p[] = new short[componentCount]; - ShortBuffer attributeData = BufferUtils.createShortBuffer(count * componentCount); - for (int i = 0; i < numFaces; i++) { - int j0 = indices[i * 3 + 0]; - int j1 = indices[i * 3 + 1]; - int j2 = indices[i * 3 + 2]; - - int mj0 = pointAttribute.mappedIndex(j0); - int mj1 = pointAttribute.mappedIndex(j1); - int mj2 = pointAttribute.mappedIndex(j2); - - pointAttribute.getValue(mj0, p); - int offset0 = j0 * componentCount; - for (int c = 0; c < componentCount; c++) { - attributeData.put(offset0 + c, p[c]); - } - pointAttribute.getValue(mj1, p); - int offset1 = j1 * componentCount; - for (int c = 0; c < componentCount; c++) { - attributeData.put(offset1 + c, p[c]); - } - pointAttribute.getValue(mj2, p); - int offset2 = j2 * componentCount; - for (int c = 0; c < componentCount; c++) { - attributeData.put(offset2 + c, p[c]); - } - } - return attributeData; - } - - /** - * Read the data from the given point attribute, as float values - * - * @param pointAttribute - * The Draco-decoded point attribute - * @param indices - * The indices, obtained from the draco mesh - * @param count - * The count, obtained from the accessor for this attribute - * @param componentCount - * The component count (number of components per element), obtained from the accessor type - * @return The resulting data, as a float buffer - */ - private static FloatBuffer readFloatDracoAttribute(PointAttribute pointAttribute, int indices[], - int count, int componentCount) { - int numFaces = indices.length / 3; - float p[] = new float[componentCount]; - FloatBuffer attributeData = BufferUtils.createFloatBuffer(count * componentCount); - for (int i = 0; i < numFaces; i++) { - int j0 = indices[i * 3 + 0]; - int j1 = indices[i * 3 + 1]; - int j2 = indices[i * 3 + 2]; - - int mj0 = pointAttribute.mappedIndex(j0); - int mj1 = pointAttribute.mappedIndex(j1); - int mj2 = pointAttribute.mappedIndex(j2); - - pointAttribute.getValue(mj0, p); - int offset0 = j0 * componentCount; - for (int c = 0; c < componentCount; c++) { - attributeData.put(offset0 + c, p[c]); - } - pointAttribute.getValue(mj1, p); - int offset1 = j1 * componentCount; - for (int c = 0; c < componentCount; c++) { - attributeData.put(offset1 + c, p[c]); - } - pointAttribute.getValue(mj2, p); - int offset2 = j2 * componentCount; - for (int c = 0; c < componentCount; c++) { - attributeData.put(offset2 + c, p[c]); - } - } - return attributeData; - } - - /** - * Create the vertex buffer for the given byte attribute data - * - * @param accessor - * The accessor that describes the component type and type - * - * @param bufferType - * The buffer type - * @param attributeData - * The attribute data - * @return The vertex buffer - */ - private static VertexBuffer createByteAttributeVertexBuffer(JsonObject accessor, - VertexBuffer.Type bufferType, ByteBuffer attributeData) { - int componentType = getAsInt(accessor, "accessor", "componentType"); - VertexBuffer vb = new VertexBuffer(bufferType); - VertexBuffer.Format format = getVertexBufferFormat(componentType); - int numComponents = getAccessorComponentCount(accessor); - vb.setupData(VertexBuffer.Usage.Dynamic, numComponents, format, attributeData); - return vb; - } - - /** - * Create the vertex buffer for the given short attribute data - * - * @param accessor - * The accessor that describes the component type and type - * - * @param bufferType - * The buffer type - * @param attributeData - * The attribute data - * @return The vertex buffer - */ - private static VertexBuffer createShortAttributeVertexBuffer(JsonObject accessor, - VertexBuffer.Type bufferType, ShortBuffer attributeData) { - int componentType = getAsInt(accessor, "accessor", "componentType"); - VertexBuffer vb = new VertexBuffer(bufferType); - VertexBuffer.Format format = getVertexBufferFormat(componentType); - int numComponents = getAccessorComponentCount(accessor); - vb.setupData(VertexBuffer.Usage.Dynamic, numComponents, format, attributeData); - return vb; - } - - /** - * Create the vertex buffer for the given float attribute data - * - * @param accessor - * The accessor that describes the component type and type - * - * @param bufferType - * The buffer type - * @param attributeData - * The attribute data - * @return The vertex buffer - */ - private static VertexBuffer createFloatAttributeVertexBuffer(JsonObject accessor, - VertexBuffer.Type bufferType, FloatBuffer attributeData) { - int componentType = getAsInt(accessor, "accessor", "componentType"); - VertexBuffer vb = new VertexBuffer(bufferType); - VertexBuffer.Format format = getVertexBufferFormat(componentType); - int numComponents = getAccessorComponentCount(accessor); - vb.setupData(VertexBuffer.Usage.Dynamic, numComponents, format, attributeData); - return vb; - } + /** + * The logger used in this class + */ + private final static Logger logger = Logger + .getLogger(DracoMeshCompressionExtensionLoader.class.getName()); + + /** + * The default log level + */ + private static final Level level = Level.INFO; + + /** + *
    + *
  • The parentName will be "primitive"
  • + *
  • The parent" will be the JSON element that represents the mesh primitive from the glTF + * JSON.
  • + *
  • The extension will be the JSON element that represents the + * KHR_draco_mesh_compression extension object.
  • + *
+ * + * {@inheritDoc} + */ + @Override + public Object handleExtension(GltfLoader loader, String parentName, JsonElement parent, + JsonElement extension, Object input) throws IOException { + + logger.log(level, "Decoding draco data"); + + JsonObject meshPrimitiveObject = parent.getAsJsonObject(); + JsonObject extensionObject = extension.getAsJsonObject(); + Mesh mesh = (Mesh) input; + + DracoMesh dracoMesh = readDracoMesh(loader, extension); + + // Fetch the indices, convert them into a vertex buffer, + // and replace the index vertex buffer of the mesh with + // the newly created buffer. + logger.log(level, "Decoding draco indices"); + int indices[] = dracoMesh.getIndices().toArray(); + int indicesAccessorIndex = getAsInt(meshPrimitiveObject, "mesh primitive", "indices"); + JsonObject indicesAccessor = loader.getAccessor(indicesAccessorIndex); + int indicesComponentType = getAsInt(indicesAccessor, "accessor " + indicesAccessorIndex, + "componentType"); + VertexBuffer indicesVertexBuffer = createIndicesVertexBuffer(loader, indicesComponentType, indices); + mesh.clearBuffer(VertexBuffer.Type.Index); + mesh.setBuffer(indicesVertexBuffer); + + // Iterate over all attributes that are found in the + // "attributes" dictionary of the extension object. + // According to the specification, these must be + // a subset of the attributes of the mesh primitive. + JsonObject attributes = extensionObject.get("attributes").getAsJsonObject(); + JsonObject parentAttributes = meshPrimitiveObject.get("attributes").getAsJsonObject(); + for (Entry entry : attributes.entrySet()) { + String attributeName = entry.getKey(); + logger.log(level, "Decoding draco attribute " + attributeName); + + // The extension object stores the attribute ID, which + // is an identifier for the attribute in the decoded + // draco data. It is NOT an accessor index! + int attributeId = entry.getValue().getAsInt(); + PointAttribute pointAttribute = getAttribute(dracoMesh, attributeName, attributeId); + + logger.log(level, "attribute " + attributeName); + logger.log(level, "attributeId " + attributeId); + logger.log(level, "pointAttribute " + pointAttribute); + + // The mesh primitive stores the accessor index for + // each attribute + int attributeAccessorIndex = getAsInt(parentAttributes, attributeName + " attribute", + attributeName); + JsonObject accessor = loader.getAccessor(attributeAccessorIndex); + + logger.log(level, "attributeAccessorIndex " + attributeAccessorIndex); + logger.log(level, "accessor " + accessor); + + // Replace the buffer in the mesh with a buffer that was + // created from the data that was fetched from the + // decoded draco PointAttribute + Type bufferType = getVertexBufferType(attributeName); + VertexBuffer attributeVertexBuffer = createAttributeVertexBuffer(attributeName, accessor, + pointAttribute, indices); + mesh.clearBuffer(bufferType); + mesh.setBuffer(attributeVertexBuffer); + } + + logger.log(level, "Decoding draco data DONE"); + return mesh; + } + + /** + * Read the draco data from the given extension, using openize-drako-java. + * + * @param loader + * The glTF loader + * @param extension + * The draco extension object that was found in a mesh primitive + * @return The Draco mesh + * @throws IOException + * If attempting to load the underlying buffer causes an IO error + */ + private static DracoMesh readDracoMesh(GltfLoader loader, JsonElement extension) throws IOException { + logger.log(level, "Decoding draco mesh"); + + JsonObject jsonObject = extension.getAsJsonObject(); + int bufferViewIndex = getAsInt(jsonObject, "Draco extension object", "bufferView"); + + ByteBuffer bufferViewData = obtainBufferViewData(loader, bufferViewIndex); + + byte bufferViewDataArray[] = new byte[bufferViewData.remaining()]; + bufferViewData.slice().get(bufferViewDataArray); + DracoMesh dracoMesh = null; + try { + dracoMesh = (DracoMesh) Draco.decode(bufferViewDataArray); + } catch (DrakoException e) { + throw new AssetLoadException("Could not decode Draco mesh from buffer view " + bufferViewIndex, + e); + } + + logger.log(level, "Decoding draco mesh DONE"); + return dracoMesh; + } + + /** + * Create the indices vertex buffer that should go into the mesh, based on the given Draco-decoded indices + * + * @param loader + * The glTF loader + * @param accessorIndex + * The accessor index of the vertices + * @param indices + * The Draco-decoded indices + * @return The indices vertex buffer + * @throws AssetLoadException + * If the given component type is not GL_UNSIGNED_BYTE, + * GL_UNSIGNED_SHORT, or GL_UNSIGNED_INT + */ + VertexBuffer createIndicesVertexBuffer(GltfLoader loader, int componentType, int indices[]) { + Buffer data = null; + if (componentType == GltfConstants.GL_UNSIGNED_BYTE) { + data = createByteBuffer(indices); + } else if (componentType == GltfConstants.GL_UNSIGNED_SHORT) { + data = createShortBuffer(indices); + } else if (componentType == GltfConstants.GL_UNSIGNED_INT) { + data = BufferUtils.createIntBuffer(indices); + } else { + throw new AssetLoadException("The indices accessor must have a component type of " + + GltfConstants.GL_UNSIGNED_BYTE + ", " + GltfConstants.GL_UNSIGNED_SHORT + ", or " + + GltfConstants.GL_UNSIGNED_INT + ", but has " + componentType); + } + VertexBuffer vb = new VertexBuffer(VertexBuffer.Type.Index); + VertexBuffer.Format format = getVertexBufferFormat(componentType); + int numComponents = 3; + vb.setupData(VertexBuffer.Usage.Dynamic, numComponents, format, data); + return vb; + } + + // TODO_DRACO Could go into GltfUtils + /** + * Determines the number of components per element for the given accessor, based on its type + * + * @param accessor + * The accessor + * @return The number of components + * @throws AssetLoadException + * If the accessor does not have a valid type property + */ + private static int getAccessorComponentCount(JsonObject accessor) { + String type = getAsString(accessor, "type"); + assertNotNull(type, "No type attribute defined for accessor"); + return getNumberOfComponents(type); + } + + // TODO_DRACO Could go into BufferUtils + /** + * Create a byte buffer containing the given values, cast to byte + * + * @param array + * The array + * @return The buffer + */ + private static Buffer createByteBuffer(int[] array) { + ByteBuffer buffer = BufferUtils.createByteBuffer(array.length); + for (int i = 0; i < array.length; i++) { + buffer.put(i, (byte) array[i]); + } + return buffer; + } + + // TODO_DRACO Could go into BufferUtils + /** + * Create a short buffer containing the given values, cast to short + * + * @param array + * The array + * @return The buffer + */ + private static Buffer createShortBuffer(int[] array) { + ShortBuffer buffer = BufferUtils.createShortBuffer(array.length); + for (int i = 0; i < array.length; i++) { + buffer.put(i, (short) array[i]); + } + return buffer; + } + + // TODO_DRACO Could fit into GltfLoader + /** + * Obtain the data for the specified buffer view of the given loader. + * + * This will return a slice of the data of the underlying buffer. Callers may not modify the returned + * data. + * + * @param loader + * The loader + * @param bufferViewIndex + * The buffer view index + * @return The buffer view data + * @throws IOException + * If attempting to load the underlying buffer causes an IO error + * @throws AssetLoadException + * If the specified index is not valid, or the buffer view did not define a valid buffer index + * or byte length + */ + private static ByteBuffer obtainBufferViewData(GltfLoader loader, int bufferViewIndex) + throws IOException { + JsonObject bufferView = loader.getBufferView(bufferViewIndex); + int bufferIndex = getAsInt(bufferView, "bufferView", "buffer"); + assertNotNull(bufferIndex, "No buffer defined for bufferView " + bufferViewIndex); + + int byteOffset = getAsInteger(bufferView, "byteOffset", 0); + int byteLength = getAsInt(bufferView, "bufferView " + bufferViewIndex, "byteLength"); + + ByteBuffer bufferData = loader.readData(bufferIndex); + ByteBuffer bufferViewData = bufferData.slice(); + bufferViewData.limit(byteOffset + byteLength); + bufferViewData.position(byteOffset); + return bufferViewData; + } + + /** + * Obtains the point attribute with the given ID from the given draco mesh. + * + * @param dracoMesh + * The draco mesh + * @param gltfAttribute + * The glTF attribute name, like "POSITION" (only used for error messages) + * @param id + * The unique ID of the attribute, i.e. the value that was stored as the + * "POSITION": id in the draco extension JSON object. + * @return The point attribute + * @throws AssetLoadException + * If the attribute with the given ID cannot be found + */ + private static PointAttribute getAttribute(DracoMesh dracoMesh, String gltfAttribute, int id) { + for (int i = 0; i < dracoMesh.getNumAttributes(); i++) { + PointAttribute attribute = dracoMesh.attribute(i); + if (attribute.getUniqueId() == id) { + return attribute; + } + } + throw new AssetLoadException("Could not obtain attribute " + gltfAttribute + " with unique ID " + id + + " from decoded Draco mesh"); + } + + /** + * Creates a vertex buffer for the specified attribute, according to the structure that is described by + * the given accessor JSON object, using the data that is obtained from the given Draco-decoded point + * attribute + * + * @param attributeName + * The attribute name + * @param accessor + * The accessor JSON object + * @param pointAttribute + * The Draco-decoded point attribute + * @param indices + * The indices, obtained from the draco mesh + * @return The vertex buffer + * @throws AssetLoadException + * If the given accessor does not have a component type that is valid for a vertex attribute + */ + private static VertexBuffer createAttributeVertexBuffer(String attributeName, JsonObject accessor, + PointAttribute pointAttribute, int indices[]) { + int count = getAsInt(accessor, "accessor", "count"); + int componentType = getAsInt(accessor, "accessor", "componentType"); + int componentCount = getAccessorComponentCount(accessor); + Type bufferType = getVertexBufferType(attributeName); + + if (componentType == GltfConstants.GL_BYTE || componentType == GltfConstants.GL_UNSIGNED_BYTE) { + ByteBuffer attributeData = readByteDracoAttribute(pointAttribute, indices, count, componentCount); + VertexBuffer attributeVertexBuffer = createByteAttributeVertexBuffer(accessor, bufferType, + attributeData); + return attributeVertexBuffer; + } + if (componentType == GltfConstants.GL_SHORT || componentType == GltfConstants.GL_UNSIGNED_SHORT) { + ShortBuffer attributeData = readShortDracoAttribute(pointAttribute, indices, count, + componentCount); + VertexBuffer attributeVertexBuffer = createShortAttributeVertexBuffer(accessor, bufferType, + attributeData); + return attributeVertexBuffer; + } + if (componentType == GltfConstants.GL_FLOAT) { + FloatBuffer attributeData = readFloatDracoAttribute(pointAttribute, indices, count, + componentCount); + VertexBuffer attributeVertexBuffer = createFloatAttributeVertexBuffer(accessor, bufferType, + attributeData); + return attributeVertexBuffer; + } + throw new AssetLoadException( + "The accessor for attribute " + attributeName + " must have a component type of " + + GltfConstants.GL_BYTE + ", " + GltfConstants.GL_UNSIGNED_BYTE + ", " + + GltfConstants.GL_SHORT + ", " + GltfConstants.GL_UNSIGNED_SHORT + ", " + "or " + + GltfConstants.GL_FLOAT + ", but has " + componentType); + } + + /** + * Read the data from the given point attribute, as byte values + * + * @param pointAttribute + * The Draco-decoded point attribute + * @param indices + * The indices, obtained from the draco mesh + * @param count + * The count, obtained from the accessor for this attribute + * @param componentCount + * The component count (number of components per element), obtained from the accessor type + * @return The resulting data, as a byte buffer + */ + private static ByteBuffer readByteDracoAttribute(PointAttribute pointAttribute, int indices[], int count, + int componentCount) { + int numFaces = indices.length / 3; + byte p[] = new byte[componentCount]; + ByteBuffer attributeData = BufferUtils.createByteBuffer(count * componentCount); + for (int i = 0; i < numFaces; i++) { + int j0 = indices[i * 3 + 0]; + int j1 = indices[i * 3 + 1]; + int j2 = indices[i * 3 + 2]; + + int mj0 = pointAttribute.mappedIndex(j0); + int mj1 = pointAttribute.mappedIndex(j1); + int mj2 = pointAttribute.mappedIndex(j2); + + pointAttribute.getValue(mj0, p); + int offset0 = j0 * componentCount; + for (int c = 0; c < componentCount; c++) { + attributeData.put(offset0 + c, p[c]); + } + pointAttribute.getValue(mj1, p); + int offset1 = j1 * componentCount; + for (int c = 0; c < componentCount; c++) { + attributeData.put(offset1 + c, p[c]); + } + pointAttribute.getValue(mj2, p); + int offset2 = j2 * componentCount; + for (int c = 0; c < componentCount; c++) { + attributeData.put(offset2 + c, p[c]); + } + } + return attributeData; + } + + /** + * Read the data from the given point attribute, as short values + * + * @param pointAttribute + * The Draco-decoded point attribute + * @param indices + * The indices, obtained from the draco mesh + * @param count + * The count, obtained from the accessor for this attribute + * @param componentCount + * The component count (number of components per element), obtained from the accessor type + * @return The resulting data, as a short buffer + */ + private static ShortBuffer readShortDracoAttribute(PointAttribute pointAttribute, int indices[], + int count, int componentCount) { + int numFaces = indices.length / 3; + short p[] = new short[componentCount]; + ShortBuffer attributeData = BufferUtils.createShortBuffer(count * componentCount); + for (int i = 0; i < numFaces; i++) { + int j0 = indices[i * 3 + 0]; + int j1 = indices[i * 3 + 1]; + int j2 = indices[i * 3 + 2]; + + int mj0 = pointAttribute.mappedIndex(j0); + int mj1 = pointAttribute.mappedIndex(j1); + int mj2 = pointAttribute.mappedIndex(j2); + + pointAttribute.getValue(mj0, p); + int offset0 = j0 * componentCount; + for (int c = 0; c < componentCount; c++) { + attributeData.put(offset0 + c, p[c]); + } + pointAttribute.getValue(mj1, p); + int offset1 = j1 * componentCount; + for (int c = 0; c < componentCount; c++) { + attributeData.put(offset1 + c, p[c]); + } + pointAttribute.getValue(mj2, p); + int offset2 = j2 * componentCount; + for (int c = 0; c < componentCount; c++) { + attributeData.put(offset2 + c, p[c]); + } + } + return attributeData; + } + + /** + * Read the data from the given point attribute, as float values + * + * @param pointAttribute + * The Draco-decoded point attribute + * @param indices + * The indices, obtained from the draco mesh + * @param count + * The count, obtained from the accessor for this attribute + * @param componentCount + * The component count (number of components per element), obtained from the accessor type + * @return The resulting data, as a float buffer + */ + private static FloatBuffer readFloatDracoAttribute(PointAttribute pointAttribute, int indices[], + int count, int componentCount) { + int numFaces = indices.length / 3; + float p[] = new float[componentCount]; + FloatBuffer attributeData = BufferUtils.createFloatBuffer(count * componentCount); + for (int i = 0; i < numFaces; i++) { + int j0 = indices[i * 3 + 0]; + int j1 = indices[i * 3 + 1]; + int j2 = indices[i * 3 + 2]; + + int mj0 = pointAttribute.mappedIndex(j0); + int mj1 = pointAttribute.mappedIndex(j1); + int mj2 = pointAttribute.mappedIndex(j2); + + pointAttribute.getValue(mj0, p); + int offset0 = j0 * componentCount; + for (int c = 0; c < componentCount; c++) { + attributeData.put(offset0 + c, p[c]); + } + pointAttribute.getValue(mj1, p); + int offset1 = j1 * componentCount; + for (int c = 0; c < componentCount; c++) { + attributeData.put(offset1 + c, p[c]); + } + pointAttribute.getValue(mj2, p); + int offset2 = j2 * componentCount; + for (int c = 0; c < componentCount; c++) { + attributeData.put(offset2 + c, p[c]); + } + } + return attributeData; + } + + /** + * Create the vertex buffer for the given byte attribute data + * + * @param accessor + * The accessor that describes the component type and type + * + * @param bufferType + * The buffer type + * @param attributeData + * The attribute data + * @return The vertex buffer + */ + private static VertexBuffer createByteAttributeVertexBuffer(JsonObject accessor, + VertexBuffer.Type bufferType, ByteBuffer attributeData) { + int componentType = getAsInt(accessor, "accessor", "componentType"); + VertexBuffer vb = new VertexBuffer(bufferType); + VertexBuffer.Format format = getVertexBufferFormat(componentType); + int numComponents = getAccessorComponentCount(accessor); + vb.setupData(VertexBuffer.Usage.Dynamic, numComponents, format, attributeData); + return vb; + } + + /** + * Create the vertex buffer for the given short attribute data + * + * @param accessor + * The accessor that describes the component type and type + * + * @param bufferType + * The buffer type + * @param attributeData + * The attribute data + * @return The vertex buffer + */ + private static VertexBuffer createShortAttributeVertexBuffer(JsonObject accessor, + VertexBuffer.Type bufferType, ShortBuffer attributeData) { + int componentType = getAsInt(accessor, "accessor", "componentType"); + VertexBuffer vb = new VertexBuffer(bufferType); + VertexBuffer.Format format = getVertexBufferFormat(componentType); + int numComponents = getAccessorComponentCount(accessor); + vb.setupData(VertexBuffer.Usage.Dynamic, numComponents, format, attributeData); + return vb; + } + + /** + * Create the vertex buffer for the given float attribute data + * + * @param accessor + * The accessor that describes the component type and type + * + * @param bufferType + * The buffer type + * @param attributeData + * The attribute data + * @return The vertex buffer + */ + private static VertexBuffer createFloatAttributeVertexBuffer(JsonObject accessor, + VertexBuffer.Type bufferType, FloatBuffer attributeData) { + int componentType = getAsInt(accessor, "accessor", "componentType"); + VertexBuffer vb = new VertexBuffer(bufferType); + VertexBuffer.Format format = getVertexBufferFormat(componentType); + int numComponents = getAccessorComponentCount(accessor); + vb.setupData(VertexBuffer.Usage.Dynamic, numComponents, format, attributeData); + return vb; + } } From ecd6c9b041040919528782b395d97ab2c23e8516 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Sat, 31 Jan 2026 15:54:00 +0100 Subject: [PATCH 11/14] Handle quantized/normalized attributes from Draco --- .../plugins/gltf/BufferQuantization.java | 179 ++++++++++++++++++ .../DracoMeshCompressionExtensionLoader.java | 49 ++++- 2 files changed, 223 insertions(+), 5 deletions(-) create mode 100644 jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/BufferQuantization.java diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/BufferQuantization.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/BufferQuantization.java new file mode 100644 index 0000000000..ce9a309b60 --- /dev/null +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/BufferQuantization.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2009-2026 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.gltf; + +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; + +import com.jme3.util.BufferUtils; + +/** + * A package-private class to perform dequantization of buffers. + * + * This handled buffers that contain (unsigned) byte or short values and that are "normalized", i.e. supposed + * to be interpreted as float values. + * + * (NOTE: Some of these methods are taken from a non-published state of JglTF, but published by the original + * author, as part of JMonkeyEngine) + */ +class BufferQuantization { + + /** + * Dequantize the given buffer into a float buffer, treating each element of the input as a signed byte. + * + * @param byteBuffer + * The input buffer + * @return The result + */ + static FloatBuffer dequantizeByteBuffer(ByteBuffer byteBuffer) { + FloatBuffer floatBuffer = BufferUtils.createFloatBuffer(byteBuffer.capacity()); + for (int i = 0; i < byteBuffer.capacity(); i++) { + byte c = byteBuffer.get(i); + float f = dequantizeByte(c); + floatBuffer.put(i, f); + } + return floatBuffer; + } + + /** + * Dequantize the given buffer into a float buffer, treating each element of the input as an unsigned + * byte. + * + * @param byteBuffer + * The input buffer + * @return The result + */ + static FloatBuffer dequantizeUnsignedByteBuffer(ByteBuffer byteBuffer) { + FloatBuffer floatBuffer = BufferUtils.createFloatBuffer(byteBuffer.capacity()); + for (int i = 0; i < byteBuffer.capacity(); i++) { + byte c = byteBuffer.get(i); + float f = dequantizeUnsignedByte(c); + floatBuffer.put(i, f); + } + return floatBuffer; + } + + /** + * Dequantize the given buffer into a float buffer, treating each element of the input as a signed short. + * + * @param shortBuffer + * The input buffer + * @return The result + */ + static FloatBuffer dequantizeShortBuffer(ShortBuffer shortBuffer) { + FloatBuffer floatBuffer = BufferUtils.createFloatBuffer(shortBuffer.capacity()); + for (int i = 0; i < shortBuffer.capacity(); i++) { + short c = shortBuffer.get(i); + float f = dequantizeShort(c); + floatBuffer.put(i, f); + } + return floatBuffer; + } + + /** + * Dequantize the given buffer into a float buffer, treating each element of the input as an unsigned + * short. + * + * @param shortBuffer + * The input buffer + * @return The result + */ + static FloatBuffer dequantizeUnsignedShortBuffer(ShortBuffer shortBuffer) { + FloatBuffer floatBuffer = BufferUtils.createFloatBuffer(shortBuffer.capacity()); + for (int i = 0; i < shortBuffer.capacity(); i++) { + short c = shortBuffer.get(i); + float f = dequantizeUnsignedShort(c); + floatBuffer.put(i, f); + } + return floatBuffer; + } + + /** + * Dequantize the given signed byte into a floating point value + * + * @param c + * The input + * @return The result + */ + private static float dequantizeByte(byte c) { + float f = Math.max(c / 127.0f, -1.0f); + return f; + } + + /** + * Dequantize the given unsigned byte into a floating point value + * + * @param c + * The input + * @return The result + */ + private static float dequantizeUnsignedByte(byte c) { + int i = Byte.toUnsignedInt(c); + float f = i / 255.0f; + return f; + } + + /** + * Dequantize the given signed short into a floating point value + * + * @param c + * The input + * @return The result + */ + private static float dequantizeShort(short c) { + float f = Math.max(c / 32767.0f, -1.0f); + return f; + } + + /** + * + * Dequantize the given unsigned byte into a floating point value + * + * @param c + * The input + * @return The result + */ + private static float dequantizeUnsignedShort(short c) { + int i = Short.toUnsignedInt(c); + float f = i / 65535.0f; + return f; + } + + /** + * Private constructor to prevent instantiation + */ + private BufferQuantization() { + // Private constructor to prevent instantiation + } + +} diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/DracoMeshCompressionExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/DracoMeshCompressionExtensionLoader.java index f2ceeafb01..2c978da2fe 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/DracoMeshCompressionExtensionLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/DracoMeshCompressionExtensionLoader.java @@ -32,6 +32,7 @@ package com.jme3.scene.plugins.gltf; import static com.jme3.scene.plugins.gltf.GltfUtils.assertNotNull; +import static com.jme3.scene.plugins.gltf.GltfUtils.getAsBoolean; import static com.jme3.scene.plugins.gltf.GltfUtils.getAsInt; import static com.jme3.scene.plugins.gltf.GltfUtils.getAsInteger; import static com.jme3.scene.plugins.gltf.GltfUtils.getAsString; @@ -530,7 +531,10 @@ private static FloatBuffer readFloatDracoAttribute(PointAttribute pointAttribute } /** - * Create the vertex buffer for the given byte attribute data + * Create the vertex buffer for the given byte attribute data. + * + * If the accessor is normalized, then this will dequantize the given data into a + * Float vertex buffer. * * @param accessor * The accessor that describes the component type and type @@ -545,15 +549,34 @@ private static VertexBuffer createByteAttributeVertexBuffer(JsonObject accessor, VertexBuffer.Type bufferType, ByteBuffer attributeData) { int componentType = getAsInt(accessor, "accessor", "componentType"); VertexBuffer vb = new VertexBuffer(bufferType); - VertexBuffer.Format format = getVertexBufferFormat(componentType); int numComponents = getAccessorComponentCount(accessor); - vb.setupData(VertexBuffer.Usage.Dynamic, numComponents, format, attributeData); + + VertexBuffer.Format originalFormat = getVertexBufferFormat(componentType); + VertexBuffer.Format resultFormat = originalFormat; + Buffer resultAttributeData = attributeData; + + boolean normalized = Boolean.TRUE.equals(getAsBoolean(accessor, "normalized")); + if (normalized) { + logger.log(level, + "Draco-decoded data is " + originalFormat + ", but normalized - dequantizing to Float"); + resultFormat = VertexBuffer.Format.Float; + if (originalFormat == VertexBuffer.Format.Byte) { + resultAttributeData = BufferQuantization.dequantizeByteBuffer(attributeData); + } else { + resultAttributeData = BufferQuantization.dequantizeUnsignedByteBuffer(attributeData); + } + } + + vb.setupData(VertexBuffer.Usage.Dynamic, numComponents, resultFormat, resultAttributeData); return vb; } /** * Create the vertex buffer for the given short attribute data * + * If the accessor is normalized, then this will dequantize the given data into a + * Float vertex buffer. + * * @param accessor * The accessor that describes the component type and type * @@ -567,9 +590,25 @@ private static VertexBuffer createShortAttributeVertexBuffer(JsonObject accessor VertexBuffer.Type bufferType, ShortBuffer attributeData) { int componentType = getAsInt(accessor, "accessor", "componentType"); VertexBuffer vb = new VertexBuffer(bufferType); - VertexBuffer.Format format = getVertexBufferFormat(componentType); int numComponents = getAccessorComponentCount(accessor); - vb.setupData(VertexBuffer.Usage.Dynamic, numComponents, format, attributeData); + + VertexBuffer.Format originalFormat = getVertexBufferFormat(componentType); + VertexBuffer.Format resultFormat = originalFormat; + Buffer resultAttributeData = attributeData; + + boolean normalized = Boolean.TRUE.equals(getAsBoolean(accessor, "normalized")); + if (normalized) { + logger.log(level, + "Draco-decoded data is " + originalFormat + ", but normalized - dequantizing to Float"); + resultFormat = VertexBuffer.Format.Float; + if (originalFormat == VertexBuffer.Format.Short) { + resultAttributeData = BufferQuantization.dequantizeShortBuffer(attributeData); + } else { + resultAttributeData = BufferQuantization.dequantizeUnsignedShortBuffer(attributeData); + } + } + + vb.setupData(VertexBuffer.Usage.Dynamic, numComponents, resultFormat, resultAttributeData); return vb; } From 933b429596c82549f2dd47b969e96c383857ffef Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Sat, 7 Feb 2026 16:09:57 +0100 Subject: [PATCH 12/14] Drako library update and skinning cleanups --- .../src/main/java/com/jme3/scene/Mesh.java | 3 + jme3-plugins/build.gradle | 2 +- .../DracoMeshCompressionExtensionLoader.java | 247 ++++++---- .../jme3/scene/plugins/gltf/GltfLoader.java | 443 ++++++++++-------- .../jme3/scene/plugins/gltf/GltfUtils.java | 52 +- 5 files changed, 450 insertions(+), 297 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/scene/Mesh.java b/jme3-core/src/main/java/com/jme3/scene/Mesh.java index 2819c2838f..e4ab6393b3 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Mesh.java +++ b/jme3-core/src/main/java/com/jme3/scene/Mesh.java @@ -366,6 +366,7 @@ public void generateBindPose() { pos.getNumComponents(), pos.getFormat(), BufferUtils.clone(pos.getData())); + clearBuffer(bindPos.getBufferType()); setBuffer(bindPos); // XXX: note that this method also sets stream mode @@ -379,6 +380,7 @@ public void generateBindPose() { norm.getNumComponents(), norm.getFormat(), BufferUtils.clone(norm.getData())); + clearBuffer(bindNorm.getBufferType()); setBuffer(bindNorm); norm.setUsage(Usage.Stream); } @@ -390,6 +392,7 @@ public void generateBindPose() { tangents.getNumComponents(), tangents.getFormat(), BufferUtils.clone(tangents.getData())); + clearBuffer(bindTangents.getBufferType()); setBuffer(bindTangents); tangents.setUsage(Usage.Stream); }// else hardware setup does nothing, mesh already in bind pose diff --git a/jme3-plugins/build.gradle b/jme3-plugins/build.gradle index bb2fa9fa4a..ce699730ac 100644 --- a/jme3-plugins/build.gradle +++ b/jme3-plugins/build.gradle @@ -17,7 +17,7 @@ repositories{ dependencies { api project(':jme3-core') - implementation "org.jmonkeyengine:drako:1.4.3" + implementation "com.openize:drako:1.4.4" implementation project(':jme3-plugins-json') implementation project(':jme3-plugins-json-gson') testRuntimeOnly project(':jme3-desktop') diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/DracoMeshCompressionExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/DracoMeshCompressionExtensionLoader.java index 2c978da2fe..4972bf8d05 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/DracoMeshCompressionExtensionLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/DracoMeshCompressionExtensionLoader.java @@ -55,12 +55,12 @@ import com.jme3.scene.Mesh; import com.jme3.scene.VertexBuffer; import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.plugins.gltf.GltfLoader.SkinBuffers; import com.jme3.util.BufferUtils; - -import dev.fileformat.drako.Draco; -import dev.fileformat.drako.DracoMesh; -import dev.fileformat.drako.DrakoException; -import dev.fileformat.drako.PointAttribute; +import com.openize.drako.Draco; +import com.openize.drako.DracoMesh; +import com.openize.drako.DrakoException; +import com.openize.drako.PointAttribute; /** * A class for handling the KHR_draco_mesh_compression extension when loading a glTF asset. @@ -157,16 +157,137 @@ public Object handleExtension(GltfLoader loader, String parentName, JsonElement // created from the data that was fetched from the // decoded draco PointAttribute Type bufferType = getVertexBufferType(attributeName); - VertexBuffer attributeVertexBuffer = createAttributeVertexBuffer(attributeName, accessor, - pointAttribute, indices); - mesh.clearBuffer(bufferType); - mesh.setBuffer(attributeVertexBuffer); + + if (attributeName.startsWith("JOINTS")) { + readJoints(loader, attributeName, accessor, pointAttribute); + } else if (attributeName.startsWith("WEIGHTS")) { + readWeights(loader, attributeName, accessor, pointAttribute); + } else { + VertexBuffer attributeVertexBuffer = createAttributeVertexBuffer(attributeName, accessor, + pointAttribute); + mesh.clearBuffer(bufferType); + mesh.setBuffer(attributeVertexBuffer); + } } + loader.postProcessSkinning(mesh); logger.log(level, "Decoding draco data DONE"); return mesh; } + /** + * Read the data from a JOINTS_n attribute that was decoded from Draco. + * + * This will read the data from the attribute, and store it as the {@link SkinBuffers#joints} array for in + * the skin buffers information that is obtained via {@link GltfLoader#getSkinBuffers(String)} for the + * given attribute name. + * + * @param loader + * The {@link GltfLoader} + * @param attributeName + * The attribute name + * @param accessor + * The accessor for the attribute + * @param pointAttribute + * The actual Draco-decoded attribute data + * @throws AssetLoadException + * If the component type of the given accessor is not GL_UNSIGNED_BYTE or + * GL_UNSIGNED_SHORT + */ + private static void readJoints(GltfLoader loader, String attributeName, JsonObject accessor, + PointAttribute pointAttribute) { + int count = getAsInt(accessor, "accessor", "count"); + int componentType = getAsInt(accessor, "accessor", "componentType"); + int componentCount = getAccessorComponentCount(accessor); + + if (componentType == GltfConstants.GL_UNSIGNED_BYTE) { + ByteBuffer attributeData = readByteDracoAttribute(pointAttribute, count, componentCount); + short array[] = new short[attributeData.capacity()]; + for (int i = 0; i < array.length; i++) { + array[i] = attributeData.get(i); + } + SkinBuffers buffs = loader.getSkinBuffers(attributeName); + buffs.componentSize = 2; + buffs.joints = array; + } else if (componentType == GltfConstants.GL_UNSIGNED_SHORT) { + ShortBuffer attributeData = readShortDracoAttribute(pointAttribute, count, componentCount); + short array[] = new short[attributeData.capacity()]; + attributeData.slice().get(array); + SkinBuffers buffs = loader.getSkinBuffers(attributeName); + buffs.componentSize = 2; + buffs.joints = array; + } else { + throw new AssetLoadException("The accessor for attribute " + attributeName + + " must have a component type of " + GltfConstants.GL_UNSIGNED_BYTE + " or " + + GltfConstants.GL_UNSIGNED_SHORT + ", but has " + componentType); + } + } + + /** + * Read the data from a WEIGHTS_n attribute that was decoded from Draco. + * + * This will read the data from the attribute, and store it as the {@link SkinBuffers#weights} array for + * in the skin buffers information that is obtained via {@link GltfLoader#getSkinBuffers(String)} for the + * given attribute name. + * + * @param loader + * The {@link GltfLoader} + * @param attributeName + * The attribute name + * @param accessor + * The accessor for the attribute + * @param pointAttribute + * The actual Draco-decoded attribute data + * @throws AssetLoadException + * If the component type of the given accessor is not GL_UNSIGNED_BYTE or + * GL_UNSIGNED_SHORT or GL_FLOAT, or if it is + * GL_UNSIGNED_BYTE or GL_UNSIGNED_SHORT and the accessor is not + * normalized. + */ + private static void readWeights(GltfLoader loader, String attributeName, JsonObject accessor, + PointAttribute pointAttribute) { + int count = getAsInt(accessor, "accessor", "count"); + int componentType = getAsInt(accessor, "accessor", "componentType"); + int componentCount = getAccessorComponentCount(accessor); + + if (componentType == GltfConstants.GL_UNSIGNED_BYTE) { + boolean normalized = Boolean.TRUE.equals(getAsBoolean(accessor, "normalized")); + if (!normalized) { + throw new AssetLoadException("The accessor for attribute " + attributeName + + " has a component type of " + componentType + " but is not normalized"); + } + ByteBuffer attributeData = readByteDracoAttribute(pointAttribute, count, componentCount); + FloatBuffer resultAttributeData = BufferQuantization.dequantizeByteBuffer(attributeData); + float array[] = new float[attributeData.capacity()]; + resultAttributeData.slice().get(array); + SkinBuffers buffs = loader.getSkinBuffers(attributeName); + buffs.weights = array; + } else if (componentType == GltfConstants.GL_UNSIGNED_SHORT) { + boolean normalized = Boolean.TRUE.equals(getAsBoolean(accessor, "normalized")); + if (!normalized) { + throw new AssetLoadException("The accessor for attribute " + attributeName + + " has a component type of " + componentType + " but is not normalized"); + } + ShortBuffer attributeData = readShortDracoAttribute(pointAttribute, count, componentCount); + FloatBuffer resultAttributeData = BufferQuantization.dequantizeShortBuffer(attributeData); + float array[] = new float[attributeData.capacity()]; + resultAttributeData.slice().get(array); + SkinBuffers buffs = loader.getSkinBuffers(attributeName); + buffs.weights = array; + } else if (componentType == GltfConstants.GL_FLOAT) { + FloatBuffer attributeData = readFloatDracoAttribute(pointAttribute, count, componentCount); + float array[] = new float[attributeData.capacity()]; + attributeData.slice().get(array); + SkinBuffers buffs = loader.getSkinBuffers(attributeName); + buffs.weights = array; + } else { + throw new AssetLoadException( + "The accessor for attribute " + attributeName + " must have a component type of " + + GltfConstants.GL_UNSIGNED_BYTE + ", " + GltfConstants.GL_UNSIGNED_SHORT + + ", or " + GltfConstants.GL_FLOAT + ", but has " + componentType); + } + } + /** * Read the draco data from the given extension, using openize-drako-java. * @@ -352,35 +473,31 @@ private static PointAttribute getAttribute(DracoMesh dracoMesh, String gltfAttri * The accessor JSON object * @param pointAttribute * The Draco-decoded point attribute - * @param indices - * The indices, obtained from the draco mesh * @return The vertex buffer * @throws AssetLoadException * If the given accessor does not have a component type that is valid for a vertex attribute */ private static VertexBuffer createAttributeVertexBuffer(String attributeName, JsonObject accessor, - PointAttribute pointAttribute, int indices[]) { + PointAttribute pointAttribute) { int count = getAsInt(accessor, "accessor", "count"); int componentType = getAsInt(accessor, "accessor", "componentType"); int componentCount = getAccessorComponentCount(accessor); Type bufferType = getVertexBufferType(attributeName); if (componentType == GltfConstants.GL_BYTE || componentType == GltfConstants.GL_UNSIGNED_BYTE) { - ByteBuffer attributeData = readByteDracoAttribute(pointAttribute, indices, count, componentCount); + ByteBuffer attributeData = readByteDracoAttribute(pointAttribute, count, componentCount); VertexBuffer attributeVertexBuffer = createByteAttributeVertexBuffer(accessor, bufferType, attributeData); return attributeVertexBuffer; } if (componentType == GltfConstants.GL_SHORT || componentType == GltfConstants.GL_UNSIGNED_SHORT) { - ShortBuffer attributeData = readShortDracoAttribute(pointAttribute, indices, count, - componentCount); + ShortBuffer attributeData = readShortDracoAttribute(pointAttribute, count, componentCount); VertexBuffer attributeVertexBuffer = createShortAttributeVertexBuffer(accessor, bufferType, attributeData); return attributeVertexBuffer; } if (componentType == GltfConstants.GL_FLOAT) { - FloatBuffer attributeData = readFloatDracoAttribute(pointAttribute, indices, count, - componentCount); + FloatBuffer attributeData = readFloatDracoAttribute(pointAttribute, count, componentCount); VertexBuffer attributeVertexBuffer = createFloatAttributeVertexBuffer(accessor, bufferType, attributeData); return attributeVertexBuffer; @@ -397,42 +514,23 @@ private static VertexBuffer createAttributeVertexBuffer(String attributeName, Js * * @param pointAttribute * The Draco-decoded point attribute - * @param indices - * The indices, obtained from the draco mesh * @param count * The count, obtained from the accessor for this attribute * @param componentCount * The component count (number of components per element), obtained from the accessor type * @return The resulting data, as a byte buffer */ - private static ByteBuffer readByteDracoAttribute(PointAttribute pointAttribute, int indices[], int count, + private static ByteBuffer readByteDracoAttribute(PointAttribute pointAttribute, int count, int componentCount) { - int numFaces = indices.length / 3; + byte p[] = new byte[componentCount]; ByteBuffer attributeData = BufferUtils.createByteBuffer(count * componentCount); - for (int i = 0; i < numFaces; i++) { - int j0 = indices[i * 3 + 0]; - int j1 = indices[i * 3 + 1]; - int j2 = indices[i * 3 + 2]; - - int mj0 = pointAttribute.mappedIndex(j0); - int mj1 = pointAttribute.mappedIndex(j1); - int mj2 = pointAttribute.mappedIndex(j2); - pointAttribute.getValue(mj0, p); - int offset0 = j0 * componentCount; - for (int c = 0; c < componentCount; c++) { - attributeData.put(offset0 + c, p[c]); - } - pointAttribute.getValue(mj1, p); - int offset1 = j1 * componentCount; - for (int c = 0; c < componentCount; c++) { - attributeData.put(offset1 + c, p[c]); - } - pointAttribute.getValue(mj2, p); - int offset2 = j2 * componentCount; + for (int i = 0; i < count; i++) { + int j = pointAttribute.mappedIndex(i); + pointAttribute.getValue(j, p); for (int c = 0; c < componentCount; c++) { - attributeData.put(offset2 + c, p[c]); + attributeData.put(i * componentCount + c, p[c]); } } return attributeData; @@ -443,42 +541,23 @@ private static ByteBuffer readByteDracoAttribute(PointAttribute pointAttribute, * * @param pointAttribute * The Draco-decoded point attribute - * @param indices - * The indices, obtained from the draco mesh * @param count * The count, obtained from the accessor for this attribute * @param componentCount * The component count (number of components per element), obtained from the accessor type * @return The resulting data, as a short buffer */ - private static ShortBuffer readShortDracoAttribute(PointAttribute pointAttribute, int indices[], - int count, int componentCount) { - int numFaces = indices.length / 3; + private static ShortBuffer readShortDracoAttribute(PointAttribute pointAttribute, int count, + int componentCount) { + short p[] = new short[componentCount]; ShortBuffer attributeData = BufferUtils.createShortBuffer(count * componentCount); - for (int i = 0; i < numFaces; i++) { - int j0 = indices[i * 3 + 0]; - int j1 = indices[i * 3 + 1]; - int j2 = indices[i * 3 + 2]; - - int mj0 = pointAttribute.mappedIndex(j0); - int mj1 = pointAttribute.mappedIndex(j1); - int mj2 = pointAttribute.mappedIndex(j2); - pointAttribute.getValue(mj0, p); - int offset0 = j0 * componentCount; - for (int c = 0; c < componentCount; c++) { - attributeData.put(offset0 + c, p[c]); - } - pointAttribute.getValue(mj1, p); - int offset1 = j1 * componentCount; - for (int c = 0; c < componentCount; c++) { - attributeData.put(offset1 + c, p[c]); - } - pointAttribute.getValue(mj2, p); - int offset2 = j2 * componentCount; + for (int i = 0; i < count; i++) { + int j = pointAttribute.mappedIndex(i); + pointAttribute.getValue(j, p); for (int c = 0; c < componentCount; c++) { - attributeData.put(offset2 + c, p[c]); + attributeData.put(i * componentCount + c, p[c]); } } return attributeData; @@ -489,43 +568,23 @@ private static ShortBuffer readShortDracoAttribute(PointAttribute pointAttribute * * @param pointAttribute * The Draco-decoded point attribute - * @param indices - * The indices, obtained from the draco mesh * @param count * The count, obtained from the accessor for this attribute * @param componentCount * The component count (number of components per element), obtained from the accessor type * @return The resulting data, as a float buffer */ - private static FloatBuffer readFloatDracoAttribute(PointAttribute pointAttribute, int indices[], - int count, int componentCount) { - int numFaces = indices.length / 3; + private static FloatBuffer readFloatDracoAttribute(PointAttribute pointAttribute, int count, + int componentCount) { float p[] = new float[componentCount]; FloatBuffer attributeData = BufferUtils.createFloatBuffer(count * componentCount); - for (int i = 0; i < numFaces; i++) { - int j0 = indices[i * 3 + 0]; - int j1 = indices[i * 3 + 1]; - int j2 = indices[i * 3 + 2]; - - int mj0 = pointAttribute.mappedIndex(j0); - int mj1 = pointAttribute.mappedIndex(j1); - int mj2 = pointAttribute.mappedIndex(j2); - - pointAttribute.getValue(mj0, p); - int offset0 = j0 * componentCount; + for (int i = 0; i < count; i++) { + int j = pointAttribute.mappedIndex(i); + pointAttribute.getValue(j, p); + int offset0 = i * componentCount; for (int c = 0; c < componentCount; c++) { attributeData.put(offset0 + c, p[c]); } - pointAttribute.getValue(mj1, p); - int offset1 = j1 * componentCount; - for (int c = 0; c < componentCount; c++) { - attributeData.put(offset1 + c, p[c]); - } - pointAttribute.getValue(mj2, p); - int offset2 = j2 * componentCount; - for (int c = 0; c < componentCount; c++) { - attributeData.put(offset2 + c, p[c]); - } } return attributeData; } diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java index 08638010d4..52c2074d6d 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java @@ -31,39 +31,87 @@ */ package com.jme3.scene.plugins.gltf; +import static com.jme3.scene.plugins.gltf.GltfUtils.assertNotNull; +import static com.jme3.scene.plugins.gltf.GltfUtils.findCommonAncestor; +import static com.jme3.scene.plugins.gltf.GltfUtils.getAdapterForMaterial; +import static com.jme3.scene.plugins.gltf.GltfUtils.getAsBoolean; +import static com.jme3.scene.plugins.gltf.GltfUtils.getAsColor; +import static com.jme3.scene.plugins.gltf.GltfUtils.getAsFloat; +import static com.jme3.scene.plugins.gltf.GltfUtils.getAsInteger; +import static com.jme3.scene.plugins.gltf.GltfUtils.getAsString; +import static com.jme3.scene.plugins.gltf.GltfUtils.getIndex; +import static com.jme3.scene.plugins.gltf.GltfUtils.getMagFilter; +import static com.jme3.scene.plugins.gltf.GltfUtils.getMeshMode; +import static com.jme3.scene.plugins.gltf.GltfUtils.getMinFilter; +import static com.jme3.scene.plugins.gltf.GltfUtils.getNumberOfComponents; +import static com.jme3.scene.plugins.gltf.GltfUtils.getVertexBufferFormat; +import static com.jme3.scene.plugins.gltf.GltfUtils.getVertexBufferType; +import static com.jme3.scene.plugins.gltf.GltfUtils.getWrapMode; +import static com.jme3.scene.plugins.gltf.GltfUtils.padBuffer; +import static com.jme3.scene.plugins.gltf.GltfUtils.parse; +import static com.jme3.scene.plugins.gltf.GltfUtils.populateBuffer; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.Base64; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.jme3.anim.AnimClip; +import com.jme3.anim.AnimComposer; +import com.jme3.anim.AnimTrack; +import com.jme3.anim.Armature; +import com.jme3.anim.Joint; +import com.jme3.anim.MorphControl; +import com.jme3.anim.MorphTrack; +import com.jme3.anim.SkinningControl; +import com.jme3.anim.TransformTrack; +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetLoadException; +import com.jme3.asset.AssetLoader; +import com.jme3.asset.TextureKey; +import com.jme3.material.Material; +import com.jme3.material.RenderState; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix4f; +import com.jme3.math.Quaternion; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; import com.jme3.plugins.json.JsonArray; +import com.jme3.plugins.json.JsonElement; import com.jme3.plugins.json.JsonObject; import com.jme3.plugins.json.JsonPrimitive; -import com.jme3.plugins.json.JsonElement; -import com.jme3.anim.*; -import com.jme3.asset.*; -import com.jme3.material.Material; -import com.jme3.material.RenderState; -import com.jme3.math.*; import com.jme3.renderer.Camera; import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.*; +import com.jme3.scene.CameraNode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.VertexBuffer; import com.jme3.scene.control.CameraControl; import com.jme3.scene.mesh.MorphTarget; -import static com.jme3.scene.plugins.gltf.GltfUtils.*; import com.jme3.texture.Texture; import com.jme3.texture.Texture2D; import com.jme3.util.BufferInputStream; import com.jme3.util.BufferUtils; import com.jme3.util.IntMap; import com.jme3.util.mikktspace.MikktspaceTangentGenerator; -import java.io.*; -import java.net.URLDecoder; -import java.nio.Buffer; -import java.nio.ByteBuffer; -import java.nio.FloatBuffer; -import java.util.*; -import java.util.logging.Level; -import java.util.logging.Logger; /** - * GLTF 2.0 loader - * Created by Nehon on 07/08/2017. + * GLTF 2.0 loader Created by Nehon on 07/08/2017. */ public class GltfLoader implements AssetLoader { @@ -99,7 +147,7 @@ public class GltfLoader implements AssetLoader { private boolean useNormalsFlag = false; Map> skinnedSpatials = new HashMap<>(); - IntMap skinBuffers = new IntMap<>(); + private final IntMap skinBuffers = new IntMap<>(); public GltfLoader() { defaultMaterialAdapters.put("pbrMetallicRoughness", new PBRMetalRoughMaterialAdapter()); @@ -130,7 +178,8 @@ protected Object loadFromStream(AssetInfo assetInfo, InputStream stream) throws String version = getAsString(asset, "version"); String minVersion = getAsString(asset, "minVersion"); if (!isSupported(version, minVersion)) { - logger.log(Level.SEVERE, "Gltf Loader doesn''t support this gltf version: {0}{1}", new Object[]{version, minVersion != null ? ("/" + minVersion) : ""}); + logger.log(Level.SEVERE, "Gltf Loader doesn''t support this gltf version: {0}{1}", + new Object[] { version, minVersion != null ? ("/" + minVersion) : "" }); } scenes = docRoot.getAsJsonArray("scenes"); @@ -280,9 +329,9 @@ public Object readNode(int nodeIndex) throws IOException { } node.setName(readMeshName(meshIndex)); - + spatial = new Node(); - ((Node)spatial).attachChild(node); + ((Node) spatial).attachChild(node); } else { // no mesh, we have a node. Can be a camera node or a regular node. @@ -361,24 +410,17 @@ public Transform readTransforms(JsonObject nodeData) { // no matrix transforms: no transforms or transforms given as translation/rotation/scale JsonArray translation = nodeData.getAsJsonArray("translation"); if (translation != null) { - transform.setTranslation( - translation.get(0).getAsFloat(), - translation.get(1).getAsFloat(), + transform.setTranslation(translation.get(0).getAsFloat(), translation.get(1).getAsFloat(), translation.get(2).getAsFloat()); } JsonArray rotation = nodeData.getAsJsonArray("rotation"); if (rotation != null) { - transform.setRotation(new Quaternion( - rotation.get(0).getAsFloat(), - rotation.get(1).getAsFloat(), - rotation.get(2).getAsFloat(), - rotation.get(3).getAsFloat())); + transform.setRotation(new Quaternion(rotation.get(0).getAsFloat(), rotation.get(1).getAsFloat(), + rotation.get(2).getAsFloat(), rotation.get(3).getAsFloat())); } JsonArray scale = nodeData.getAsJsonArray("scale"); if (scale != null) { - transform.setScale( - scale.get(0).getAsFloat(), - scale.get(1).getAsFloat(), + transform.setScale(scale.get(0).getAsFloat(), scale.get(1).getAsFloat(), scale.get(2).getAsFloat()); } @@ -387,7 +429,7 @@ public Transform readTransforms(JsonObject nodeData) { public Geometry[] readMeshPrimitives(int meshIndex) throws IOException { Geometry[] geomArray = (Geometry[]) fetchFromCache("meshes", meshIndex, Object.class); - if (geomArray == null) { + if (geomArray == null) { JsonObject meshData = meshes.get(meshIndex).getAsJsonObject(); JsonArray primitives = meshData.getAsJsonArray("primitives"); assertNotNull(primitives, "Can't find any primitives in mesh " + meshIndex); @@ -403,7 +445,8 @@ public Geometry[] readMeshPrimitives(int meshIndex) throws IOException { mesh.setMode(getMeshMode(mode)); Integer indices = getAsInteger(meshObject, "indices"); if (indices != null) { - mesh.setBuffer(readAccessorData(indices, new VertexBufferPopulator(VertexBuffer.Type.Index))); + mesh.setBuffer( + readAccessorData(indices, new VertexBufferPopulator(VertexBuffer.Type.Index))); } JsonObject attributes = meshObject.getAsJsonObject("attributes"); assertNotNull(attributes, "No attributes defined for mesh " + mesh); @@ -415,17 +458,19 @@ public Geometry[] readMeshPrimitives(int meshIndex) throws IOException { for (Map.Entry entry : attributes.entrySet()) { // special case for joints and weights buffer. // If there are more than 4 bones per vertex, there might be several of them - // we need to read them all and to keep only the 4 that have the most weight on the vertex. + // we need to read them all and to keep only the 4 that have the most weight on the + // vertex. String bufferType = entry.getKey(); if (bufferType.startsWith("JOINTS")) { SkinBuffers buffs = getSkinBuffers(bufferType); - SkinBuffers buffer - = readAccessorData(entry.getValue().getAsInt(), new JointArrayPopulator()); + SkinBuffers buffer = readAccessorData(entry.getValue().getAsInt(), + new JointArrayPopulator()); buffs.joints = buffer.joints; buffs.componentSize = buffer.componentSize; } else if (bufferType.startsWith("WEIGHTS")) { SkinBuffers buffs = getSkinBuffers(bufferType); - buffs.weights = readAccessorData(entry.getValue().getAsInt(), new FloatArrayPopulator()); + buffs.weights = readAccessorData(entry.getValue().getAsInt(), + new FloatArrayPopulator()); } else { VertexBuffer vb = readAccessorData(entry.getValue().getAsInt(), new VertexBufferPopulator(getVertexBufferType(bufferType))); @@ -438,26 +483,13 @@ public Geometry[] readMeshPrimitives(int meshIndex) throws IOException { useVertexColors = true; } } - handleSkinningBuffers(mesh, skinBuffers); - - if (mesh.getBuffer(VertexBuffer.Type.BoneIndex) != null) { - // the mesh has some skinning, let's create needed buffers for HW skinning - // creating empty buffers for HW skinning - // the buffers will be set up if ever used. - VertexBuffer weightsHW = new VertexBuffer(VertexBuffer.Type.HWBoneWeight); - VertexBuffer indicesHW = new VertexBuffer(VertexBuffer.Type.HWBoneIndex); - // setting usage to cpuOnly so that the buffer is not sent empty to the GPU - indicesHW.setUsage(VertexBuffer.Usage.CpuOnly); - weightsHW.setUsage(VertexBuffer.Usage.CpuOnly); - mesh.setBuffer(weightsHW); - mesh.setBuffer(indicesHW); - mesh.generateBindPose(); - } + postProcessSkinning(mesh); // Read morph target names LinkedList targetNames = new LinkedList<>(); if (meshData.has("extras") && meshData.getAsJsonObject("extras").has("targetNames")) { - JsonArray targetNamesJson = meshData.getAsJsonObject("extras").getAsJsonArray("targetNames"); + JsonArray targetNamesJson = meshData.getAsJsonObject("extras") + .getAsJsonArray("targetNames"); for (JsonElement target : targetNamesJson) { targetNames.add(target.getAsString()); } @@ -494,13 +526,15 @@ public Geometry[] readMeshPrimitives(int meshIndex) throws IOException { } else { useNormalsFlag = false; geom.setMaterial(readMaterial(materialIndex)); - if (geom.getMaterial().getAdditionalRenderState().getBlendMode() - == RenderState.BlendMode.Alpha) { - // Alpha blending is enabled for this material. Let's place the geom in the transparent bucket. + if (geom.getMaterial().getAdditionalRenderState() + .getBlendMode() == RenderState.BlendMode.Alpha) { + // Alpha blending is enabled for this material. Let's place the geom in the + // transparent bucket. geom.setQueueBucket(RenderQueue.Bucket.Transparent); } if (useNormalsFlag && mesh.getBuffer(VertexBuffer.Type.Tangent) == null) { - // No tangent buffer, but there is a normal map, we have to generate them using MikktSpace + // No tangent buffer, but there is a normal map, we have to generate them using + // MikktSpace MikktspaceTangentGenerator.generate(geom); } } @@ -510,7 +544,7 @@ public Geometry[] readMeshPrimitives(int meshIndex) throws IOException { } geom.setName(name + "_" + index); - + geom.updateModelBound(); geomArray[index] = geom; index++; @@ -528,7 +562,7 @@ public Geometry[] readMeshPrimitives(int meshIndex) throws IOException { return geoms; } - private SkinBuffers getSkinBuffers(String bufferType) { + SkinBuffers getSkinBuffers(String bufferType) { int bufIndex = getIndex(bufferType); SkinBuffers buffs = skinBuffers.get(bufIndex); if (buffs == null) { @@ -538,6 +572,36 @@ private SkinBuffers getSkinBuffers(String bufferType) { return buffs; } + /** + * Perform the post-processing on the given mesh that is required for the skinning to work properly, after + * the mesh information was read from the glTF input. + * + * Many details are unspecified here. But this is what had originally been done after reading the skinning + * information for a mesh primitive. Now it is also called after any Draco-encoded data was decoded (which + * may have updated the skinning data with the Draco-decoded data). + * + * @param mesh + * The mesh + */ + void postProcessSkinning(Mesh mesh) { + GltfUtils.handleSkinningBuffers(mesh, skinBuffers); + if (mesh.getBuffer(VertexBuffer.Type.BoneIndex) != null) { + // the mesh has some skinning, let's create needed buffers for HW skinning + // creating empty buffers for HW skinning + // the buffers will be set up if ever used. + VertexBuffer weightsHW = new VertexBuffer(VertexBuffer.Type.HWBoneWeight); + VertexBuffer indicesHW = new VertexBuffer(VertexBuffer.Type.HWBoneIndex); + // setting usage to cpuOnly so that the buffer is not sent empty to the GPU + indicesHW.setUsage(VertexBuffer.Usage.CpuOnly); + weightsHW.setUsage(VertexBuffer.Usage.CpuOnly); + mesh.clearBuffer(weightsHW.getBufferType()); + mesh.setBuffer(weightsHW); + mesh.clearBuffer(indicesHW.getBufferType()); + mesh.setBuffer(indicesHW); + mesh.generateBindPose(); + } + } + private R readAccessorData(int accessorIndex, Populator populator) throws IOException { assertNotNull(accessors, "No accessor attribute in the gltf file"); @@ -579,7 +643,7 @@ public Object readBuffer(Integer bufferViewIndex, int byteOffset, int count, Obj ByteBuffer data = readData(bufferIndex); data = customContentManager.readExtensionAndExtras("bufferView", bufferView, data); - if(!(data instanceof ByteBuffer)){ + if (!(data instanceof ByteBuffer)) { throw new IOException("Buffer data is not a NIO Buffer"); } @@ -596,8 +660,8 @@ public Object readBuffer(Integer bufferViewIndex, int byteOffset, int count, Obj return store; } - public Buffer viewBuffer(Integer bufferViewIndex, int byteOffset, int count, - int numComponents, VertexBuffer.Format originalFormat, VertexBuffer.Format targetFormat) throws IOException { + public Buffer viewBuffer(Integer bufferViewIndex, int byteOffset, int count, int numComponents, + VertexBuffer.Format originalFormat, VertexBuffer.Format targetFormat) throws IOException { JsonObject bufferView = bufferViews.get(bufferViewIndex).getAsJsonObject(); Integer bufferIndex = getAsInteger(bufferView, "buffer"); assertNotNull(bufferIndex, "No buffer defined for bufferView " + bufferViewIndex); @@ -609,72 +673,75 @@ public Buffer viewBuffer(Integer bufferViewIndex, int byteOffset, int count, ByteBuffer data = readData(bufferIndex); data = customContentManager.readExtensionAndExtras("bufferView", bufferView, data); - if(!(data instanceof ByteBuffer)){ + if (!(data instanceof ByteBuffer)) { throw new IOException("Buffer data is not a NIO Buffer"); } - if (count == -1) { count = byteLength; } - return GltfUtils.getBufferView(data, byteOffset + bvByteOffset, count, byteStride, numComponents, originalFormat, targetFormat ); + return GltfUtils.getBufferView(data, byteOffset + bvByteOffset, count, byteStride, numComponents, + originalFormat, targetFormat); + + } + + /** + * Returns the JSON object that represents the buffer view with the specified index in the glTF JSON. + * + * @param index + * The buffer view index + * @return The buffer view as a JSON object + * @throws AssetLoadException + * If the index is negative or not smaller than the number of buffer views in the glTF JSON + */ + JsonObject getBufferView(int index) { + assertNotNull(bufferViews, "No buffer views when trying to access buffer view with index " + index); + validateIndex("bufferView", index, bufferViews.size()); + JsonObject bufferView = bufferViews.get(index).getAsJsonObject(); + return bufferView; + } + /** + * Returns the JSON object that represents the accessor with the specified index in the glTF JSON. + * + * @param index + * The accessor index + * @return The accessor as a JSON object + * @throws AssetLoadException + * If the index is negative or not smaller than the number of accessors in the glTF JSON + */ + JsonObject getAccessor(int index) { + assertNotNull(accessors, "No accessors when trying to access accessor with index " + index); + validateIndex("accessor", index, accessors.size()); + JsonObject accessor = accessors.get(index).getAsJsonObject(); + return accessor; } - /** - * Returns the JSON object that represents the buffer view with the specified - * index in the glTF JSON. - * - * @param index The buffer view index - * @return The buffer view as a JSON object - * @throws AssetLoadException If the index is negative or not smaller than the - * number of buffer views in the glTF JSON - */ - JsonObject getBufferView(int index) { - assertNotNull(bufferViews, "No buffer views when trying to access buffer view with index " + index); - validateIndex("bufferView", index, bufferViews.size()); - JsonObject bufferView = bufferViews.get(index).getAsJsonObject(); - return bufferView; - } - - /** - * Returns the JSON object that represents the accessor with the specified index - * in the glTF JSON. - * - * @param index The accessor index - * @return The accessor as a JSON object - * @throws AssetLoadException If the index is negative or not smaller than the - * number of accessors in the glTF JSON - */ - JsonObject getAccessor(int index) { - assertNotNull(accessors, "No accessors when trying to access accessor with index " + index); - validateIndex("accessor", index, accessors.size()); - JsonObject accessor = accessors.get(index).getAsJsonObject(); - return accessor; - } - - /** - * Ensure that the given index is valid for the specified size, and throw an - * exception of this is not the case. - * - * @param name The name of the index - * @param index The index - * @param size The size - * @throws AssetLoadException If the index is negative or not smaller than the - * size - */ - private static void validateIndex(String name, int index, int size) { - if (index < 0 || index >= size) { - throw new AssetLoadException( - "The " + name + " index must be positive and smaller than " + size + ", but is " + index); - } - } + /** + * Ensure that the given index is valid for the specified size, and throw an exception of this is not the + * case. + * + * @param name + * The name of the index + * @param index + * The index + * @param size + * The size + * @throws AssetLoadException + * If the index is negative or not smaller than the size + */ + private static void validateIndex(String name, int index, int size) { + if (index < 0 || index >= size) { + throw new AssetLoadException( + "The " + name + " index must be positive and smaller than " + size + ", but is " + index); + } + } public ByteBuffer readData(int bufferIndex) throws IOException { assertNotNull(buffers, "No buffer defined"); validateIndex("buffer", bufferIndex, buffers.size()); - + JsonObject buffer = buffers.get(bufferIndex).getAsJsonObject(); String uri = getAsString(buffer, "uri"); Integer bufferLength = getAsInteger(buffer, "byteLength"); @@ -696,7 +763,8 @@ protected ByteBuffer getBytes(int bufferIndex, String uri, Integer bufferLength) if (uri != null) { if (uri.startsWith("data:")) { // base 64 embed data - data = BufferUtils.createByteBuffer(Base64.getDecoder().decode(uri.substring(uri.indexOf(",") + 1))); + data = BufferUtils + .createByteBuffer(Base64.getDecoder().decode(uri.substring(uri.indexOf(",") + 1))); } else { // external file let's load it String decoded = decodeUri(uri); @@ -706,11 +774,11 @@ protected ByteBuffer getBytes(int bufferIndex, String uri, Integer bufferLength) } BinDataKey key = new BinDataKey(info.getKey().getFolder() + decoded); - try(InputStream input = (InputStream) info.getManager().loadAsset(key)){ + try (InputStream input = (InputStream) info.getManager().loadAsset(key)) { data = BufferUtils.createByteBuffer(bufferLength); GltfUtils.readToByteBuffer(input, data, bufferLength); } - + } } else { // no URI, this should not happen in a gltf file, only in glb files. @@ -754,7 +822,8 @@ public Material readMaterial(int materialIndex) throws IOException { adapter.setParam("metallicRoughnessTexture", readTexture(pbrMat.getAsJsonObject("metallicRoughnessTexture"))); JsonObject metallicRoughnessJson = pbrMat.getAsJsonObject("metallicRoughnessTexture"); - metallicRoughnessIndex = metallicRoughnessJson != null ? getAsInteger(metallicRoughnessJson, "index") : null; + metallicRoughnessIndex = metallicRoughnessJson != null ? getAsInteger(metallicRoughnessJson, + "index") : null; } adapter.getMaterial().setName(getAsString(matData, "name")); @@ -780,7 +849,7 @@ public Material readMaterial(int materialIndex) throws IOException { Integer occlusionIndex = occlusionJson != null ? getAsInteger(occlusionJson, "index") : null; if (occlusionIndex != null && occlusionIndex.equals(metallicRoughnessIndex)) { adapter.getMaterial().setBoolean("AoPackedInMRMap", true); - } else { + } else { adapter.setParam("occlusionTexture", readTexture(matData.getAsJsonObject("occlusionTexture"))); } @@ -854,7 +923,7 @@ public Texture2D readTexture(JsonObject texture, boolean flip) throws IOExceptio if (texture2d != null) { return texture2d; } - + JsonObject textureData = textures.get(textureIndex).getAsJsonObject(); Integer sourceIndex = getAsInteger(textureData, "source"); Integer samplerIndex = getAsInteger(textureData, "sampler"); @@ -887,11 +956,12 @@ public Texture2D readImage(int sourceIndex, boolean flip) throws IOException { if (uri == null) { assertNotNull(bufferView, "Image " + sourceIndex + " should either have an uri or a bufferView"); assertNotNull(mimeType, "Image " + sourceIndex + " should have a mimeType"); - ByteBuffer data = (ByteBuffer) viewBuffer(bufferView, 0, -1, 1, VertexBuffer.Format.Byte, VertexBuffer.Format.Byte); + ByteBuffer data = (ByteBuffer) viewBuffer(bufferView, 0, -1, 1, VertexBuffer.Format.Byte, + VertexBuffer.Format.Byte); String extension = mimeType.split("/")[1]; TextureKey key = new TextureKey("image" + sourceIndex + "." + extension, flip); - try(BufferedInputStream bis = new BufferedInputStream(new BufferInputStream(data))){ + try (BufferedInputStream bis = new BufferedInputStream(new BufferInputStream(data))) { result = (Texture2D) info.getManager().loadAssetFromStream(key, bis); } } else if (uri.startsWith("data:")) { @@ -901,7 +971,7 @@ public Texture2D readImage(int sourceIndex, boolean flip) throws IOException { String headerInfo = uriInfo[0].split(";")[0]; String extension = headerInfo.split("/")[1]; TextureKey key = new TextureKey("image" + sourceIndex + "." + extension, flip); - try(BufferedInputStream bis = new BufferedInputStream(new BufferInputStream(data))){ + try (BufferedInputStream bis = new BufferedInputStream(new BufferInputStream(data))) { result = (Texture2D) info.getManager().loadAssetFromStream(key, bis); } } else { @@ -921,7 +991,7 @@ public void readAnimation(int animationIndex) throws IOException { String name = getAsString(animation, "name"); assertNotNull(channels, "No channels for animation " + name); assertNotNull(samplers, "No samplers for animation " + name); - + // temp data storage of track data TrackData[] tracks = new TrackData[nodes.size()]; boolean hasMorphTrack = false; @@ -936,13 +1006,13 @@ public void readAnimation(int animationIndex) throws IOException { continue; } assertNotNull(targetPath, "No target path for channel"); -// -// if (targetPath.equals("weights")) { -// // Morph animation, not implemented in JME, let's warn the user and skip the channel -// logger.log(Level.WARNING, -// "Morph animation is not supported by JME yet, skipping animation track"); -// continue; -// } + // + // if (targetPath.equals("weights")) { + // // Morph animation, not implemented in JME, let's warn the user and skip the channel + // logger.log(Level.WARNING, + // "Morph animation is not supported by JME yet, skipping animation track"); + // continue; + // } TrackData trackData = tracks[targetNode]; if (trackData == null) { @@ -1017,8 +1087,8 @@ public void readAnimation(int animationIndex) throws IOException { spatials.add(s); if (trackData.rotations != null || trackData.translations != null || trackData.scales != null) { - TransformTrack track = new TransformTrack(s, trackData.times, - trackData.translations, trackData.rotations, trackData.scales); + TransformTrack track = new TransformTrack(s, trackData.times, trackData.translations, + trackData.rotations, trackData.scales); aTracks.add(track); } if (trackData.weights != null) { @@ -1043,15 +1113,14 @@ public void readAnimation(int animationIndex) throws IOException { // the track will be skipped. if (skinIndex != jw.skinIndex) { logger.log(Level.WARNING, "Animation " + animationIndex + " (" + name - + ") applies to joints that are not from the same skin: skin " - + skinIndex + ", joint " + jw.joint.getName() - + " from skin " + jw.skinIndex); + + ") applies to joints that are not from the same skin: skin " + skinIndex + + ", joint " + jw.joint.getName() + " from skin " + jw.skinIndex); continue; } } - TransformTrack track = new TransformTrack(jw.joint, trackData.times, - trackData.translations, trackData.rotations, trackData.scales); + TransformTrack track = new TransformTrack(jw.joint, trackData.times, trackData.translations, + trackData.rotations, trackData.scales); aTracks.add(track); } } @@ -1065,17 +1134,17 @@ public void readAnimation(int animationIndex) throws IOException { for (Joint joint : skin.joints) { if (!usedJoints.contains(joint)) { // create a track - float[] times = new float[]{0}; + float[] times = new float[] { 0 }; - Vector3f[] translations = new Vector3f[]{joint.getLocalTranslation()}; - Quaternion[] rotations = new Quaternion[]{joint.getLocalRotation()}; - Vector3f[] scales = new Vector3f[]{joint.getLocalScale()}; + Vector3f[] translations = new Vector3f[] { joint.getLocalTranslation() }; + Quaternion[] rotations = new Quaternion[] { joint.getLocalRotation() }; + Vector3f[] scales = new Vector3f[] { joint.getLocalScale() }; TransformTrack track = new TransformTrack(joint, times, translations, rotations, scales); aTracks.add(track); } } } - + anim.setTracks(aTracks.toArray(new AnimTrack[aTracks.size()])); anim = customContentManager.readExtensionAndExtras("animations", animation, anim); @@ -1232,8 +1301,7 @@ public Joint readNodeAsBone(int nodeIndex, int jointIndex, int skinIndex, Matrix private void findChildren(int nodeIndex) throws IOException { JointWrapper jw = fetchFromCache("nodes", nodeIndex, JointWrapper.class); if (jw == null) { - logger.log(Level.WARNING, - "No JointWrapper found for nodeIndex={0}.", nodeIndex); + logger.log(Level.WARNING, "No JointWrapper found for nodeIndex={0}.", nodeIndex); return; } @@ -1277,12 +1345,12 @@ private void setupControls() { if (spatials.size() >= 1) { spatial = findCommonAncestor(spatials); } -// if (spatial != skinData.parent) { -// skinData.rootBoneTransformOffset = spatial.getWorldTransform().invert(); -// if (skinData.parent != null) { -// skinData.rootBoneTransformOffset.combineWithParent(skinData.parent.getWorldTransform()); -// } -// } + // if (spatial != skinData.parent) { + // skinData.rootBoneTransformOffset = spatial.getWorldTransform().invert(); + // if (skinData.parent != null) { + // skinData.rootBoneTransformOffset.combineWithParent(skinData.parent.getWorldTransform()); + // } + // } if (skinData.animComposer != null && skinData.animComposer.getSpatial() == null) { spatial.addControl(skinData.animComposer); } @@ -1314,7 +1382,7 @@ private String readMeshName(int meshIndex) { JsonObject meshData = meshes.get(meshIndex).getAsJsonObject(); return getAsString(meshData, "name"); } - + private MorphTrack toMorphTrack(TrackData data, Spatial spatial) { Geometry g = (Geometry) spatial; int nbMorph = g.getMesh().getMorphTargets().length; @@ -1401,17 +1469,18 @@ private class SkinData { boolean used = false; } - public static class SkinBuffers { + static class SkinBuffers { short[] joints; float[] weights; int componentSize; - public SkinBuffers(short[] joints, int componentSize) { + SkinBuffers(short[] joints, int componentSize) { this.joints = joints; this.componentSize = componentSize; } - public SkinBuffers() {} + SkinBuffers() { + } } private interface Populator { @@ -1430,7 +1499,9 @@ public VertexBufferPopulator(VertexBuffer.Type bufferType) { public VertexBuffer populate(Integer bufferViewIndex, int componentType, String type, int count, int byteOffset, boolean normalized) throws IOException { if (bufferType == null) { - logger.log(Level.WARNING, "could not assign data to any VertexBuffer type for buffer view {0}", bufferViewIndex); + logger.log(Level.WARNING, + "could not assign data to any VertexBuffer type for buffer view {0}", + bufferViewIndex); return null; } @@ -1452,7 +1523,8 @@ public VertexBuffer populate(Integer bufferViewIndex, int componentType, String // no referenced buffer, specs says to pad the buffer with zeros. padBuffer(buff, bufferSize); } else { - buff = (Buffer) viewBuffer(bufferViewIndex, byteOffset, count, numComponents, originalFormat, format); + buff = (Buffer) viewBuffer(bufferViewIndex, byteOffset, count, numComponents, originalFormat, + format); } if (bufferType == VertexBuffer.Type.Index) { @@ -1484,29 +1556,29 @@ public float[] populate(Integer bufferViewIndex, int componentType, String type, return data; } } -// -// private class FloatGridPopulator implements Populator { -// -// @Override -// public float[][] populate(Integer bufferViewIndex, int componentType, String type, int count, -// int byteOffset, boolean normalized) throws IOException { -// -// int numComponents = getNumberOfComponents(type); -// int dataSize = numComponents * count; -// float[] data = new float[dataSize]; -// -// if (bufferViewIndex == null) { -// // no referenced buffer, specs say to pad the data with zeros. -// padBuffer(data, dataSize); -// } else { -// readBuffer(bufferViewIndex, byteOffset, count, data, numComponents, -// getVertexBufferFormat(componentType)); -// } -// -// return data; -// } -// -// } + // + // private class FloatGridPopulator implements Populator { + // + // @Override + // public float[][] populate(Integer bufferViewIndex, int componentType, String type, int count, + // int byteOffset, boolean normalized) throws IOException { + // + // int numComponents = getNumberOfComponents(type); + // int dataSize = numComponents * count; + // float[] data = new float[dataSize]; + // + // if (bufferViewIndex == null) { + // // no referenced buffer, specs say to pad the data with zeros. + // padBuffer(data, dataSize); + // } else { + // readBuffer(bufferViewIndex, byteOffset, count, data, numComponents, + // getVertexBufferFormat(componentType)); + // } + // + // return data; + // } + // + // } private class Vector3fArrayPopulator implements Populator { @@ -1596,17 +1668,15 @@ public SkinBuffers populate(Integer bufferViewIndex, int componentType, String t return new SkinBuffers(data, format.getComponentSize()); } } - public static void registerExtension(String name, Class ext) { - CustomContentManager.defaultExtensionLoaders.put(name, ext); + CustomContentManager.defaultExtensionLoaders.put(name, ext); } - public static void unregisterExtension(String name) { CustomContentManager.defaultExtensionLoaders.remove(name); } - + /** * Sets the default extras loader used when no loader is specified in the GltfModelKey. * @@ -1617,7 +1687,6 @@ public static void registerDefaultExtrasLoader(Class loa CustomContentManager.defaultExtraLoaderClass = loader; } - /** * Unregisters the default extras loader. */ diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java index 6b28840153..677f15e6ea 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java @@ -31,25 +31,47 @@ */ package com.jme3.scene.plugins.gltf; -import com.jme3.plugins.json.JsonArray; -import com.jme3.plugins.json.JsonElement; -import com.jme3.plugins.json.JsonObject; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + import com.jme3.asset.AssetInfo; import com.jme3.asset.AssetLoadException; -import com.jme3.math.*; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix4f; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; import com.jme3.plugins.json.Json; +import com.jme3.plugins.json.JsonArray; +import com.jme3.plugins.json.JsonElement; +import com.jme3.plugins.json.JsonObject; import com.jme3.plugins.json.JsonParser; -import com.jme3.renderer.opengl.GL; -import com.jme3.scene.*; +import com.jme3.scene.Mesh; +import com.jme3.scene.Spatial; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.plugins.gltf.GltfLoader.SkinBuffers; import com.jme3.texture.Texture; -import com.jme3.util.*; -import java.io.*; -import java.nio.*; -import java.nio.channels.Channels; -import java.nio.channels.ReadableByteChannel; -import java.util.*; -import java.util.logging.Level; -import java.util.logging.Logger; +import com.jme3.util.BufferUtils; +import com.jme3.util.IntMap; +import com.jme3.util.LittleEndien; /** * Created by Nehon on 07/08/2017. @@ -505,7 +527,7 @@ public static byte[] toByteArray(short[] shortArray) { } - public static void handleSkinningBuffers(Mesh mesh, IntMap skinBuffers) { + static void handleSkinningBuffers(Mesh mesh, IntMap skinBuffers) { if (skinBuffers.size() > 0) { int length = skinBuffers.get(0).joints.length; short[] jointsArray = new short[length]; From c519e150e33762543bd1259874f9b748952f8346 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Sat, 7 Feb 2026 16:58:18 +0100 Subject: [PATCH 13/14] Removed probably unused repository entry --- jme3-plugins/build.gradle | 6 ------ 1 file changed, 6 deletions(-) diff --git a/jme3-plugins/build.gradle b/jme3-plugins/build.gradle index ce699730ac..05a30fb328 100644 --- a/jme3-plugins/build.gradle +++ b/jme3-plugins/build.gradle @@ -9,12 +9,6 @@ sourceSets { } } -repositories{ - maven { - url "https://maven.rblb.it/jMonkeyEngine/openize-drako-java/" - } -} - dependencies { api project(':jme3-core') implementation "com.openize:drako:1.4.4" From a5c67e7d44b8398e2a33d4f41c9a08152dd5f0e0 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Sat, 7 Feb 2026 16:59:05 +0100 Subject: [PATCH 14/14] Minor update for glTF loader test --- .../java/jme3test/model/TestGltfLoading.java | 49 ++++++++++--------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java b/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java index 0255f7e85a..f1f2bdc875 100644 --- a/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java +++ b/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java @@ -107,8 +107,6 @@ public void simpleInitApp() { chaseCam = new ChaseCameraAppState(); getStateManager().attach(chaseCam); - loadModelSample("BoomBox", "gltf"); - // loadModelSample("Duck", "gltf"); // loadModelSample("Duck", "glb"); // loadModelSample("ABeautifulGame", "gltf"); @@ -128,35 +126,37 @@ public void simpleInitApp() { // DRACO SAMPLES // loadModelSample("Avocado", "draco"); - // loadModelSample("BarramundiFish", "draco"); - // loadModelSample("BoomBox", "draco"); - - // FIXME: bad skinning? - // loadModelSample("BrainStem", "draco"); - // loadModelSample("CesiumMilkTruck", "draco"); - - // FIXME: FAILS WITH INDEX OUT OF BOUND EXCEPTION - // loadModelSample("VirtualCity", "draco"); - // loadModelSample("Corset", "draco"); - // loadModelSample("Lantern", "draco"); - // loadModelSample("MorphPrimitivesTest", "draco"); + // loadModelSample("WaterBottle", "draco"); + + // Draco skinning samples + //loadModelSample("BrainStem", "draco"); + //loadModelSample("BrainStem", "glb"); - // FIXME: skinning? - // loadModelSample("RiggedFigure", "draco"); + //loadModelSample("CesiumMan", "draco"); + //loadModelSample("CesiumMan", "glb"); - // FIXME: skinning? - // loadModelSample("RiggedSimple", "draco"); + loadModelSample("RiggedFigure", "draco"); + //loadModelSample("RiggedFigure", "glb"); - // FIXME: "dracoMesh" is null - // loadModelSample("SunglassesKhronos", "draco"); - // loadModelSample("WaterBottle", "draco"); + //loadModelSample("RiggedSimple", "draco"); + //loadModelSample("RiggedSimple", "glb"); + // Test for normalized texture coordinates in draco + //loadModelFromPath("Models/gltf/unitSquare11x11_unsignedShortTexCoords-draco.glb"); + + // Uses EXT_texture_webp - not supported yet + //loadModelSample("SunglassesKhronos", "draco"); + + // Probably invalid model + // See https://github.com/KhronosGroup/glTF-Sample-Assets/issues/264 + // loadModelSample("VirtualCity", "draco"); + probeNode.attachChild(assets.get(0)); inputManager.addMapping("autorotate", new KeyTrigger(KeyInput.KEY_SPACE)); @@ -219,11 +219,14 @@ private void loadModelSample(String name, String type) { break; } path += name + "." + ext; + loadModelFromPath(path); + } + + private void loadModelFromPath(String path) { Spatial s = loadModel(path, new Vector3f(0, 0, 0), 1f); BoundingBox bbox = (BoundingBox) s.getWorldBound(); - float maxExtent = Math.max(bbox.getXExtent(), Math.max(bbox.getYExtent(), bbox.getZExtent())); if (maxExtent < 10f) { s.scale(10f / maxExtent); @@ -234,7 +237,7 @@ private void loadModelSample(String name, String type) { chaseCam.setTarget(s); chaseCam.setInvertHorizontalAxis(true); chaseCam.setInvertVerticalAxis(true); - chaseCam.setZoomSpeed(0.5f); + chaseCam.setZoomSpeed(1.5f); chaseCam.setMinVerticalRotation(-FastMath.HALF_PI); chaseCam.setRotationSpeed(3); chaseCam.setDefaultDistance(distance);