Class GltfLoader

java.lang.Object
limn.render3d.gltf.GltfLoader

public final class GltfLoader extends Object
Loads a glTF 2.0 asset into a neutral 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

    Fields
    Modifier and Type
    Field
    Description
    static final long
    The most array elements one load creates unless setElementBudget(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 Type
    Method
    Description
    static long
    The ceiling on the array elements one load may create: DEFAULT_ELEMENT_BUDGET until setElementBudget(long) moves it.
    static GltfModel
    fromResource(String resource)
    Reads a classpath resource whole and parses it (.gltf or .glb), on the calling thread: the shape for a model shipped inside the application jar.
    static Work<GltfModel>
    Reads and parses a classpath resource on the Ui worker pool, delivering the model on the UI thread: the asynchronous form of fromResource(String), and what an application shipping models in its jar should call.
    static GltfModel
    load(byte[] data)
    Parses a model already in memory, on the calling thread.
    static GltfModel
    load(Path file)
    Reads file whole and parses it (.gltf or .glb), on the calling thread.
    static Work<GltfModel>
    loadAsync(byte[] data)
    Parses bytes already in memory on the Ui worker pool, delivering the model on the UI thread: the asynchronous form of load(byte[]).
    static Work<GltfModel>
    Reads and parses file on the Ui worker pool, delivering the model to onSuccess on the UI thread: the asynchronous form of load(Path).
    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.

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Field Details

    • DEFAULT_ELEMENT_BUDGET

      public static final long DEFAULT_ELEMENT_BUDGET
      The most array elements one load creates unless setElementBudget(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 - if elements is less than 1
    • elementBudget

      public static long elementBudget()
      The ceiling on the array elements one load may create: DEFAULT_ELEMENT_BUDGET until setElementBudget(long) moves it.
      Returns:
      the current budget, in elements
    • load

      public static GltfModel load(Path file)
      Reads file whole and parses it (.gltf or .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

      public static GltfModel fromResource(String resource)
      Reads a classpath resource whole and parses it (.gltf or .glb), on the calling thread: the shape for a model shipped inside the application jar. See load(Path) for the cost and fromResourceAsync(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

      public static GltfModel load(byte[] data)
      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 see load(Path) for the cost and loadAsync(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

      public static Work<GltfModel> loadAsync(Path file)
      Reads and parses file on the Ui worker pool, delivering the model to onSuccess on the UI thread: the asynchronous form of load(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: a GltfModel is 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

      public static Work<GltfModel> loadAsync(byte[] data)
      Parses bytes already in memory on the Ui worker pool, delivering the model on the UI thread: the asynchronous form of load(byte[]). Returned unstarted, cancellable and reporting progress exactly as loadAsync(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

      public static Work<GltfModel> fromResourceAsync(String resource)
      Reads and parses a classpath resource on the Ui worker pool, delivering the model on the UI thread: the asynchronous form of fromResource(String), and what an application shipping models in its jar should call. Returned unstarted, cancellable and reporting progress exactly as loadAsync(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)