Просмотр исходного кода

Merge pull request #2553 from riccardobl/gltfimp

Improve memory efficiency and speed of gltf importer
Ryan McDonough 5 дней назад
Родитель
Сommit
169f395055

+ 72 - 0
jme3-core/src/main/java/com/jme3/util/BufferInputStream.java

@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2009-2025 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.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+public class BufferInputStream extends InputStream {
+
+    ByteBuffer input;
+
+    public BufferInputStream(ByteBuffer input) {
+        this.input = input;
+    }
+
+    @Override
+    public int read() throws IOException {
+        if (input.remaining() == 0) return -1; else return input.get() & 0xff;
+    }
+
+    @Override
+    public int read(byte[] b) {
+        return read(b, 0, b.length);
+    }
+
+    @Override
+    public int read(byte[] b, int off, int len) {
+        if (b == null) throw new NullPointerException("b == null");
+        if (off < 0 || len < 0 || len > b.length - off) throw new IndexOutOfBoundsException();
+        if (len == 0) return 0;
+        if (!input.hasRemaining()) return -1;
+
+        int toRead = Math.min(len, input.remaining());
+        input.get(b, off, toRead);
+        return toRead;
+    }
+
+    @Override
+    public int available() {
+        return input.remaining();
+    }
+}

+ 13 - 2
jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java

@@ -31,11 +31,11 @@
  */
 package jme3test.model;
 
-import com.jme3.anim.AnimClip;
 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.input.KeyInput;
 import com.jme3.input.controls.ActionListener;
 import com.jme3.input.controls.KeyTrigger;
@@ -82,6 +82,7 @@ public class TestGltfLoading extends SimpleApplication {
 
         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);
 
         // cam.setLocation(new Vector3f(4.0339394f, 2.645184f, 6.4627485f));
         // cam.setRotation(new Quaternion(-0.013950467f, 0.98604023f, -0.119502485f, -0.11510504f));
