Class GltfLoader
GltfModel: no AWT, no GPU. Handles
both .gltf (JSON, with base64 data: buffers) and .glb
(binary container). Reads the node hierarchy, mesh primitives
(POSITION/NORMAL/TEXCOORD_0 + indices), metallic-roughness materials and encoded
texture images. Buffers and images must be embedded, as data: URIs or the GLB
binary chunk: a reference to an external file is refused. Skinning and animation are
not read; nodes are placed by their static transforms. Images are kept encoded here;
decoding them is the separate, off-thread-able step GltfModel.decodeTextures().
Every entry point reads and parses on the thread that calls it. The ...Async ones put
that on the Ui worker pool and deliver on the UI thread, which is what an application
loading a model behind a live window wants; the synchronous ones stay for setup code and tests.
One load creates at most 226 array elements by default, a quarter of a
gigabyte of floats, and a model that needs more is refused with an
MalformedContentException; setElementBudget(long) moves that ceiling for the whole
process. What the default holds depends on whether the model carries its normals. A mesh that does costs six floats a
vertex and one index a triangle corner, so an indexed one of the usual shape, with about half as
many vertices as triangles, fits up to some eleven million triangles. A mesh without normals is
given flat ones, which takes a vertex of its own for every corner: three floats of position,
three of normal, two more with texture coordinates and a new index, on top of the arrays read
from the file. The same indexed mesh then fits up to about 2.6 million triangles, and one with
no index list, as a scan converted from STL often is, about two million. Exporting the normals
with the model is what lets a larger one load.
-
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final longThe most array elements one load creates unlesssetElementBudget(long)says otherwise, over every accessor, index list, converted strip and generated normal together: 226, a quarter of a gigabyte of floats. -
Method Summary
Modifier and TypeMethodDescriptionstatic longThe ceiling on the array elements one load may create:DEFAULT_ELEMENT_BUDGETuntilsetElementBudget(long)moves it.static GltfModelfromResource(String resource) Reads a classpath resource whole and parses it (.gltfor.glb), on the calling thread: the shape for a model shipped inside the application jar.fromResourceAsync(String resource) Reads and parses a classpath resource on theUiworker pool, delivering the model on the UI thread: the asynchronous form offromResource(String), and what an application shipping models in its jar should call.static GltfModelload(byte[] data) Parses a model already in memory, on the calling thread.static GltfModelReadsfilewhole and parses it (.gltfor.glb), on the calling thread.loadAsync(byte[] data) Parses bytes already in memory on theUiworker pool, delivering the model on the UI thread: the asynchronous form ofload(byte[]).Reads and parsesfileon theUiworker pool, delivering the model toonSuccesson the UI thread: the asynchronous form ofload(Path).static voidsetElementBudget(long elements) Moves the ceiling on the array elements one load may create, for every load in the process from the next one on; a load already running keeps the ceiling it started with.
-
Field Details
-
DEFAULT_ELEMENT_BUDGET
public static final long DEFAULT_ELEMENT_BUDGETThe most array elements one load creates unlesssetElementBudget(long)says otherwise, over every accessor, index list, converted strip and generated normal together: 226, a quarter of a gigabyte of floats. Each accessor is bounded by the bytes behind it, but primitives may name the same accessor again and again: a 168 KB file whose two thousand strips all reused one 64 KB buffer asked for more than two gigabytes. What that holds, in triangles, is in the class documentation.- See Also:
-
-
Method Details
-
setElementBudget
public static void setElementBudget(long elements) Moves the ceiling on the array elements one load may create, for every load in the process from the next one on; a load already running keeps the ceiling it started with. Raise it for an application whose own models are large and trusted: 227 takes a mesh without normals of about five million triangles, at twice the memory a hostile file can make a load ask for before it is refused. Lower it where models come from strangers.- Parameters:
elements- the most elements one load creates, at least 1- Throws:
IllegalArgumentException- ifelementsis less than 1
-
elementBudget
public static long elementBudget()The ceiling on the array elements one load may create:DEFAULT_ELEMENT_BUDGETuntilsetElementBudget(long)moves it.- Returns:
- the current budget, in elements
-
load
Readsfilewhole and parses it (.gltfor.glb), on the calling thread. This is the toolkit's largest blocking read: the file, a JSON parse of the document, a base64 decode of every embedded buffer, and a de-interleaving copy per accessor add up to hundreds of milliseconds for an ordinary model. That is a dropped second on the UI thread.loadAsync(Path)is the same work on the worker pool. -
fromResource
Reads a classpath resource whole and parses it (.gltfor.glb), on the calling thread: the shape for a model shipped inside the application jar. Seeload(Path)for the cost andfromResourceAsync(java.lang.String)for the same work on the worker pool.- Parameters:
resource- an absolute resource path, e.g."/app/models/robot.glb"- Throws:
IllegalStateException- if no such resource exists
-
load
Parses a model already in memory, on the calling thread. It is still a JSON parse, a base64 decode of every embedded buffer and a copy per accessor, so seeload(Path)for the cost andloadAsync(byte[])for the same work on the worker pool.- Throws:
MalformedContentException- if the document is malformed, whatever is wrong with it: text that is not JSON or ends early, a member of the wrong type or missing, an index past the end of the list it names, and one that declares more elements or bytes than the buffer behind them holds, which is refused before the array for them is allocated, so a small file cannot ask for an enormous heap. It is the only exception a malformed document is refused with
-
loadAsync
Reads and parsesfileon theUiworker pool, delivering the model toonSuccesson the UI thread: the asynchronous form ofload(Path).loading = GltfLoader.loadAsync(file) .onProgress(fraction -> bar.setProgress((float) fraction)) .onSuccess(model -> show(model)) .deliverIf(view::isShowing) .start();Returned unstarted: register handlers, then call
start(). Nothing is read until then, and a job cancelled while it runs stops between meshes rather than parsing the rest. No disposer is registered and none is needed: aGltfModelis plain arrays, so an undelivered one is collected like any other object.Progress runs 0 to 1 across the read, the JSON parse, the buffers and the meshes, weighted by where the time actually goes rather than by member count, so it is monotonic but not linear in time.
The model it produces is CPU data only. Turning it into something drawable still costs a texture decode and a GPU upload; see
GltfModel.decodeTexturesAsync()for the half of that which also belongs here.- Throws:
IllegalStateException- if no backend is running (there is no worker pool to use)
-
loadAsync
Parses bytes already in memory on theUiworker pool, delivering the model on the UI thread: the asynchronous form ofload(byte[]). Returned unstarted, cancellable and reporting progress exactly asloadAsync(Path)does, minus the read.The array is read by the worker thread and must not be modified until the job finishes.
- Throws:
IllegalStateException- if no backend is running (there is no worker pool to use)
-
fromResourceAsync
Reads and parses a classpath resource on theUiworker pool, delivering the model on the UI thread: the asynchronous form offromResource(String), and what an application shipping models in its jar should call. Returned unstarted, cancellable and reporting progress exactly asloadAsync(Path)does.- Parameters:
resource- an absolute resource path, e.g."/app/models/robot.glb"- Throws:
IllegalStateException- if no backend is running (there is no worker pool to use)
-