Gltf loader can load mesh data and scene structure.
This commit is contained in:
parent
50eaa34143
commit
7951f5a987
@ -23,3 +23,5 @@ LOADER com.jme3.scene.plugins.ogre.SceneLoader : scene
|
||||
LOADER com.jme3.scene.plugins.blender.BlenderModelLoader : blend
|
||||
LOADER com.jme3.shader.plugins.GLSLLoader : vert, frag, geom, tsctrl, tseval, glsl, glsllib
|
||||
LOADER com.jme3.scene.plugins.fbx.FbxLoader : fbx
|
||||
LOADER com.jme3.scene.plugins.gltf.GltfLoader : gltf
|
||||
LOADER com.jme3.scene.plugins.gltf.BinLoader : bin
|
||||
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012 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 jme3test.model;
|
||||
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.light.DirectionalLight;
|
||||
import com.jme3.light.PointLight;
|
||||
import com.jme3.math.*;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.shape.Sphere;
|
||||
|
||||
public class TestGltfLoading extends SimpleApplication {
|
||||
|
||||
|
||||
public static void main(String[] args) {
|
||||
TestGltfLoading app = new TestGltfLoading();
|
||||
app.start();
|
||||
}
|
||||
|
||||
public void simpleInitApp() {
|
||||
flyCam.setMoveSpeed(10f);
|
||||
viewPort.setBackgroundColor(ColorRGBA.DarkGray);
|
||||
|
||||
// sunset light
|
||||
// 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);
|
||||
|
||||
//rootNode.attachChild(assetManager.loadModel("Models/gltf/box/box.gltf"));
|
||||
rootNode.attachChild(assetManager.loadModel("Models/gltf/duck/Duck.gltf"));
|
||||
|
||||
//rootNode.attachChild(assetManager.loadModel("Models/gltf/hornet/scene.gltf"));
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -6,6 +6,7 @@ sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDir 'src/ogre/java'
|
||||
srcDir 'src/gltf/java'
|
||||
srcDir 'src/fbx/java'
|
||||
srcDir 'src/xml/java'
|
||||
}
|
||||
@ -14,5 +15,6 @@ sourceSets {
|
||||
|
||||
dependencies {
|
||||
compile project(':jme3-core')
|
||||
compile 'com.google.code.gson:gson:2.8.1'
|
||||
testCompile project(':jme3-desktop')
|
||||
}
|
||||
|
@ -0,0 +1,16 @@
|
||||
package com.jme3.scene.plugins.gltf;
|
||||
|
||||
import com.jme3.asset.AssetInfo;
|
||||
import com.jme3.asset.AssetLoader;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Created by Nehon on 08/08/2017.
|
||||
*/
|
||||
public class BinLoader implements AssetLoader {
|
||||
@Override
|
||||
public Object load(AssetInfo assetInfo) throws IOException {
|
||||
return assetInfo.openStream();
|
||||
}
|
||||
}
|
@ -0,0 +1,399 @@
|
||||
package com.jme3.scene.plugins.gltf;
|
||||
|
||||
import com.google.gson.*;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.jme3.asset.*;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.*;
|
||||
import com.jme3.scene.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.Buffer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.jme3.scene.plugins.gltf.GltfUtils.*;
|
||||
|
||||
/**
|
||||
* GLTF 2.0 loader
|
||||
* Created by Nehon on 07/08/2017.
|
||||
*/
|
||||
public class GltfLoader implements AssetLoader {
|
||||
|
||||
//Data cache for already parsed JME objects
|
||||
private Map<String, Object[]> dataCache = new HashMap<>();
|
||||
private JsonArray scenes;
|
||||
private JsonArray nodes;
|
||||
private JsonArray meshes;
|
||||
private JsonArray accessors;
|
||||
private JsonArray bufferViews;
|
||||
private JsonArray buffers;
|
||||
private JsonArray materials;
|
||||
private Material defaultMat;
|
||||
private byte[] tmpByteArray;
|
||||
private AssetInfo info;
|
||||
|
||||
@Override
|
||||
public Object load(AssetInfo assetInfo) throws IOException {
|
||||
try {
|
||||
dataCache.clear();
|
||||
info = assetInfo;
|
||||
|
||||
if (defaultMat == null) {
|
||||
defaultMat = new Material(assetInfo.getManager(), "Common/MatDefs/Light/PBRLighting.j3md");
|
||||
defaultMat.setColor("BaseColor", ColorRGBA.White);
|
||||
defaultMat.setFloat("Metallic", 0f);
|
||||
defaultMat.setFloat("Roughness", 1f);
|
||||
}
|
||||
|
||||
|
||||
JsonObject root = new JsonParser().parse(new JsonReader(new InputStreamReader(assetInfo.openStream()))).getAsJsonObject();
|
||||
|
||||
JsonObject asset = root.getAsJsonObject().get("asset").getAsJsonObject();
|
||||
String generator = getAsString(asset, "generator");
|
||||
String version = getAsString(asset, "version");
|
||||
String minVersion = getAsString(asset, "minVersion");
|
||||
if (!isSupported(version, minVersion)) {
|
||||
//TODO maybe just warn. gltf specs claims it will be backward compatible so at worst the user will miss some data.
|
||||
throw new AssetLoadException("Gltf Loader doesn't support this gltf version: " + version + (minVersion != null ? ("/" + minVersion) : ""));
|
||||
}
|
||||
|
||||
scenes = root.getAsJsonArray("scenes");
|
||||
nodes = root.getAsJsonArray("nodes");
|
||||
meshes = root.getAsJsonArray("meshes");
|
||||
accessors = root.getAsJsonArray("accessors");
|
||||
bufferViews = root.getAsJsonArray("bufferViews");
|
||||
buffers = root.getAsJsonArray("buffers");
|
||||
materials = root.getAsJsonArray("materials");
|
||||
|
||||
allocatedTmpByteArray();
|
||||
|
||||
JsonPrimitive defaultScene = root.getAsJsonPrimitive("scene");
|
||||
|
||||
Node n = loadScenes(defaultScene);
|
||||
//only one scene let's not return the root.
|
||||
if (n.getChildren().size() == 1) {
|
||||
n = (Node) n.getChild(0);
|
||||
}
|
||||
//no name for the scene... let's set the file name.
|
||||
if (n.getName() == null) {
|
||||
n.setName(assetInfo.getKey().getName());
|
||||
}
|
||||
return n;
|
||||
} catch (Exception e) {
|
||||
throw new AssetLoadException("An error occurred loading " + assetInfo.getKey().getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void allocatedTmpByteArray() {
|
||||
//Allocate the tmpByteArray to the biggest bufferView
|
||||
if (bufferViews == null) {
|
||||
throw new AssetLoadException("No buffer view defined but one is referenced by an accessor");
|
||||
}
|
||||
int maxLength = 0;
|
||||
for (JsonElement bufferView : bufferViews) {
|
||||
Integer byteLength = getAsInteger(bufferView.getAsJsonObject(), "byteLength");
|
||||
if (byteLength != null && maxLength < byteLength) {
|
||||
maxLength = byteLength;
|
||||
}
|
||||
}
|
||||
tmpByteArray = new byte[maxLength];
|
||||
}
|
||||
|
||||
private boolean isSupported(String version, String minVersion) {
|
||||
return "2.0".equals(version);
|
||||
}
|
||||
|
||||
private Node loadScenes(JsonPrimitive defaultScene) throws IOException {
|
||||
if (scenes == null) {
|
||||
//no scene... lets handle this later...
|
||||
throw new AssetLoadException("Gltf files with no scene is not yet supported");
|
||||
}
|
||||
Node root = new Node();
|
||||
for (JsonElement scene : scenes) {
|
||||
Node sceneNode = new Node();
|
||||
//specs says that only the default scene should be rendered,
|
||||
// if there are several scenes, they are attached to the rootScene, but they are culled
|
||||
sceneNode.setCullHint(Spatial.CullHint.Always);
|
||||
|
||||
sceneNode.setName(getAsString(scene.getAsJsonObject(), "name"));
|
||||
JsonArray sceneNodes = scene.getAsJsonObject().getAsJsonArray("nodes");
|
||||
for (JsonElement node : sceneNodes) {
|
||||
sceneNode.attachChild(loadNode(node.getAsInt()));
|
||||
}
|
||||
root.attachChild(sceneNode);
|
||||
}
|
||||
|
||||
//Setting the default scene cul hint to inherit.
|
||||
int activeChild = 0;
|
||||
if (defaultScene != null) {
|
||||
activeChild = defaultScene.getAsInt();
|
||||
}
|
||||
root.getChild(activeChild).setCullHint(Spatial.CullHint.Inherit);
|
||||
return root;
|
||||
}
|
||||
|
||||
private Spatial loadNode(int nodeIndex) throws IOException {
|
||||
Spatial spatial = fetchFromCache("nodes", nodeIndex, Spatial.class);
|
||||
if (spatial != null) {
|
||||
//If a spatial is referenced several times, it may be attached to different parents,
|
||||
// and it's not possible in JME, so we have to clone it.
|
||||
return spatial.clone();
|
||||
}
|
||||
JsonObject nodeData = nodes.get(nodeIndex).getAsJsonObject();
|
||||
Integer meshIndex = getAsInteger(nodeData, "mesh");
|
||||
if (meshIndex != null) {
|
||||
if (meshes == null) {
|
||||
throw new AssetLoadException("Can't find any mesh data, yet a node references a mesh");
|
||||
}
|
||||
|
||||
//TODO material
|
||||
Material mat = defaultMat;
|
||||
|
||||
//there is a mesh in this node, however gltf can split meshes in primitives (some kind of sub meshes),
|
||||
//We don't have this in JME so we have to make one mesh and one Geometry for each primitive.
|
||||
Mesh[] primitives = loadMeshPrimitives(meshIndex);
|
||||
if (primitives.length > 1) {
|
||||
//only one mesh, lets just make a geometry.
|
||||
Geometry geometry = new Geometry(null, primitives[0]);
|
||||
geometry.setMaterial(mat);
|
||||
geometry.updateModelBound();
|
||||
spatial = geometry;
|
||||
} else {
|
||||
//several meshes, let's make a parent Node and attach several geometries to it
|
||||
Node node = new Node();
|
||||
for (Mesh primitive : primitives) {
|
||||
Geometry geom = new Geometry(null, primitive);
|
||||
geom.setMaterial(mat);
|
||||
geom.updateModelBound();
|
||||
node.attachChild(geom);
|
||||
}
|
||||
spatial = node;
|
||||
}
|
||||
|
||||
|
||||
spatial.setName(loadMeshName(meshIndex));
|
||||
|
||||
} else {
|
||||
//no mesh, we have a node. Can be a camera node or a regular node.
|
||||
//TODO handle camera nodes?
|
||||
Node node = new Node();
|
||||
JsonArray children = nodeData.getAsJsonArray("children");
|
||||
if (children != null) {
|
||||
for (JsonElement child : children) {
|
||||
node.attachChild(loadNode(child.getAsInt()));
|
||||
}
|
||||
}
|
||||
spatial = node;
|
||||
}
|
||||
if (spatial.getName() == null) {
|
||||
spatial.setName(getAsString(nodeData.getAsJsonObject(), "name"));
|
||||
}
|
||||
spatial.setLocalTransform(loadTransforms(nodeData));
|
||||
|
||||
addToCache("nodes", nodeIndex, spatial, nodes.size());
|
||||
return spatial;
|
||||
}
|
||||
|
||||
private Transform loadTransforms(JsonObject nodeData) {
|
||||
Transform transform = new Transform();
|
||||
JsonArray matrix = nodeData.getAsJsonArray("matrix");
|
||||
if (matrix != null) {
|
||||
//transforms are given as a mat4
|
||||
float[] tmpArray = new float[16];
|
||||
for (int i = 0; i < tmpArray.length; i++) {
|
||||
tmpArray[i] = matrix.get(i).getAsFloat();
|
||||
}
|
||||
Matrix4f mat = new Matrix4f(tmpArray);
|
||||
transform.fromTransformMatrix(mat);
|
||||
return transform;
|
||||
}
|
||||
//no matrix transforms: no transforms or transforms givens as translation/rotation/scale
|
||||
JsonArray translation = nodeData.getAsJsonArray("translation");
|
||||
if (translation != null) {
|
||||
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()));
|
||||
}
|
||||
JsonArray scale = nodeData.getAsJsonArray("scale");
|
||||
if (scale != null) {
|
||||
transform.setScale(
|
||||
scale.get(0).getAsFloat(),
|
||||
scale.get(1).getAsFloat(),
|
||||
scale.get(2).getAsFloat());
|
||||
}
|
||||
|
||||
return transform;
|
||||
}
|
||||
|
||||
private Mesh[] loadMeshPrimitives(int meshIndex) throws IOException {
|
||||
Mesh[] meshArray = (Mesh[]) fetchFromCache("meshes", meshIndex, Object.class);
|
||||
if (meshArray != null) {
|
||||
return meshArray;
|
||||
}
|
||||
JsonObject meshData = meshes.get(meshIndex).getAsJsonObject();
|
||||
JsonArray primitives = meshData.getAsJsonArray("primitives");
|
||||
if (primitives == null) {
|
||||
throw new AssetLoadException("Can't find any primitives in mesh " + meshIndex);
|
||||
}
|
||||
|
||||
meshArray = new Mesh[primitives.size()];
|
||||
int index = 0;
|
||||
for (JsonElement primitive : primitives) {
|
||||
JsonObject meshObject = primitive.getAsJsonObject();
|
||||
Mesh mesh = new Mesh();
|
||||
Integer mode = getAsInteger(meshObject, "mode");
|
||||
mesh.setMode(getMeshMode(mode));
|
||||
Integer indices = getAsInteger(meshObject, "indices");
|
||||
if (indices != null) {
|
||||
mesh.setBuffer(loadVertexBuffer(indices, VertexBuffer.Type.Index));
|
||||
|
||||
}
|
||||
JsonObject attributes = meshObject.getAsJsonObject("attributes");
|
||||
assertNotNull(attributes, "No attributes defined for mesh " + mesh);
|
||||
for (Map.Entry<String, JsonElement> entry : attributes.entrySet()) {
|
||||
mesh.setBuffer(loadVertexBuffer(entry.getValue().getAsInt(), getVertexBufferType(entry.getKey())));
|
||||
}
|
||||
meshArray[index] = mesh;
|
||||
index++;
|
||||
|
||||
//TODO material, targets(morph anim...)
|
||||
}
|
||||
|
||||
addToCache("meshes", meshIndex, meshArray, meshes.size());
|
||||
return meshArray;
|
||||
}
|
||||
|
||||
private VertexBuffer loadVertexBuffer(int accessorIndex, VertexBuffer.Type bufferType) throws IOException {
|
||||
|
||||
if (accessors == null) {
|
||||
throw new AssetLoadException("No accessor attribute in the gltf file");
|
||||
}
|
||||
JsonObject accessor = accessors.get(accessorIndex).getAsJsonObject();
|
||||
Integer bufferViewIndex = getAsInteger(accessor, "bufferView");
|
||||
int byteOffset = getAsInteger(accessor, "byteOffset", 0);
|
||||
Integer componentType = getAsInteger(accessor, "componentType");
|
||||
assertNotNull(componentType, "No component type defined for accessor " + accessorIndex);
|
||||
boolean normalized = getAsBoolean(accessor, "normalized", false);
|
||||
Integer count = getAsInteger(accessor, "count");
|
||||
assertNotNull(count, "No count attribute defined for accessor " + accessorIndex);
|
||||
String type = getAsString(accessor, "type");
|
||||
assertNotNull(type, "No type attribute defined for accessor " + accessorIndex);
|
||||
|
||||
VertexBuffer vb = new VertexBuffer(bufferType);
|
||||
VertexBuffer.Format format = getVertexBufferFormat(componentType);
|
||||
int numComponents = getNumberOfComponents(type);
|
||||
|
||||
Buffer buff = VertexBuffer.createBuffer(format, numComponents, count);
|
||||
readBuffer(bufferViewIndex, byteOffset, numComponents * count, buff, numComponents);
|
||||
if (bufferType == VertexBuffer.Type.Index) {
|
||||
numComponents = 3;
|
||||
}
|
||||
vb.setupData(VertexBuffer.Usage.Dynamic, numComponents, format, buff);
|
||||
|
||||
//TODO min / max
|
||||
//TODO sparse
|
||||
//TODO extensions?
|
||||
//TODO extras?
|
||||
return vb;
|
||||
}
|
||||
|
||||
private void readBuffer(Integer bufferViewIndex, int byteOffset, int bufferSize, Buffer buff, int numComponents) throws IOException {
|
||||
if (bufferViewIndex == null) {
|
||||
//no referenced buffer, specs says to pad the buffer with zeros.
|
||||
padBuffer(buff, bufferSize);
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject bufferView = bufferViews.get(bufferViewIndex).getAsJsonObject();
|
||||
Integer bufferIndex = getAsInteger(bufferView, "buffer");
|
||||
assertNotNull(bufferIndex, "No buffer defined for bufferView " + bufferViewIndex);
|
||||
int bvByteOffset = getAsInteger(bufferView, "byteOffset", 0);
|
||||
Integer byteLength = getAsInteger(bufferView, "byteLength");
|
||||
assertNotNull(byteLength, "No byte length defined for bufferView " + bufferViewIndex);
|
||||
int byteStride = getAsInteger(bufferView, "byteStride", 0);
|
||||
|
||||
//target defines ELEMENT_ARRAY_BUFFER or ARRAY_BUFFER, but we already know that since we know we load the indexbuffer or any other...
|
||||
//not sure it's useful for us, but I guess it's useful when you map data directly to the GPU.
|
||||
//int target = getAsInteger(bufferView, "target", 0);
|
||||
|
||||
byte[] data = readData(bufferIndex);
|
||||
populateBuffer(buff, data, bufferSize, byteOffset + bvByteOffset, byteStride, numComponents);
|
||||
|
||||
//TODO extensions?
|
||||
//TODO extras?
|
||||
|
||||
}
|
||||
|
||||
private byte[] readData(int bufferIndex) throws IOException {
|
||||
|
||||
if (buffers == null) {
|
||||
throw new AssetLoadException("No buffer defined");
|
||||
}
|
||||
JsonObject buffer = buffers.get(bufferIndex).getAsJsonObject();
|
||||
String uri = getAsString(buffer, "uri");
|
||||
Integer bufferLength = getAsInteger(buffer, "byteLength");
|
||||
assertNotNull(bufferLength, "No byteLength defined for buffer " + bufferIndex);
|
||||
if (uri != null) {
|
||||
if (uri.startsWith("data:")) {
|
||||
//inlined base64 data
|
||||
//data:<mimeType>;base64,<base64 data>
|
||||
//TODO handle inlined base64
|
||||
throw new AssetLoadException("Inlined base64 data is not supported yet");
|
||||
} else {
|
||||
//external file let's load it
|
||||
if (!uri.endsWith(".bin")) {
|
||||
throw new AssetLoadException("Cannot load " + uri + ", a .bin extension is required.");
|
||||
}
|
||||
byte[] data = (byte[]) fetchFromCache("buffers", bufferIndex, Object.class);
|
||||
if (data != null) {
|
||||
return data;
|
||||
}
|
||||
InputStream input = (InputStream) info.getManager().loadAsset(info.getKey().getFolder() + uri);
|
||||
data = new byte[bufferLength];
|
||||
input.read(data);
|
||||
addToCache("buffers", bufferIndex, data, buffers.size());
|
||||
|
||||
return data;
|
||||
}
|
||||
} else {
|
||||
//no URI we are in a binary file so the data is in the 2nd chunk
|
||||
//TODO handle binary GLTF (GLB)
|
||||
throw new AssetLoadException("Binary gltf is not supported yet");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private String loadMeshName(int meshIndex) {
|
||||
JsonObject meshData = meshes.get(meshIndex).getAsJsonObject();
|
||||
return getAsString(meshData, "name");
|
||||
}
|
||||
|
||||
private <T> T fetchFromCache(String name, int index, Class<T> type) {
|
||||
Object[] data = dataCache.get(name);
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
return type.cast(data[index]);
|
||||
}
|
||||
|
||||
private void addToCache(String name, int index, Object object, int maxLength) {
|
||||
Object[] data = dataCache.get(name);
|
||||
if (data == null) {
|
||||
data = new Object[maxLength];
|
||||
dataCache.put(name, data);
|
||||
}
|
||||
data[index] = object;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,285 @@
|
||||
package com.jme3.scene.plugins.gltf;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.jme3.asset.AssetLoadException;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.VertexBuffer;
|
||||
import com.jme3.util.LittleEndien;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.*;
|
||||
|
||||
/**
|
||||
* Created by Nehon on 07/08/2017.
|
||||
*/
|
||||
public class GltfUtils {
|
||||
|
||||
public static Mesh.Mode getMeshMode(Integer mode) {
|
||||
if (mode == null) {
|
||||
return Mesh.Mode.Triangles;
|
||||
}
|
||||
//too bad, we could have returned the enum value from the ordinal
|
||||
//but LineLoop and LineStrip are inverted in the Mesh.Mode Enum declaration.
|
||||
switch (mode) {
|
||||
case 0:
|
||||
return Mesh.Mode.Points;
|
||||
case 1:
|
||||
return Mesh.Mode.Lines;
|
||||
case 2:
|
||||
return Mesh.Mode.LineLoop;
|
||||
case 3:
|
||||
return Mesh.Mode.LineStrip;
|
||||
case 4:
|
||||
return Mesh.Mode.Triangles;
|
||||
case 5:
|
||||
return Mesh.Mode.TriangleStrip;
|
||||
case 6:
|
||||
return Mesh.Mode.TriangleFan;
|
||||
}
|
||||
return Mesh.Mode.Triangles;
|
||||
}
|
||||
|
||||
public static VertexBuffer.Format getVertexBufferFormat(int componentType) {
|
||||
switch (componentType) {
|
||||
case 5120:
|
||||
return VertexBuffer.Format.Byte;
|
||||
case 5121:
|
||||
return VertexBuffer.Format.UnsignedByte;
|
||||
case 5122:
|
||||
return VertexBuffer.Format.Short;
|
||||
case 5123:
|
||||
return VertexBuffer.Format.UnsignedShort;
|
||||
case 5125:
|
||||
return VertexBuffer.Format.UnsignedInt;
|
||||
case 5126:
|
||||
return VertexBuffer.Format.Float;
|
||||
default:
|
||||
throw new AssetLoadException("Illegal component type: " + componentType);
|
||||
}
|
||||
}
|
||||
|
||||
public static int getNumberOfComponents(String type) {
|
||||
switch (type) {
|
||||
case "SCALAR":
|
||||
return 1;
|
||||
case "VEC2":
|
||||
return 2;
|
||||
case "VEC3":
|
||||
return 3;
|
||||
case "VEC4":
|
||||
return 4;
|
||||
case "MAT2":
|
||||
return 4;
|
||||
case "MAT3":
|
||||
return 9;
|
||||
case "MAT4":
|
||||
return 16;
|
||||
default:
|
||||
throw new AssetLoadException("Illegal type: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
public static VertexBuffer.Type getVertexBufferType(String attribute) {
|
||||
switch (attribute) {
|
||||
case "POSITION":
|
||||
return VertexBuffer.Type.Position;
|
||||
case "NORMAL":
|
||||
return VertexBuffer.Type.Normal;
|
||||
case "TANGENT":
|
||||
return VertexBuffer.Type.Tangent;
|
||||
case "TEXCOORD_0":
|
||||
return VertexBuffer.Type.TexCoord;
|
||||
case "TEXCOORD_1":
|
||||
return VertexBuffer.Type.TexCoord2;
|
||||
case "TEXCOORD_2":
|
||||
return VertexBuffer.Type.TexCoord3;
|
||||
case "TEXCOORD_3":
|
||||
return VertexBuffer.Type.TexCoord4;
|
||||
case "TEXCOORD_4":
|
||||
return VertexBuffer.Type.TexCoord5;
|
||||
case "TEXCOORD_5":
|
||||
return VertexBuffer.Type.TexCoord6;
|
||||
case "TEXCOORD_6":
|
||||
return VertexBuffer.Type.TexCoord7;
|
||||
case "TEXCOORD_7":
|
||||
return VertexBuffer.Type.TexCoord8;
|
||||
case "COLOR_0":
|
||||
return VertexBuffer.Type.Color;
|
||||
case "JOINTS_0":
|
||||
return VertexBuffer.Type.BoneIndex;
|
||||
case "WEIGHT_0":
|
||||
return VertexBuffer.Type.BoneWeight;
|
||||
default:
|
||||
throw new AssetLoadException("Unsupported buffer attribute: " + attribute);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static void padBuffer(Buffer buffer, int bufferSize) {
|
||||
buffer.clear();
|
||||
if (buffer instanceof IntBuffer) {
|
||||
IntBuffer ib = (IntBuffer) buffer;
|
||||
for (int i = 0; i < bufferSize; i++) {
|
||||
ib.put(0);
|
||||
}
|
||||
} else if (buffer instanceof FloatBuffer) {
|
||||
FloatBuffer fb = (FloatBuffer) buffer;
|
||||
for (int i = 0; i < bufferSize; i++) {
|
||||
fb.put(0);
|
||||
}
|
||||
} else if (buffer instanceof ShortBuffer) {
|
||||
ShortBuffer sb = (ShortBuffer) buffer;
|
||||
for (int i = 0; i < bufferSize; i++) {
|
||||
sb.put((short) 0);
|
||||
}
|
||||
} else if (buffer instanceof ByteBuffer) {
|
||||
ByteBuffer bb = (ByteBuffer) buffer;
|
||||
for (int i = 0; i < bufferSize; i++) {
|
||||
bb.put((byte) 0);
|
||||
}
|
||||
}
|
||||
buffer.rewind();
|
||||
}
|
||||
|
||||
public static void populateBuffer(Buffer buffer, byte[] source, int length, int byteOffset, int byteStride, int numComponents) throws IOException {
|
||||
buffer.clear();
|
||||
|
||||
if (buffer instanceof ByteBuffer) {
|
||||
populateByteBuffer((ByteBuffer) buffer, source, length, byteOffset, byteStride, numComponents);
|
||||
return;
|
||||
}
|
||||
LittleEndien stream = getStream(source);
|
||||
if (buffer instanceof ShortBuffer) {
|
||||
populateShortBuffer((ShortBuffer) buffer, stream, length, byteOffset, byteStride, numComponents);
|
||||
} else if (buffer instanceof IntBuffer) {
|
||||
populateIntBuffer((IntBuffer) buffer, stream, length, byteOffset, byteStride, numComponents);
|
||||
} else if (buffer instanceof FloatBuffer) {
|
||||
populateFloatBuffer((FloatBuffer) buffer, stream, length, byteOffset, byteStride, numComponents);
|
||||
}
|
||||
buffer.rewind();
|
||||
}
|
||||
|
||||
private static void populateByteBuffer(ByteBuffer buffer, byte[] source, int length, int byteOffset, int byteStride, int numComponents) {
|
||||
int index = byteOffset;
|
||||
int componentSize = 1;
|
||||
while (index < length + byteOffset) {
|
||||
for (int i = 0; i < numComponents; i++) {
|
||||
buffer.put(source[index + i]);
|
||||
}
|
||||
index += Math.max(componentSize * numComponents, byteStride);
|
||||
}
|
||||
}
|
||||
|
||||
private static void populateShortBuffer(ShortBuffer buffer, LittleEndien stream, int length, int byteOffset, int byteStride, int numComponents) throws IOException {
|
||||
int index = byteOffset;
|
||||
int componentSize = 2;
|
||||
int end = length * componentSize + byteOffset;
|
||||
stream.skipBytes(byteOffset);
|
||||
while (index < end) {
|
||||
for (int i = 0; i < numComponents; i++) {
|
||||
buffer.put(stream.readShort());
|
||||
}
|
||||
index += Math.max(componentSize * numComponents, byteStride);
|
||||
}
|
||||
}
|
||||
|
||||
private static void populateIntBuffer(IntBuffer buffer, LittleEndien stream, int length, int byteOffset, int byteStride, int numComponents) throws IOException {
|
||||
int index = byteOffset;
|
||||
int componentSize = 4;
|
||||
int end = length * componentSize + byteOffset;
|
||||
stream.skipBytes(byteOffset);
|
||||
while (index < end) {
|
||||
for (int i = 0; i < numComponents; i++) {
|
||||
buffer.put(stream.readInt());
|
||||
}
|
||||
index += Math.max(componentSize * numComponents, byteStride);
|
||||
}
|
||||
}
|
||||
|
||||
private static void populateFloatBuffer(FloatBuffer buffer, LittleEndien stream, int length, int byteOffset, int byteStride, int numComponents) throws IOException {
|
||||
int index = byteOffset;
|
||||
int componentSize = 4;
|
||||
int end = length * componentSize + byteOffset;
|
||||
stream.skipBytes(byteOffset);
|
||||
while (index < end) {
|
||||
for (int i = 0; i < numComponents; i++) {
|
||||
buffer.put(stream.readFloat());
|
||||
}
|
||||
index += Math.max(componentSize * numComponents, byteStride);
|
||||
}
|
||||
}
|
||||
|
||||
private static LittleEndien getStream(byte[] buffer) {
|
||||
return new LittleEndien(new DataInputStream(new ByteArrayInputStream(buffer)));
|
||||
}
|
||||
|
||||
public static String getAsString(JsonObject parent, String name) {
|
||||
JsonElement el = parent.get(name);
|
||||
return el == null ? null : el.getAsString();
|
||||
}
|
||||
|
||||
public static Integer getAsInteger(JsonObject parent, String name) {
|
||||
JsonElement el = parent.get(name);
|
||||
return el == null ? null : el.getAsInt();
|
||||
}
|
||||
|
||||
public static Integer getAsInteger(JsonObject parent, String name, int defaultValue) {
|
||||
JsonElement el = parent.get(name);
|
||||
return el == null ? defaultValue : el.getAsInt();
|
||||
}
|
||||
|
||||
public static Boolean getAsBoolean(JsonObject parent, String name) {
|
||||
JsonElement el = parent.get(name);
|
||||
return el == null ? null : el.getAsBoolean();
|
||||
}
|
||||
|
||||
public static Boolean getAsBoolean(JsonObject parent, String name, boolean defaultValue) {
|
||||
JsonElement el = parent.get(name);
|
||||
return el == null ? defaultValue : el.getAsBoolean();
|
||||
}
|
||||
|
||||
public static void assertNotNull(Object o, String errorMessage) {
|
||||
if (o == null) {
|
||||
throw new AssetLoadException(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public static void dumpMesh(Mesh m) {
|
||||
for (VertexBuffer vertexBuffer : m.getBufferList().getArray()) {
|
||||
System.err.println(vertexBuffer.getBufferType());
|
||||
System.err.println(vertexBuffer.getFormat());
|
||||
if (vertexBuffer.getData() instanceof FloatBuffer) {
|
||||
FloatBuffer b = (FloatBuffer) vertexBuffer.getData();
|
||||
float[] arr = new float[b.capacity()];
|
||||
b.rewind();
|
||||
b.get(arr);
|
||||
b.rewind();
|
||||
for (float v : arr) {
|
||||
System.err.print(v + ",");
|
||||
}
|
||||
}
|
||||
if (vertexBuffer.getData() instanceof ShortBuffer) {
|
||||
ShortBuffer b = (ShortBuffer) vertexBuffer.getData();
|
||||
short[] arr = new short[b.capacity()];
|
||||
b.rewind();
|
||||
b.get(arr);
|
||||
b.rewind();
|
||||
for (short v : arr) {
|
||||
System.err.print(v + ",");
|
||||
}
|
||||
}
|
||||
if (vertexBuffer.getData() instanceof IntBuffer) {
|
||||
IntBuffer b = (IntBuffer) vertexBuffer.getData();
|
||||
int[] arr = new int[b.capacity()];
|
||||
b.rewind();
|
||||
b.get(arr);
|
||||
b.rewind();
|
||||
for (int v : arr) {
|
||||
System.err.print(v + ",");
|
||||
}
|
||||
}
|
||||
System.err.println("\n---------------------------");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package com.jme3.scene.plugins.gltf;
|
||||
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.material.plugin.TestMaterialWrite;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.system.JmeSystem;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Created by Nehon on 07/08/2017.
|
||||
*/
|
||||
public class GltfLoaderTest {
|
||||
|
||||
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";
|
||||
|
||||
private AssetManager assetManager;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
assetManager = JmeSystem.newAssetManager(
|
||||
TestMaterialWrite.class.getResource("/com/jme3/asset/Desktop.cfg"));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoad() {
|
||||
Spatial scene = assetManager.loadModel("gltf/box/box.gltf");
|
||||
dumpScene(scene, 0);
|
||||
// scene = assetManager.loadModel("gltf/hornet/scene.gltf");
|
||||
// dumpScene(scene, 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());
|
||||
if (s instanceof Node) {
|
||||
Node n = (Node) s;
|
||||
for (Spatial spatial : n.getChildren()) {
|
||||
dumpScene(spatial, indent + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
BIN
jme3-plugins/src/test/resources/gltf/box/Box0.bin
Normal file
BIN
jme3-plugins/src/test/resources/gltf/box/Box0.bin
Normal file
Binary file not shown.
142
jme3-plugins/src/test/resources/gltf/box/box.gltf
Normal file
142
jme3-plugins/src/test/resources/gltf/box/box.gltf
Normal file
@ -0,0 +1,142 @@
|
||||
{
|
||||
"asset": {
|
||||
"generator": "COLLADA2GLTF",
|
||||
"version": "2.0"
|
||||
},
|
||||
"scene": 0,
|
||||
"scenes": [
|
||||
{
|
||||
"nodes": [
|
||||
0
|
||||
]
|
||||
}
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"children": [
|
||||
1
|
||||
],
|
||||
"matrix": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
-1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"mesh": 0
|
||||
}
|
||||
],
|
||||
"meshes": [
|
||||
{
|
||||
"primitives": [
|
||||
{
|
||||
"attributes": {
|
||||
"NORMAL": 1,
|
||||
"POSITION": 2
|
||||
},
|
||||
"indices": 0,
|
||||
"mode": 4,
|
||||
"material": 0
|
||||
}
|
||||
],
|
||||
"name": "Mesh"
|
||||
}
|
||||
],
|
||||
"accessors": [
|
||||
{
|
||||
"bufferView": 0,
|
||||
"byteOffset": 0,
|
||||
"componentType": 5123,
|
||||
"count": 36,
|
||||
"max": [
|
||||
23
|
||||
],
|
||||
"min": [
|
||||
0
|
||||
],
|
||||
"type": "SCALAR"
|
||||
},
|
||||
{
|
||||
"bufferView": 1,
|
||||
"byteOffset": 0,
|
||||
"componentType": 5126,
|
||||
"count": 24,
|
||||
"max": [
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"min": [
|
||||
-1.0,
|
||||
-1.0,
|
||||
-1.0
|
||||
],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 1,
|
||||
"byteOffset": 288,
|
||||
"componentType": 5126,
|
||||
"count": 24,
|
||||
"max": [
|
||||
0.5,
|
||||
0.5,
|
||||
0.5
|
||||
],
|
||||
"min": [
|
||||
-0.5,
|
||||
-0.5,
|
||||
-0.5
|
||||
],
|
||||
"type": "VEC3"
|
||||
}
|
||||
],
|
||||
"materials": [
|
||||
{
|
||||
"pbrMetallicRoughness": {
|
||||
"baseColorFactor": [
|
||||
0.800000011920929,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"metallicFactor": 0.0
|
||||
},
|
||||
"name": "Red"
|
||||
}
|
||||
],
|
||||
"bufferViews": [
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteOffset": 576,
|
||||
"byteLength": 72,
|
||||
"target": 34963
|
||||
},
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteOffset": 0,
|
||||
"byteLength": 576,
|
||||
"byteStride": 12,
|
||||
"target": 34962
|
||||
}
|
||||
],
|
||||
"buffers": [
|
||||
{
|
||||
"byteLength": 648,
|
||||
"uri": "Box0.bin"
|
||||
}
|
||||
]
|
||||
}
|
BIN
jme3-testdata/src/main/resources/Models/gltf/box/Box0.bin
Normal file
BIN
jme3-testdata/src/main/resources/Models/gltf/box/Box0.bin
Normal file
Binary file not shown.
142
jme3-testdata/src/main/resources/Models/gltf/box/box.gltf
Normal file
142
jme3-testdata/src/main/resources/Models/gltf/box/box.gltf
Normal file
@ -0,0 +1,142 @@
|
||||
{
|
||||
"asset": {
|
||||
"generator": "COLLADA2GLTF",
|
||||
"version": "2.0"
|
||||
},
|
||||
"scene": 0,
|
||||
"scenes": [
|
||||
{
|
||||
"nodes": [
|
||||
0
|
||||
]
|
||||
}
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"children": [
|
||||
1
|
||||
],
|
||||
"matrix": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
-1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"mesh": 0
|
||||
}
|
||||
],
|
||||
"meshes": [
|
||||
{
|
||||
"primitives": [
|
||||
{
|
||||
"attributes": {
|
||||
"NORMAL": 1,
|
||||
"POSITION": 2
|
||||
},
|
||||
"indices": 0,
|
||||
"mode": 4,
|
||||
"material": 0
|
||||
}
|
||||
],
|
||||
"name": "Mesh"
|
||||
}
|
||||
],
|
||||
"accessors": [
|
||||
{
|
||||
"bufferView": 0,
|
||||
"byteOffset": 0,
|
||||
"componentType": 5123,
|
||||
"count": 36,
|
||||
"max": [
|
||||
23
|
||||
],
|
||||
"min": [
|
||||
0
|
||||
],
|
||||
"type": "SCALAR"
|
||||
},
|
||||
{
|
||||
"bufferView": 1,
|
||||
"byteOffset": 0,
|
||||
"componentType": 5126,
|
||||
"count": 24,
|
||||
"max": [
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"min": [
|
||||
-1.0,
|
||||
-1.0,
|
||||
-1.0
|
||||
],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 1,
|
||||
"byteOffset": 288,
|
||||
"componentType": 5126,
|
||||
"count": 24,
|
||||
"max": [
|
||||
0.5,
|
||||
0.5,
|
||||
0.5
|
||||
],
|
||||
"min": [
|
||||
-0.5,
|
||||
-0.5,
|
||||
-0.5
|
||||
],
|
||||
"type": "VEC3"
|
||||
}
|
||||
],
|
||||
"materials": [
|
||||
{
|
||||
"pbrMetallicRoughness": {
|
||||
"baseColorFactor": [
|
||||
0.800000011920929,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"metallicFactor": 0.0
|
||||
},
|
||||
"name": "Red"
|
||||
}
|
||||
],
|
||||
"bufferViews": [
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteOffset": 576,
|
||||
"byteLength": 72,
|
||||
"target": 34963
|
||||
},
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteOffset": 0,
|
||||
"byteLength": 576,
|
||||
"byteStride": 12,
|
||||
"target": 34962
|
||||
}
|
||||
],
|
||||
"buffers": [
|
||||
{
|
||||
"byteLength": 648,
|
||||
"uri": "Box0.bin"
|
||||
}
|
||||
]
|
||||
}
|
219
jme3-testdata/src/main/resources/Models/gltf/duck/Duck.gltf
Normal file
219
jme3-testdata/src/main/resources/Models/gltf/duck/Duck.gltf
Normal file
@ -0,0 +1,219 @@
|
||||
{
|
||||
"asset": {
|
||||
"generator": "COLLADA2GLTF",
|
||||
"version": "2.0"
|
||||
},
|
||||
"scene": 0,
|
||||
"scenes": [
|
||||
{
|
||||
"nodes": [
|
||||
0
|
||||
]
|
||||
}
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"children": [
|
||||
2,
|
||||
1
|
||||
],
|
||||
"matrix": [
|
||||
0.009999999776482582,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.009999999776482582,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.009999999776482582,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"matrix": [
|
||||
-0.7289686799049377,
|
||||
0.0,
|
||||
-0.6845470666885376,
|
||||
0.0,
|
||||
-0.4252049028873444,
|
||||
0.7836934328079224,
|
||||
0.4527972936630249,
|
||||
0.0,
|
||||
0.5364750623703003,
|
||||
0.6211478114128113,
|
||||
-0.571287989616394,
|
||||
0.0,
|
||||
400.1130065917969,
|
||||
463.2640075683594,
|
||||
-431.0780334472656,
|
||||
1.0
|
||||
],
|
||||
"camera": 0
|
||||
},
|
||||
{
|
||||
"mesh": 0
|
||||
}
|
||||
],
|
||||
"cameras": [
|
||||
{
|
||||
"perspective": {
|
||||
"aspectRatio": 1.5,
|
||||
"yfov": 0.6605925559997559,
|
||||
"zfar": 10000.0,
|
||||
"znear": 1.0
|
||||
},
|
||||
"type": "perspective"
|
||||
}
|
||||
],
|
||||
"meshes": [
|
||||
{
|
||||
"primitives": [
|
||||
{
|
||||
"attributes": {
|
||||
"NORMAL": 1,
|
||||
"POSITION": 2,
|
||||
"TEXCOORD_0": 3
|
||||
},
|
||||
"indices": 0,
|
||||
"mode": 4,
|
||||
"material": 0
|
||||
}
|
||||
],
|
||||
"name": "LOD3spShape"
|
||||
}
|
||||
],
|
||||
"accessors": [
|
||||
{
|
||||
"bufferView": 0,
|
||||
"byteOffset": 0,
|
||||
"componentType": 5123,
|
||||
"count": 12636,
|
||||
"max": [
|
||||
2398
|
||||
],
|
||||
"min": [
|
||||
0
|
||||
],
|
||||
"type": "SCALAR"
|
||||
},
|
||||
{
|
||||
"bufferView": 1,
|
||||
"byteOffset": 0,
|
||||
"componentType": 5126,
|
||||
"count": 2399,
|
||||
"max": [
|
||||
0.9995989799499512,
|
||||
0.999580979347229,
|
||||
0.9984359741210938
|
||||
],
|
||||
"min": [
|
||||
-0.9990839958190918,
|
||||
-1.0,
|
||||
-0.9998319745063782
|
||||
],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 1,
|
||||
"byteOffset": 28788,
|
||||
"componentType": 5126,
|
||||
"count": 2399,
|
||||
"max": [
|
||||
96.17990112304688,
|
||||
163.97000122070313,
|
||||
53.92519760131836
|
||||
],
|
||||
"min": [
|
||||
-69.29850006103516,
|
||||
9.929369926452637,
|
||||
-61.32819747924805
|
||||
],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 2,
|
||||
"byteOffset": 0,
|
||||
"componentType": 5126,
|
||||
"count": 2399,
|
||||
"max": [
|
||||
0.9833459854125976,
|
||||
0.9800369739532472
|
||||
],
|
||||
"min": [
|
||||
0.026409000158309938,
|
||||
0.01996302604675293
|
||||
],
|
||||
"type": "VEC2"
|
||||
}
|
||||
],
|
||||
"materials": [
|
||||
{
|
||||
"pbrMetallicRoughness": {
|
||||
"baseColorTexture": {
|
||||
"index": 0
|
||||
},
|
||||
"metallicFactor": 0.0
|
||||
},
|
||||
"emissiveFactor": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"name": "blinn3-fx"
|
||||
}
|
||||
],
|
||||
"textures": [
|
||||
{
|
||||
"sampler": 0,
|
||||
"source": 0
|
||||
}
|
||||
],
|
||||
"images": [
|
||||
{
|
||||
"uri": "DuckCM.png"
|
||||
}
|
||||
],
|
||||
"samplers": [
|
||||
{
|
||||
"magFilter": 9729,
|
||||
"minFilter": 9986,
|
||||
"wrapS": 10497,
|
||||
"wrapT": 10497
|
||||
}
|
||||
],
|
||||
"bufferViews": [
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteOffset": 76768,
|
||||
"byteLength": 25272,
|
||||
"target": 34963
|
||||
},
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteOffset": 0,
|
||||
"byteLength": 57576,
|
||||
"byteStride": 12,
|
||||
"target": 34962
|
||||
},
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteOffset": 57576,
|
||||
"byteLength": 19192,
|
||||
"byteStride": 8,
|
||||
"target": 34962
|
||||
}
|
||||
],
|
||||
"buffers": [
|
||||
{
|
||||
"byteLength": 102040,
|
||||
"uri": "Duck0.bin"
|
||||
}
|
||||
]
|
||||
}
|
BIN
jme3-testdata/src/main/resources/Models/gltf/duck/Duck0.bin
Normal file
BIN
jme3-testdata/src/main/resources/Models/gltf/duck/Duck0.bin
Normal file
Binary file not shown.
BIN
jme3-testdata/src/main/resources/Models/gltf/duck/DuckCM.png
Normal file
BIN
jme3-testdata/src/main/resources/Models/gltf/duck/DuckCM.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
Loading…
x
Reference in New Issue
Block a user