Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reading a glTF file #55

Closed
Suicolen opened this issue Nov 26, 2021 · 5 comments
Closed

Reading a glTF file #55

Suicolen opened this issue Nov 26, 2021 · 5 comments

Comments

@Suicolen
Copy link

Suicolen commented Nov 26, 2021

Hi, i have written a glTF exporter for my custom model format using this library and it was really simple to do as the library did most of the work for me, for example writing the joints and weights just took about 25 lines total as all i had to do was create the 2 buffers, create an accessor model for each, add the 2 attributes(JOINTS_0 and WEIGHTS_0) and create a buffer view model which was extremely easy due to the 'helper' classes like BufferStructureBuilder

Now i am not sure about importing a glTF file and extracting data from it, are there any helper methods that make it simpler?
For example if i wanted to read the weights of a mesh primitive, this is what i do right now:

        GltfReaderV2 reader = new GltfReaderV2();
        GlTF gltf = reader.read(new ByteArrayInputStream(Files.readAllBytes(filePath)));
        Mesh mesh = gltf.getMeshes().get(0); // the gltf file only has 1 mesh
        MeshPrimitive primitive = mesh.getPrimitives().get(0); // the mesh only has 1 mesh primitive(for now)
        int weightsIndex = primitive.getAttributes().get("WEIGHTS_0");
        Accessor weightsAccessor = gltf.getAccessors().get(weightsIndex);
        BufferView weightsBufferView = gltf.getBufferViews().get(weightsAccessor.getBufferView());
        Buffer gltfBuffer = gltf.getBuffers().get(weightsBufferView.getBuffer());
        String base64 = gltfBuffer.getUri().substring(gltfBuffer.getUri().indexOf("base64") + "base64, ".length());
        byte[] bytes = Base64.getDecoder().decode(base64.getBytes(StandardCharsets.UTF_8));
        int byteOffset = weightsBufferView.getByteOffset();
        int byteLength = weightsBufferView.getByteLength();
        //TODO create float buffer and start reading weights

This isn't very tedious to do, but im still curious if there are perhaps simpler ways.
Any advice is appreciated.

@javagl
Copy link
Owner

javagl commented Nov 26, 2021

Some background: When I started creating that library, glTF 1.0 was published, and the update for the next version was in progress. Unfortunately, the changes from glTF 1.0 to glTF 2.0 turned out to be significant. I naively tried to find some sort of "abstraction" for changes, in form of the ...Model classes. In hindsight, much of this could have been much simpler and cleaner if I had just dropped the glTF 1.0 support.
However, here we are. I'm trying to make the best out of this.


When you mention the BufferStructureBuilder, then I have to emphasize the JavaDoc comment from the current state of this class:

This class is only intended for internal use!

But I'm aware of the demand for such a "helper" class: Most of glTF is somewhat trivial to build (for simple gemetry models, at least). But the buffer-bufferView-accessor structures are the most tricky part, even for "simple" models. Therefore, I started extending that functionality, with the goal to be able to "easily" create glTF models. The progress is slow (too many other things to do), but I actually made a bit of progress recently. This is done in the https://github.com/javagl/JglTF/tree/model-creation branch.

The goal of this "refactoring" is to offer more convenient classes for building models (and there will be some changes in the BufferStructureBuilder and related classes). An example of what this could look like can be found at https://github.com/javagl/JglTF/blob/model-creation/jgltf-model-builder/src/test/java/de/javagl/jgltf/model/creation/example/GltfModelCreationExample.java#L97 . Feedback and suggestions for improvements are welcome - but I'm aware that until now, only a tiny fraction of glTF is covered with these classes, and there are some things that I'll have to address before making these changes "official".


Coming back to your actual question:

There are some degrees of freedom, depending on your exact goals. But as I mentioned above, there are two levels of abstraction:

  • The classes in the de.javagl.jgltf.impl.v2 package. These are low-level classes, auto-generated from the JSON schema. And that's what you are using in the code that you showed.
  • The classes in the de.javagl.jgltf.model package. These are "convenience classes", representing the glTF asset in a form that is supposed to be easier to use, when you don't need to manipulate things on the level of the JSON input

And I think that the code that you showed is tedious (at least, more tedious than it should be). There are many assumptions in the code. This refers to the special case like having only 1 mesh primitive, but also to the fact that the buffer has a data-uri and so on. And when the //TODO create float buffer and start reading weights is to be addressed, things can become tricky and clumsy very quickly: You'll have to check the component type, number of elements, and think about sparse accessors and such...

The model classes, and particularly the AccessorModel/AccessorData classes, should make that a bit easier. Here is an example that loads the SimpleSkin sample model, and prints the WEIGHTS_0 data:

package de.javagl.jgltf.example;

import java.net.URI;

import de.javagl.jgltf.model.AccessorFloatData;
import de.javagl.jgltf.model.AccessorModel;
import de.javagl.jgltf.model.GltfModel;
import de.javagl.jgltf.model.MeshModel;
import de.javagl.jgltf.model.MeshPrimitiveModel;
import de.javagl.jgltf.model.io.GltfModelReader;