@@ -152,7 +153,14 @@ public class TestGltfLoading extends SimpleApplication {
 
 //        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);
+
         probeNode.attachChild(assets.get(0));
 
         ChaseCameraAppState chaseCam = new ChaseCameraAppState();
@@ -231,7 +239,10 @@ public class TestGltfLoading extends SimpleApplication {
     private void loadModel(String path, Vector3f offset, Vector3f scale) {
         GltfModelKey k = new GltfModelKey(path);
         //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);

+ 9 - 7
jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GlbLoader.java

@@ -32,9 +32,11 @@
 package com.jme3.scene.plugins.gltf;
 
 import com.jme3.asset.AssetInfo;
+import com.jme3.util.BufferUtils;
 import com.jme3.util.LittleEndien;
 
 import java.io.*;
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -50,12 +52,12 @@ public class GlbLoader extends GltfLoader {
      */
     private static final Logger logger = Logger.getLogger(GlbLoader.class.getName());
 
-    private ArrayList<byte[]> data = new ArrayList<>();
+    private ArrayList<ByteBuffer> data = new ArrayList<>();
 
     @Override
     public Object load(AssetInfo assetInfo) throws IOException {
         data.clear();
-        LittleEndien stream = new LittleEndien(new DataInputStream(assetInfo.openStream()));
+        LittleEndien stream = new LittleEndien(new BufferedInputStream(assetInfo.openStream()));
         /* magic */ stream.readInt();
 
         int version = stream.readInt();
@@ -76,11 +78,11 @@ public class GlbLoader extends GltfLoader {
             int chunkType = stream.readInt();
             if (chunkType == JSON_TYPE) {
                 json = new byte[chunkLength];
-                stream.read(json);
+                GltfUtils.readToByteArray(stream, json, chunkLength);
             } else {
-                byte[] bin = new byte[chunkLength];
-                stream.read(bin);
-                data.add(bin);
+                ByteBuffer buff = BufferUtils.createByteBuffer(chunkLength);
+                GltfUtils.readToByteBuffer(stream, buff, chunkLength);
+                data.add(buff);
             }
             //8 is the byte size of the 2 ints chunkLength and chunkType.
             length -= chunkLength + 8;
@@ -93,7 +95,7 @@ public class GlbLoader extends GltfLoader {
     }
 
     @Override
-    protected byte[] getBytes(int bufferIndex, String uri, Integer bufferLength) throws IOException {
+    protected ByteBuffer getBytes(int bufferIndex, String uri, Integer bufferLength) throws IOException {
         return data.get(bufferIndex);
     }
 

+ 77 - 19
jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java

@@ -48,11 +48,14 @@ 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;
@@ -109,7 +112,6 @@ public class GltfLoader implements AssetLoader {
 
     protected Object loadFromStream(AssetInfo assetInfo, InputStream stream) throws IOException {
         try {
-            dataCache.clear();
             info = assetInfo;
             skinnedSpatials.clear();
             rootNode = new Node();
@@ -181,6 +183,27 @@ public class GltfLoader implements AssetLoader {
             throw new AssetLoadException("An error occurred loading " + assetInfo.getKey().getName(), e);
         } finally {
             stream.close();
+            dataCache.clear();
+            skinBuffers.clear();
+            skinnedSpatials.clear();
+            info = null;
+            docRoot = null;
+            rootNode = null;
+            defaultMat = null;
+            accessors = null;
+            bufferViews = null;
+            buffers = null;
+            scenes = null;
+            nodes = null;
+            meshes = null;
+            materials = null;
+            textures = null;
+            images = null;
+            samplers = null;
+            animations = null;
+            skins = null;
+            cameras = null;
+            useNormalsFlag = false;
         }
     }
 
@@ -553,11 +576,15 @@ public class GltfLoader implements AssetLoader {
         // 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);
+        ByteBuffer data = readData(bufferIndex);
         data = customContentManager.readExtensionAndExtras("bufferView", bufferView, data);
 
+        if(!(data instanceof ByteBuffer)){
+            throw new IOException("Buffer data is not a NIO Buffer");
+        }
+
         if (store == null) {
-            store = new byte[byteLength];
+            store = BufferUtils.createByteBuffer(byteLength);
         }
 
         if (count == -1) {
@@ -569,14 +596,40 @@ public class GltfLoader implements AssetLoader {
         return store;
     }
 
-    public byte[] readData(int bufferIndex) 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);
+        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);
+
+        ByteBuffer data = readData(bufferIndex);
+        data = customContentManager.readExtensionAndExtras("bufferView", bufferView, data);
+
+        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 );
+
+    }
+
+    public ByteBuffer readData(int bufferIndex) throws IOException {
         assertNotNull(buffers, "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);
-        byte[] data = (byte[]) fetchFromCache("buffers", bufferIndex, Object.class);
+        ByteBuffer data = (ByteBuffer) fetchFromCache("buffers", bufferIndex, Object.class);
         if (data != null) {
             return data;
         }
@@ -588,12 +641,12 @@ public class GltfLoader implements AssetLoader {
         return data;
     }
 
-    protected byte[] getBytes(int bufferIndex, String uri, Integer bufferLength) throws IOException {
-        byte[] data;
+    protected ByteBuffer getBytes(int bufferIndex, String uri, Integer bufferLength) throws IOException {
+        ByteBuffer data;
         if (uri != null) {
             if (uri.startsWith("data:")) {
                 // base 64 embed data
-                data = 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);
@@ -603,11 +656,11 @@ public class GltfLoader implements AssetLoader {
                 }
 
                 BinDataKey key = new BinDataKey(info.getKey().getFolder() + decoded);
-                InputStream input = (InputStream) info.getManager().loadAsset(key);
-                data = new byte[bufferLength];
-                try (DataInputStream dataStream = new DataInputStream(input)) {
-                    dataStream.readFully(data);
+                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.
@@ -784,19 +837,23 @@ public class GltfLoader implements AssetLoader {
         if (uri == null) {
             assertNotNull(bufferView, "Image " + sourceIndex + " should either have an uri or a bufferView");
             assertNotNull(mimeType, "Image " + sourceIndex + " should have a mimeType");
-            byte[] data = (byte[]) readBuffer(bufferView, 0, -1, null, 1, 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);
-            result = (Texture2D) info.getManager().loadAssetFromStream(key, new ByteArrayInputStream(data));
-
+            try(BufferedInputStream bis = new BufferedInputStream(new BufferInputStream(data))){
+                result = (Texture2D) info.getManager().loadAssetFromStream(key, bis);
+            }
         } else if (uri.startsWith("data:")) {
             // base64 encoded image
             String[] uriInfo = uri.split(",");
-            byte[] data = Base64.getDecoder().decode(uriInfo[1]);
+            ByteBuffer data = BufferUtils.createByteBuffer(Base64.getDecoder().decode(uriInfo[1]));
             String headerInfo = uriInfo[0].split(";")[0];
             String extension = headerInfo.split("/")[1];
             TextureKey key = new TextureKey("image" + sourceIndex + "." + extension, flip);
-            result = (Texture2D) info.getManager().loadAssetFromStream(key, new ByteArrayInputStream(data));
+            try(BufferedInputStream bis = new BufferedInputStream(new BufferInputStream(data))){
+                result = (Texture2D) info.getManager().loadAssetFromStream(key, bis);
+            }
         } else {
             // external file image
             String decoded = decodeUri(uri);
@@ -1338,13 +1395,14 @@ public class GltfLoader implements AssetLoader {
             }
             int numComponents = getNumberOfComponents(type);
 
-            Buffer buff = VertexBuffer.createBuffer(format, numComponents, count);
             int bufferSize = numComponents * count;
+            Buffer buff;
             if (bufferViewIndex == null) {
+                buff = VertexBuffer.createBuffer(format, numComponents, count);
                 // no referenced buffer, specs says to pad the buffer with zeros.
                 padBuffer(buff, bufferSize);
             } else {
-                readBuffer(bufferViewIndex, byteOffset, count, buff, numComponents, originalFormat);
+                buff = (Buffer) viewBuffer(bufferViewIndex, byteOffset, count, numComponents, originalFormat, format);
             }
 
             if (bufferType == VertexBuffer.Type.Index) {

+ 308 - 132
jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java

@@ -44,6 +44,8 @@ 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;
@@ -60,7 +62,11 @@ public class GltfUtils {
      */
     private GltfUtils() {
     }
-    
+
+    public static ByteBuffer asReadableByteBuffer(ByteBuffer bbf){
+        return bbf.slice().order(ByteOrder.LITTLE_ENDIAN);
+    }
+
     /**
      * Parse a json input stream and returns a {@link JsonObject}
      * @param stream the stream to parse
@@ -227,62 +233,68 @@ public class GltfUtils {
         }
     }
 
-    public static void padBuffer(Object store, int bufferSize) {
-        if (store instanceof Buffer) {
-            Buffer buffer = (Buffer) store;
-            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();
-        }
-        if (store instanceof short[]) {
-            short[] array = (short[]) store;
-            for (int i = 0; i < array.length; i++) {
-                array[i] = 0;
-            }
-        } else if (store instanceof float[]) {
-            float[] array = (float[]) store;
-            for (int i = 0; i < array.length; i++) {
-                array[i] = 0;
+    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 (store instanceof Vector3f[]) {
-            Vector3f[] array = (Vector3f[]) store;
-            for (int i = 0; i < array.length; i++) {
-                array[i] = new Vector3f();
+        } else if (buffer instanceof FloatBuffer) {
+            FloatBuffer fb = (FloatBuffer) buffer;
+            for (int i = 0; i < bufferSize; i++) {
+                fb.put(0);
             }
-        } else if (store instanceof Quaternion[]) {
-            Quaternion[] array = (Quaternion[]) store;
-            for (int i = 0; i < array.length; i++) {
-                array[i] = new Quaternion();
+        } else if (buffer instanceof ShortBuffer) {
+            ShortBuffer sb = (ShortBuffer) buffer;
+            for (int i = 0; i < bufferSize; i++) {
+                sb.put((short) 0);
             }
-        } else if (store instanceof Matrix4f[]) {
-            Matrix4f[] array = (Matrix4f[]) store;
-            for (int i = 0; i < array.length; i++) {
-                array[i] = new Matrix4f();
+        } 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 padBuffer(float[] array, int bufferSize) {
+        for (int i = 0; i < bufferSize; i++) {
+            array[i] = 0;
+        }
     }
 
-    public static void populateBuffer(Object store, byte[] source, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException {
+    public static void padBuffer(short[] array, int bufferSize) {
+        for (int i = 0; i < bufferSize; i++) {
+            array[i] = 0;
+        }
+    }
+    
+    public static void padBuffer(Vector3f[] array, int bufferSize) {
+        for (int i = 0; i < bufferSize; i++) {
+            array[i] = new Vector3f();
+        }
+    }
+
+    public static void padBuffer(Quaternion[] array, int bufferSize) {
+        for (int i = 0; i < bufferSize; i++) {
+            array[i] = new Quaternion();
+        }      
+    }
+
+    public static void padBuffer(Matrix4f[] array, int bufferSize) {
+        for (int i = 0; i < bufferSize; i++) {
+            array[i] = new Matrix4f();
+        }        
+    }
+
+
+
+
+
+    public static void populateBuffer(Object store, ByteBuffer source, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException {
+        source = asReadableByteBuffer(source);
 
         if (store instanceof Buffer) {
             Buffer buffer = (Buffer) store;
@@ -291,34 +303,37 @@ public class GltfUtils {
                 populateByteBuffer((ByteBuffer) buffer, source, count, byteOffset, byteStride, numComponents, format);
                 return;
             }
-            LittleEndien stream = getStream(source);
             if (buffer instanceof ShortBuffer) {
-                populateShortBuffer((ShortBuffer) buffer, stream, count, byteOffset, byteStride, numComponents, format);
+                populateShortBuffer((ShortBuffer) buffer, source, count, byteOffset, byteStride, numComponents, format);
             } else if (buffer instanceof IntBuffer) {
-                populateIntBuffer((IntBuffer) buffer, stream, count, byteOffset, byteStride, numComponents, format);
+                populateIntBuffer((IntBuffer) buffer, source, count, byteOffset, byteStride, numComponents, format);
             } else if (buffer instanceof FloatBuffer) {
-                populateFloatBuffer((FloatBuffer) buffer, stream, count, byteOffset, byteStride, numComponents, format);
+                populateFloatBuffer((FloatBuffer) buffer, source, count, byteOffset, byteStride, numComponents, format);
             }
             buffer.rewind();
             return;
         }
-        LittleEndien stream = getStream(source);
+
         if (store instanceof byte[]) {
-            populateByteArray((byte[]) store, stream, count, byteOffset, byteStride, numComponents, format);
+            populateByteArray((byte[]) store, source, count, byteOffset, byteStride, numComponents, format);
         } else if (store instanceof short[]) {
-            populateShortArray((short[]) store, stream, count, byteOffset, byteStride, numComponents, format);
+            populateShortArray((short[]) store, source, count, byteOffset, byteStride, numComponents, format);
         } else if (store instanceof float[]) {
-            populateFloatArray((float[]) store, stream, count, byteOffset, byteStride, numComponents, format);
+            populateFloatArray((float[]) store, source, count, byteOffset, byteStride, numComponents, format);
         } else if (store instanceof Vector3f[]) {
-            populateVector3fArray((Vector3f[]) store, stream, count, byteOffset, byteStride, numComponents, format);
+            populateVector3fArray((Vector3f[]) store, source, count, byteOffset, byteStride, numComponents, format);
         } else if (store instanceof Quaternion[]) {
-            populateQuaternionArray((Quaternion[]) store, stream, count, byteOffset, byteStride, numComponents, format);
+            populateQuaternionArray((Quaternion[]) store, source, count, byteOffset, byteStride, numComponents, format);
         } else if (store instanceof Matrix4f[]) {
-            populateMatrix4fArray((Matrix4f[]) store, stream, count, byteOffset, byteStride, numComponents, format);
+            populateMatrix4fArray((Matrix4f[]) store, source, count, byteOffset, byteStride, numComponents, format);
         }
     }
 
-    private static void populateByteBuffer(ByteBuffer buffer, byte[] source, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) {
+    private static void skip(ByteBuffer buff, int n) {
+        buff.position(Math.min(buff.position() + n, buff.limit()));
+    }
+
+    private static void populateByteBuffer(ByteBuffer buffer, ByteBuffer source, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) {
         int componentSize = format.getComponentSize();
         int index = byteOffset;
         int dataLength = componentSize * numComponents;
@@ -326,69 +341,69 @@ public class GltfUtils {
         int end = count * stride + byteOffset;
         while (index < end) {
             for (int i = 0; i < numComponents; i++) {
-                buffer.put(source[index + i]);
+                buffer.put(source.get(index + i));
             }
             index += stride;
         }
     }
 
-    private static void populateShortBuffer(ShortBuffer buffer, LittleEndien stream, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException {
+    private static void populateShortBuffer(ShortBuffer buffer, ByteBuffer source, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException {
         int componentSize = format.getComponentSize();
         int index = byteOffset;
         int dataLength = componentSize * numComponents;
         int stride = Math.max(dataLength, byteStride);
-        int end = count * stride + byteOffset;
-        stream.skipBytes(byteOffset);
+        int end = count * stride + byteOffset;        
+        source.position(source.position() + byteOffset);
         while (index < end) {
             for (int i = 0; i < numComponents; i++) {
-                buffer.put(stream.readShort());
+                buffer.put(source.getShort());
             }
 
             if (dataLength < stride) {
-                stream.skipBytes(stride - dataLength);
+                skip(source, stride - dataLength);
             }
             index += stride;
         }
     }
 
 
-    private static void populateIntBuffer(IntBuffer buffer, LittleEndien stream, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException {
+    private static void populateIntBuffer(IntBuffer buffer, ByteBuffer source, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException {
         int componentSize = format.getComponentSize();
         int index = byteOffset;
         int dataLength = componentSize * numComponents;
         int stride = Math.max(dataLength, byteStride);
         int end = count * stride + byteOffset;
-        stream.skipBytes(byteOffset);
+        source.position(source.position() + byteOffset);
         while (index < end) {
             for (int i = 0; i < numComponents; i++) {
-                buffer.put(stream.readInt());
+                buffer.put(source.getInt());
             }
             if (dataLength < stride) {
-                stream.skipBytes(stride - dataLength);
+                skip(source, stride - dataLength);
             }
             index += stride;
         }
     }
 
-    private static void populateFloatBuffer(FloatBuffer buffer, LittleEndien stream, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException {
+    private static void populateFloatBuffer(FloatBuffer buffer, ByteBuffer source, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException {
         int componentSize = format.getComponentSize();
         int index = byteOffset;
         int dataLength = componentSize * numComponents;
         int stride = Math.max(dataLength, byteStride);
         int end = count * stride + byteOffset;
-        stream.skipBytes(byteOffset);
+        source.position(source.position() + byteOffset);
         while (index < end) {
             for (int i = 0; i < numComponents; i++) {
-                buffer.put(readAsFloat(stream, format));
+                buffer.put(readAsFloat(source, format));
             }
             if (dataLength < stride) {
-                stream.skipBytes(stride - dataLength);
+                skip(source, stride - dataLength);
             }
             index += stride;
         }
     }
 
-    public static float readAsFloat(LittleEndien stream, VertexBuffer.Format format) throws IOException {
+    public static float readAsFloat(ByteBuffer source, VertexBuffer.Format format) throws IOException {
         //We may have packed data so depending on the format, we need to read data differently and unpack it
         // Implementations must use following equations to get corresponding floating-point value f from a normalized integer c and vise-versa:
         // accessor.componentType    int-to-float                float-to-int
@@ -399,34 +414,34 @@ public class GltfUtils {
         int c;
         switch (format) {
             case Byte:
-                c = stream.readByte();
+                c = source.get();
                 return Math.max(c / 127f, -1f);
             case UnsignedByte:
-                c = stream.readUnsignedByte();
+                c = source.get() & 0xFF;
                 return c / 255f;
             case Short:
-                c = stream.readShort();
+                c = source.getShort();
                 return Math.max(c / 32767f, -1f);
-            case UnsignedShort:
-                c = stream.readUnsignedShort();
+            case UnsignedShort:               
+                c = source.get() & 0xff | (source.get() & 0xff) << 8;
                 return c / 65535f;
             default:
                 //we have a regular float
-                return stream.readFloat();
+                return source.getFloat();
         }
 
     }
 
-    private static void populateByteArray(byte[] array, LittleEndien stream, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException {
+    private static void populateByteArray(byte[] array, ByteBuffer source, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException {
         int componentSize = format.getComponentSize();
         int index = byteOffset;
         int dataLength = componentSize * numComponents;
         int stride = Math.max(dataLength, byteStride);
         int end = count * stride + byteOffset;
-        stream.skipBytes(byteOffset);
+        source.position(source.position() + byteOffset);
 
         if (dataLength == stride) {
-            read(stream, array, end - index);
+            read(source, array, end - index);
 
             return;
         }
@@ -434,20 +449,21 @@ public class GltfUtils {
         int arrayIndex = 0;
         byte[] buffer = new byte[numComponents];
         while (index < end) {
-            read(stream, buffer, numComponents);
+            read(source, buffer, numComponents);
             System.arraycopy(buffer, 0, array, arrayIndex, numComponents);
             arrayIndex += numComponents;
             if (dataLength < stride) {
-                stream.skipBytes(stride - dataLength);
+                skip(source, stride - dataLength);
             }
             index += stride;
         }
     }
 
-    private static void read(LittleEndien stream, byte[] buffer, int length) throws IOException {
+    private static void read(ByteBuffer source, byte[] buffer, int length) throws IOException {
         int n = 0;
         while (n < length) {
-            int cnt = stream.read(buffer, n, length - n);
+            int cnt = Math.min(source.remaining(), length - n);
+            source.get(buffer, n, cnt);
             if (cnt < 0) {
                 throw new AssetLoadException("Data ended prematurely");
             }
@@ -455,25 +471,25 @@ public class GltfUtils {
         }
     }
 
-    private static void populateShortArray(short[] array, LittleEndien stream, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException {
+    private static void populateShortArray(short[] array, ByteBuffer source, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException {
         int componentSize = format.getComponentSize();
         int index = byteOffset;
         int dataLength = componentSize * numComponents;
         int stride = Math.max(dataLength, byteStride);
         int end = count * stride + byteOffset;
-        stream.skipBytes(byteOffset);
+        source.position(source.position() + byteOffset);
         int arrayIndex = 0;
         while (index < end) {
             for (int i = 0; i < numComponents; i++) {
                 if (componentSize == 2) {
-                    array[arrayIndex] = stream.readShort();
+                    array[arrayIndex] = source.getShort();
                 } else {
-                    array[arrayIndex] = stream.readByte();
+                    array[arrayIndex] = source.get();
                 }
                 arrayIndex++;
             }
             if (dataLength < stride) {
-                stream.skipBytes(stride - dataLength);
+                skip(source, stride - dataLength);
             }
             index += stride;
         }
@@ -557,107 +573,106 @@ public class GltfUtils {
         mesh.getBuffer(VertexBuffer.Type.BoneWeight).setUsage(VertexBuffer.Usage.CpuOnly);
     }
 
-    private static void populateFloatArray(float[] array, LittleEndien stream, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException {
+    private static void populateFloatArray(float[] array, ByteBuffer source, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException {
         int componentSize = format.getComponentSize();
         int index = byteOffset;
         int dataLength = componentSize * numComponents;
         int stride = Math.max(dataLength, byteStride);
         int end = count * stride + byteOffset;
-        stream.skipBytes(byteOffset);
+        source.position(source.position() + byteOffset);
         int arrayIndex = 0;
         while (index < end) {
             for (int i = 0; i < numComponents; i++) {
-                array[arrayIndex] = readAsFloat(stream, format);
+                array[arrayIndex] = readAsFloat(source, format);
                 arrayIndex++;
             }
             if (dataLength < stride) {
-                stream.skipBytes(stride - dataLength);
+                skip(source, stride - dataLength);
             }
             index += stride;
         }
     }
 
-    private static void populateVector3fArray(Vector3f[] array, LittleEndien stream, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException {
+    private static void populateVector3fArray(Vector3f[] array, ByteBuffer source, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException {
         int componentSize = format.getComponentSize();
         int index = byteOffset;
         int dataLength = componentSize * numComponents;
         int stride = Math.max(dataLength, byteStride);
         int end = count * stride + byteOffset;
-        stream.skipBytes(byteOffset);
+        source.position(source.position() + byteOffset);
         int arrayIndex = 0;
         while (index < end) {
             array[arrayIndex] = new Vector3f(
-                    readAsFloat(stream, format),
-                    readAsFloat(stream, format),
-                    readAsFloat(stream, format)
+                    readAsFloat(source, format),
+                    readAsFloat(source, format),
+                    readAsFloat(source, format)
             );
 
             arrayIndex++;
             if (dataLength < stride) {
-                stream.skipBytes(stride - dataLength);
+                skip(source, stride - dataLength);
             }
-
             index += stride;
         }
     }
 
-    private static void populateQuaternionArray(Quaternion[] array, LittleEndien stream, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException {
+    private static void populateQuaternionArray(Quaternion[] array, ByteBuffer source, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException {
         int componentSize = format.getComponentSize();
         int index = byteOffset;
         int dataLength = componentSize * numComponents;
         int stride = Math.max(dataLength, byteStride);
         int end = count * stride + byteOffset;
-        stream.skipBytes(byteOffset);
+        source.position(source.position() + byteOffset);
         int arrayIndex = 0;
         while (index < end) {
             array[arrayIndex] = new Quaternion(
-                    readAsFloat(stream, format),
-                    readAsFloat(stream, format),
-                    readAsFloat(stream, format),
-                    readAsFloat(stream, format)
+                    readAsFloat(source, format),
+                    readAsFloat(source, format),
+                    readAsFloat(source, format),
+                    readAsFloat(source, format)
             );
 
             arrayIndex++;
             if (dataLength < stride) {
-                stream.skipBytes(stride - dataLength);
+                skip(source, stride - dataLength);
             }
             index += stride;
         }
     }
 
-    private static void populateMatrix4fArray(Matrix4f[] array, LittleEndien stream, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException {
+    private static void populateMatrix4fArray(Matrix4f[] array, ByteBuffer source, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException {
         int componentSize = format.getComponentSize();
         int index = byteOffset;
         int dataLength = componentSize * numComponents;
         int stride = Math.max(dataLength, byteStride);
         int end = count * stride + byteOffset;
-        stream.skipBytes(byteOffset);
+        source.position(source.position() + byteOffset);
         int arrayIndex = 0;
         while (index < end) {
 
             array[arrayIndex] = toRowMajor(
-                    readAsFloat(stream, format),
-                    readAsFloat(stream, format),
-                    readAsFloat(stream, format),
-                    readAsFloat(stream, format),
-                    readAsFloat(stream, format),
-                    readAsFloat(stream, format),
-                    readAsFloat(stream, format),
-                    readAsFloat(stream, format),
-                    readAsFloat(stream, format),
-                    readAsFloat(stream, format),
-                    readAsFloat(stream, format),
-                    readAsFloat(stream, format),
-                    readAsFloat(stream, format),
-                    readAsFloat(stream, format),
-                    readAsFloat(stream, format),
-                    readAsFloat(stream, format)
+                    readAsFloat(source, format),
+                    readAsFloat(source, format),
+                    readAsFloat(source, format),
+                    readAsFloat(source, format),
+                    readAsFloat(source, format),
+                    readAsFloat(source, format),
+                    readAsFloat(source, format),
+                    readAsFloat(source, format),
+                    readAsFloat(source, format),
+                    readAsFloat(source, format),
+                    readAsFloat(source, format),
+                    readAsFloat(source, format),
+                    readAsFloat(source, format),
+                    readAsFloat(source, format),
+                    readAsFloat(source, format),
+                    readAsFloat(source, format)
             );
             //gltf matrix are column major, JME ones are row major.
 
             arrayIndex++;
             if (dataLength < stride) {
-                stream.skipBytes(stride - dataLength);
+                skip(source, stride - dataLength);
             }
 
             index += stride;
@@ -895,4 +910,165 @@ public class GltfUtils {
             System.err.println("\n---------------------------");
         }
     }
+
+    public static void readToByteBuffer(InputStream input, ByteBuffer dst, int bytesToRead) throws IOException {
+        if (bytesToRead <= 0) throw new IOException("bytesToRead must be > 0");
+
+        int startPos = dst.position();
+        int remaining = dst.limit() - startPos;
+        if (remaining < bytesToRead) {
+            throw new IOException("Destination ByteBuffer too small: remaining=" + remaining + " < bytesToRead=" + bytesToRead);
+        }
+    
+        ReadableByteChannel ch = Channels.newChannel(input); 
+        int total = 0;
+        while (total < bytesToRead) {
+            int n = ch.read(dst);
+            if (n == -1) break;
+            total += n;
+        }
+
+        if (total < bytesToRead) {
+            throw new IOException("Data ended prematurely " + total + " < " + bytesToRead);
+        }
+
+        dst.flip();
+    }
+
+    public static void readToByteArray(InputStream input, byte[] dst, int bytesToRead) throws IOException {
+        if (bytesToRead < 0) throw new IllegalArgumentException("bytesToRead < 0");
+        if (bytesToRead > dst.length) {
+            throw new IOException("Destination array too small: length=" + dst.length + " < bytesToRead=" + bytesToRead);
+        }
+
+        int totalRead = 0;
+        while (totalRead < bytesToRead) {
+            int n = input.read(dst, totalRead, bytesToRead - totalRead);
+            if (n == -1) break;
+            totalRead += n;
+        }
+
+        if (totalRead < bytesToRead) {
+            throw new IOException("Data ended prematurely " + totalRead + " < " + bytesToRead);
+        }
+    }
+
+
+    /**
+     * Try to expose a glTF buffer region as a typed NIO view without copying.
+     * Falls back to allocating a destination buffer and populating it when
+     * interleaving, normalization, or format mismatch prevents a pure view.
+     *
+     * @param source         the original ByteBuffer (direct or heap)
+     * @param count          number of elements
+     * @param byteOffset     start offset within source (relative to beginning)
+     * @param byteStride     stride in bytes (0 means tightly packed = element size)
+     * @param numComponents  components per element (e.g. 3 for VEC3)
+     * @param originalFormat the source component type  
+     * @param targetFormat   the desired buffer view type to return
+     */
+    public static Buffer getBufferView(ByteBuffer source, int byteOffset,  int count, int byteStride,
+                                       int numComponents, VertexBuffer.Format originalFormat,
+                                       VertexBuffer.Format targetFormat) throws IOException {
+        // Work in little-endian as per glTF spec
+        source = asReadableByteBuffer(source);  
+
+        // Layout from source format
+        int srcCompSize = originalFormat.getComponentSize();
+        int elemSize = srcCompSize * numComponents;
+        int stride = Math.max(elemSize, byteStride);
+        int start = byteOffset;
+        int bytes = stride * count;
+
+
+        boolean tightlyPacked = (stride == elemSize);
+
+        if (tightlyPacked) {
+            ByteBuffer view = source.duplicate();
+            view.position(start).limit(start + bytes);
+            view = view.slice().order(ByteOrder.LITTLE_ENDIAN);
+
+            // Zero-copy returns only when source/target formats are compatible and aligned
+            switch (targetFormat) {
+                case Byte:
+                case UnsignedByte:
+                    if (srcCompSize == 1 &&
+                        (originalFormat == VertexBuffer.Format.Byte ||
+                         originalFormat == VertexBuffer.Format.UnsignedByte)) {
+                        return view;
+                    }
+                    break;
+
+                case Short:
+                case UnsignedShort:
+                    if (srcCompSize == 2 &&
+                        (originalFormat == VertexBuffer.Format.Short ||
+                         originalFormat == VertexBuffer.Format.UnsignedShort) &&
+                        (start & 1) == 0) {
+                        return view.asShortBuffer();
+                    }
+                    break;
+
+                case Int:
+                case UnsignedInt:
+                    if (srcCompSize == 4 &&
+                        (originalFormat == VertexBuffer.Format.Int ||
+                         originalFormat == VertexBuffer.Format.UnsignedInt) &&
+                        (start & 3) == 0) {
+                        return view.asIntBuffer();
+                    }
+                    break;
+
+                case Float:
+                    if (srcCompSize == 4 &&
+                        originalFormat == VertexBuffer.Format.Float &&
+                        (start & 3) == 0) {
+                        return view.asFloatBuffer();
+                    }
+                    break;
+
+                case Double:
+                    if (srcCompSize == 8 &&
+                        originalFormat == VertexBuffer.Format.Double &&
+                        (start & 7) == 0) {
+                        return view.asDoubleBuffer();
+                    }
+                    break;
+            }
+        }
+
+        // Fallback: allocate destination buffer by desired targetFormat and populate from source
+        int elements = count * numComponents;
+        switch (targetFormat) {
+            case Byte:
+            case UnsignedByte: {
+                ByteBuffer out = BufferUtils.createByteBuffer(elements);
+                populateBuffer(out, source, count, byteOffset, byteStride, numComponents, originalFormat);
+                return out;
+            }
+            case Short:
+            case UnsignedShort: {
+                ShortBuffer out = BufferUtils.createShortBuffer(elements);
+                populateBuffer(out, source, count, byteOffset, byteStride, numComponents, originalFormat);
+                return out;
+            }
+            case Int:
+            case UnsignedInt: {
+                IntBuffer out = BufferUtils.createIntBuffer(elements);
+                populateBuffer(out, source, count, byteOffset, byteStride, numComponents, originalFormat);
+                return out;
+            }
+            case Float: {
+                FloatBuffer out = BufferUtils.createFloatBuffer(elements);
+                populateBuffer(out, source, count, byteOffset, byteStride, numComponents, originalFormat);
+                return out;
+            }
+            case Double:
+                throw new IllegalArgumentException("Double conversion fallback not supported");
+            default:
+                throw new IllegalArgumentException("Unsupported format " + targetFormat);
+        }
+    }
+
+
 }