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-core/src/main/java/com/jme3/util/BufferUtils.java b/jme3-core/src/main/java/com/jme3/util/BufferUtils.java
index 535f91d4ed..8ed6d9eb78 100644
--- a/jme3-core/src/main/java/com/jme3/util/BufferUtils.java
+++ b/jme3-core/src/main/java/com/jme3/util/BufferUtils.java
@@ -1139,6 +1139,37 @@ public static ShortBuffer clone(ShortBuffer buf) {
return copy;
}
+
+ /**
+ * Create a byte buffer containing the given values, cast to byte
+ *
+ * @param array
+ * The array
+ * @return The buffer
+ */
+ public 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;
+ }
+
+ /**
+ * Create a short buffer containing the given values, cast to short
+ *
+ * @param array
+ * The array
+ * @return The buffer
+ */
+ public 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;
+ }
+
/**
* Ensures there is at least the required number of entries
* left after the current position of the buffer. If the buffer is too small
diff --git a/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java b/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java
index 4ece681a65..0ba1cb10f7 100644
--- a/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java
+++ b/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java
@@ -32,46 +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() {
@@ -80,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);
@@ -98,81 +104,60 @@ 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);
-// 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("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");
+ // loadModelSample("BarramundiFish", "draco");
+ // loadModelSample("BoomBox", "draco");
+ // loadModelSample("CesiumMilkTruck", "draco");
+ // loadModelSample("Corset", "draco");
+ // loadModelSample("Lantern", "draco");
+ // loadModelSample("MorphPrimitivesTest", "draco");
+ // loadModelSample("WaterBottle", "draco");
+
+ // Draco skinning samples
+ //loadModelSample("BrainStem", "draco");
+ //loadModelSample("BrainStem", "glb");
- 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("CesiumMan", "draco");
+ //loadModelSample("CesiumMan", "glb");
+
+ // loadModelSample("RiggedFigure", "draco");
+ //loadModelSample("RiggedFigure", "glb");
+
+ //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));
inputManager.addListener(new ActionListener() {
@@ -213,36 +198,66 @@ 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;
+ 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);
+ maxExtent = 10f;
}
- return null;
+ float distance = 50f;
+
+ chaseCam.setTarget(s);
+ chaseCam.setInvertHorizontalAxis(true);
+ chaseCam.setInvertVerticalAxis(true);
+ chaseCam.setZoomSpeed(1.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);
@@ -250,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);
@@ -317,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()) {
diff --git a/jme3-plugins/build.gradle b/jme3-plugins/build.gradle
index e84f234a68..44e80f017f 100644
--- a/jme3-plugins/build.gradle
+++ b/jme3-plugins/build.gradle
@@ -11,7 +11,7 @@ sourceSets {
dependencies {
api project(':jme3-core')
-
+ implementation "org.jmonkeyengine:drako:1.4.5-jme"
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/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/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..670c9d06db
--- /dev/null
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/DracoMeshCompressionExtensionLoader.java
@@ -0,0 +1,658 @@
+/*
+ * 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.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;
+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.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 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.
+ *
+ * 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 for Draco extension handling
+ */
+ private static final Level level = Level.FINER;
+
+ /**
+ *
+ * - 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 and fill the index vertex buffer of
+ // the mesh with the data from draco
+ 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 = mesh.getBuffer(VertexBuffer.Type.Index);
+ Buffer indicesVertexBufferData = createIndicesVertexBufferData(indicesComponentType, indices);
+ indicesVertexBuffer.setupData(VertexBuffer.Usage.Dynamic, 3,
+ getVertexBufferFormat(indicesComponentType), indicesVertexBufferData);
+
+ // 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);
+
+ 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 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 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.
+ *
+ * @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 data, based on the given Draco-decoded indices
+ *
+ * @param accessorIndex
+ * The accessor index of the vertices
+ * @param indices
+ * The Draco-decoded indices
+ * @return The indices vertex buffer data
+ * @throws AssetLoadException
+ * If the given component type is not GL_UNSIGNED_BYTE,
+ * GL_UNSIGNED_SHORT, or GL_UNSIGNED_INT
+ */
+ private Buffer createIndicesVertexBufferData(int componentType, int indices[]) {
+ if (componentType == GltfConstants.GL_UNSIGNED_BYTE) {
+ return BufferUtils.createByteBuffer(indices);
+ }
+ if (componentType == GltfConstants.GL_UNSIGNED_SHORT) {
+ return BufferUtils.createShortBuffer(indices);
+ }
+ if (componentType == GltfConstants.GL_UNSIGNED_INT) {
+ return BufferUtils.createIntBuffer(indices);
+ }
+ 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);
+ }
+
+ // 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 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
+ * @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 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, count, componentCount);
+ VertexBuffer attributeVertexBuffer = createByteAttributeVertexBuffer(accessor, bufferType,
+ attributeData);
+ return attributeVertexBuffer;
+ }
+ if (componentType == GltfConstants.GL_SHORT || componentType == GltfConstants.GL_UNSIGNED_SHORT) {
+ ShortBuffer attributeData = readShortDracoAttribute(pointAttribute, count, componentCount);
+ VertexBuffer attributeVertexBuffer = createShortAttributeVertexBuffer(accessor, bufferType,
+ attributeData);
+ return attributeVertexBuffer;
+ }
+ if (componentType == GltfConstants.GL_FLOAT) {
+ FloatBuffer attributeData = readFloatDracoAttribute(pointAttribute, 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 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 count,
+ int componentCount) {
+
+ byte p[] = new byte[componentCount];
+ ByteBuffer attributeData = BufferUtils.createByteBuffer(count * 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(i * componentCount + c, p[c]);
+ }
+ }
+ return attributeData;
+ }
+
+ /**
+ * Read the data from the given point attribute, as short values
+ *
+ * @param pointAttribute
+ * The Draco-decoded point attribute
+ * @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 count,
+ int componentCount) {
+
+ short p[] = new short[componentCount];
+ ShortBuffer attributeData = BufferUtils.createShortBuffer(count * 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(i * componentCount + c, p[c]);
+ }
+ }
+ return attributeData;
+ }
+
+ /**
+ * Read the data from the given point attribute, as float values
+ *
+ * @param pointAttribute
+ * The Draco-decoded point attribute
+ * @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 count,
+ int componentCount) {
+ float p[] = new float[componentCount];
+ FloatBuffer attributeData = BufferUtils.createFloatBuffer(count * 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]);
+ }
+ }
+ return attributeData;
+ }
+
+ /**
+ * 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
+ *
+ * @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);
+ int numComponents = getAccessorComponentCount(accessor);
+
+ 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
+ *
+ * @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);
+ int numComponents = getAccessorComponentCount(accessor);
+
+ 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;
+ }
+
+ /**
+ * 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/GltfConstants.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfConstants.java
new file mode 100644
index 0000000000..e9e4a5e853
--- /dev/null
+++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfConstants.java
@@ -0,0 +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;
+
+/**
+ * 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
+ }
+}
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..0a39689bd7 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,88 @@
*/
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.VertexBuffer.Usage;
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 +148,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 +179,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 +330,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 +411,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 +430,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 +446,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 +459,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 +484,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 +527,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 +545,7 @@ public Geometry[] readMeshPrimitives(int meshIndex) throws IOException {
}
geom.setName(name + "_" + index);
-
+
geom.updateModelBound();
geomArray[index] = geom;
index++;
@@ -528,7 +563,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 +573,50 @@ 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.
+ // setting usage to cpuOnly so that the buffer is not sent empty to the GPU
+ ensureBuffer(mesh, VertexBuffer.Type.HWBoneIndex, VertexBuffer.Usage.CpuOnly);
+ ensureBuffer(mesh, VertexBuffer.Type.HWBoneWeight, VertexBuffer.Usage.CpuOnly);
+ mesh.generateBindPose();
+ }
+ }
+
+ /**
+ * Ensure that the given mesh has a buffer with the given type.
+ *
+ * If it does not yet have a buffer with the given type, then
+ * a new buffer with the given type and usage is created and
+ * assigned to the mesh.
+ *
+ * @param mesh The mesh
+ * @param type The type
+ * @param usage The usage
+ */
+ private static void ensureBuffer(Mesh mesh, VertexBuffer.Type type, Usage usage) {
+ if (mesh.getBuffer(type) != null) {
+ return;
+ }
+ VertexBuffer vb = new VertexBuffer(type);
+ vb.setUsage(usage);
+ mesh.setBuffer(vb);
+ }
+
private R readAccessorData(int accessorIndex, Populator populator) throws IOException {
assertNotNull(accessors, "No accessor attribute in the gltf file");
@@ -579,7 +658,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 +675,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,21 +688,74 @@ 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;
+ }
+ /**
+ * 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");
@@ -646,7 +778,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);
@@ -656,11 +789,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.
@@ -704,7 +837,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"));
@@ -730,7 +864,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")));
}
@@ -804,7 +938,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");
@@ -837,11 +971,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:")) {
@@ -851,7 +986,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 {
@@ -871,7 +1006,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;
@@ -886,13 +1021,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) {
@@ -967,8 +1102,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) {
@@ -993,15 +1128,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);
}
}
@@ -1015,17 +1149,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);
@@ -1182,8 +1316,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;
}
@@ -1227,12 +1360,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);
}
@@ -1264,7 +1397,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;
@@ -1351,17 +1484,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 {
@@ -1380,7 +1514,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;
}
@@ -1402,7 +1538,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) {
@@ -1434,29 +1571,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 {
@@ -1546,17 +1683,15 @@ public SkinBuffers populate(Integer bufferViewIndex, int componentType, String t
return new SkinBuffers(data, format.getComponentSize());
}
}
-
public static void registerExtension(String name, Class extends ExtensionLoader> 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.
*
@@ -1567,7 +1702,6 @@ public static void registerDefaultExtrasLoader(Class extends ExtrasLoader> 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 7c6ee853f1..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.export.binary.ByteUtils;
-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.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.
@@ -105,17 +127,17 @@ public static Mesh.Mode getMeshMode(Integer mode) {
public static VertexBuffer.Format getVertexBufferFormat(int componentType) {
switch (componentType) {
- case 5120:
+ case GltfConstants.GL_BYTE:
return VertexBuffer.Format.Byte;
- case 5121:
+ case GltfConstants.GL_UNSIGNED_BYTE:
return VertexBuffer.Format.UnsignedByte;
- case 5122:
+ case GltfConstants.GL_SHORT:
return VertexBuffer.Format.Short;
- case 5123:
+ case GltfConstants.GL_UNSIGNED_SHORT:
return VertexBuffer.Format.UnsignedShort;
- case 5125:
+ case GltfConstants.GL_UNSIGNED_INT:
return VertexBuffer.Format.UnsignedInt;
- case 5126:
+ case GltfConstants.GL_FLOAT:
return VertexBuffer.Format.Float;
default:
throw new AssetLoadException("Illegal component type: " + componentType);
@@ -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];
@@ -724,6 +746,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();