public class GltfModelExample
{
    public static void main(String[] args) throws Exception
    {
        String modelName = "SimpleSkin";
        String attributeName = "WEIGHTS_0";
        
        String flavor = "Embedded";
        String extensionWithoutDot = "gltf";
        URI uri = new URI("https://raw.githubusercontent.com/KhronosGroup/"
            + "glTF-Sample-Models/master/2.0/" + modelName + "/glTF-" + flavor
            + "/" + modelName + "." + extensionWithoutDot);
        GltfModelReader gltfModelReader = new GltfModelReader();

        System.out.println("Reading from " + uri);
        GltfModel gltfModel = gltfModelReader.read(uri);

        MeshModel meshModel = gltfModel.getMeshModels().get(0);
        MeshPrimitiveModel meshPrimitiveModel =
            meshModel.getMeshPrimitiveModels().get(0);
        
        AccessorModel accessorModel =
            meshPrimitiveModel.getAttributes().get(attributeName);
        
        // This cast is safe, because we know the type. Otherwise, check 
        // that accessorModel.getComponentDataType() == float.class
        AccessorFloatData data =
            (AccessorFloatData) accessorModel.getAccessorData();
        
        int numElements = data.getNumElements();
        int numComponents = data.getNumComponentsPerElement();
        for (int e = 0; e < numElements; e++)
        {
            System.out.println("Element " + e);
            for (int c = 0; c < numComponents; c++)
            {
                float f = data.get(e, c);
                System.out.println("  component " + c + ": " + f);
            }
        }
    }

}

When you change the modelName to SimpleSparseAccessor, and the attributeName to POSITION, then it will print the positions, with the sparse substitution already applied - just as one example where these model classes can make things a bit simpler...

@Suicolen
Copy link
Author

First of all, thanks for the detailed reply.
As for BufferStructureBuilder being a class for only internal use, i was not aware of that (Perhaps cause i just looked at the compiled class in IntelliJ and it showed no comments/documentation at all since comments are removed by the compiler)

Now, i should've definitely done some more research before opening this issue as i completely forgot that jglTF also provided a jGltf-Model package which makes things fairly easy compared to the low level classes in the impl package(in my original code i believe i just used the low level classes which did indeed make things fairly tedious)

And like you mentioned in my code i made quite a few assumptions(like there only being exactly 1 mesh primitive which was indeed the case for my test gltf file but rarely the case overall) I just tried to make a really simple example.

Finally, thank you for the example code on how to read weights using the gltf-model package, it honestly looks fairly simple and much simpler compared to using the low level classes.

I will also take a look at the model-creation branch that you mentioned.
Thank you for working on this library, i feel like writing a glTF parser from scratch is a rather difficult task and would have taken me a while(and it would most certainly have had bugs) so it has helped me a lot.

@Suicolen
Copy link
Author

Suicolen commented Nov 26, 2021

Also, im assuming that the GltfModel interface has been updated since late 2018 as i don't have the getMeshModels() method in mine, im using the latest published release of JglTF model 2.0 in maven repository. The interface has the following methods:

package de.javagl.jgltf.model;

import java.util.List;

public interface GltfModel {
    List<AccessorModel> getAccessorModels();

    List<AnimationModel> getAnimationModels();

    List<BufferModel> getBufferModels();

    List<BufferViewModel> getBufferViewModels();

    List<CameraModel> getCameraModels();

    List<ImageModel> getImageModels();

    List<MaterialModel> getMaterialModels();

    List<NodeModel> getNodeModels();

    List<SceneModel> getSceneModels();

    List<TextureModel> getTextureModels();
}

looking at https://github.com/javagl/gltfOverview/releases i suppose the only way for me to include it into my project is just include the source code or maybe create a jar of the source code and then include that?

Edit: i just realized what i linked is just the gltf overview and not the actual source code
The correct link should be: https://github.com/javagl/JglTF/releases
However the latest release is from 2017, so now im guessing i just have to clone the repository and include that in my project (or build a jar from it and include in my project)

@javagl
Copy link
Owner

javagl commented Nov 26, 2021

Also, im assuming that the GltfModel interface has been updated since late 2018 as i don't have the getMeshModels() method in mine,

Yes, sorry, I had quickly created this example while being on the model-creation branch. In the released version, the line should be

MeshModel meshModel = gltfModel.getNodeModels().get(0).getMeshModels().get(0);

The packages in the releases are only preview releases of the standalone gltf-browser application. (This hasn't been updated in a while). The latest "official" release is version 2.0.0 in Maven Central.

@Suicolen
Copy link
Author

Suicolen commented Nov 26, 2021

Thank you, it all works now.

Quick edit: i had to do getNodeModels().get(1) instead as node 0 was my root node, 1 was my mesh node

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants