Skip to content

Better Model API #858

@bjornbytes

Description

@bjornbytes

There are some limitations of the Model API that would be nice to improve:

  • There isn't a very easy way to manually render the individual nodes/meshes of a model. This can be useful if you want to use different shaders, materials, or render states when rendering some nodes, or just do the rendering yourself. Technically it's possible. You can get all the meshes in a model, you can walk the node tree and use ModelData:getNodeMeshes(node) to see which meshes to render. There are some downsides:
    • It's a little more cumbersome than I'd like.
    • It does not work with animations.
  • Using your own materials with a Model is pretty hard. People often have a simple OBJ model with 1 node and mesh, and want to load the texture manually and use it with the Model. It seems like this should work with Pass:setMaterial(texture), but the model doesn't use the pass's material -- it uses its own material (which is usually just plain white). There is a workaround where you can load the model with { material = false }, but it shouldn't be necessary.
  • It isn't easy to create a MeshShape or ConvexShape from a "piece" of a model. There isn't an existing Model/ModelData API that can give you the triangles of a single node or mesh, and even if you get the full vertex/index buffer, you have to get the mesh draw range (or ranges, for a node) and extract that subset of vertices yourself.
    • There are lots of ways to solve this. MeshShape constructor could take a Model plus a node/mesh, or a vertex range, but these feel somehow wrong. Alternatively there could be a way to query different sets of triangles from the Model.
    • Also, Model:getMesh doesn't work because the Mesh itself references the model's entire vertex buffer and uses the draw range to reference a slice from that buffer, which newMeshShape isn't able to understand.
  • Relatedly, it's also hard to just get the vertices from a Model. ModelData's API to return vertices is able to support arbitrary vertex formats, which makes it really difficult to "just get the UVs from the mesh" or whatever. You have to search through all the attributes, find the UV attribute, and then hope it's in a format that works for you.
    • This is because glTF supports arbitrary vertex formats. This might be overkill, I've been thinking LÖVR's ModelData object could convert all the vertices into a standard format on import (Model already does this and no one's complained, maybe we can just move that logic to ModelData).
  • We currently do not support what glTF calls "instantiated meshes" (animated meshes referenced by multiple nodes, each potentially with a different skin or set of blend shape weights). It would be nice to support this.
  • As discussed in Multiple Animated Poses Per Model #701, models can only have a single animated state right now. Model:clone solves this for now, but we should keep this in mind while redesigning Model-related APIs.

Here are some ideas to improve the API and solve some of these problems:

Improved Node Walking API

  • Model:getNodeChildren is bad because it creates garbage. It also requires recursion to iterate?
  • Consider the following alternative APIs:
    • Model(Data):getNodeChild --> returns index of a node's first child
    • Model(Data):getNodeSibling --> returns index of a node's next sibling
    • for node in Model(Data):nodes([mode]) --> returns a Lua iterator that iterates over all node indices
      • mode could let you iterate in DFS/BFS order.
      • There could be other parameters like "only visible nodes" or "root node to start at" or "recurse".
      • I think this could be a stateless iterator to avoid garbage.

This would let you do something like the following to walk over nodes:

for node in model:nodes() do
  for mesh in model:meshes(node) do -- assume some way to get meshes for node
    pass:draw(mesh, model:getNodeTransform(node))
  end
end

This makes it a lot easier to walk the node graph.

Improved Mesh API

  • Suggestion: Convert Model vertices immediately when importing, in ModelData, rather than in Model.
    • LÖVR will have a standard vertex format for model vertices.
    • ModelData accessors can be simplified, and return data in a known format.
    • There could be a function like ModelData:encode that returns a Blob with LÖVR-optimized binary model data, which would make models very quick to load.
  • Keep Model:getMesh. Maybe it could use something other than draw range to refer to a subsection of the Model's vertex buffer, not sure yet.
  • Make Model(Data):getTriangles more flexible:
    • You should be able to pass in a mesh index and get local vertices for just that mesh.
    • There is a notion of "local vertices" vs. "full vertices" (name TBD):
      • local vertices are the raw contents of the vertex buffer. The vertices are not duplicated for every node that references them, and they are not transformed by their node transform(s). This is basically the contents of the Model's vertex buffer, or what you'd want if you're creating a MeshCollider from a specific node in the model.
      • "full" vertices are the full set of vertices in the model: they are duplicated and transformed by each node.
  • Replace ModelData's :getMeshVertex and :getMeshIndex with :getVertices and :getIndices (or :getVertex / :getIndex) methods.
    • These should have the same flexibility mentioned above for :getTriangles, so you can get the vertices for the entire model, or just a single mesh.
    • Additionally, they'll return data in a single, known format.
  • Replace ModelData's :getMeshDraWMode and :getMeshMaterial with ModelData:getMesh
    • Consider mode, material, start, count, base = ModelData:getMesh(i)
  • Add for i, mesh in Model:meshes(node) iterator that lets you quickly iterate over Mesh objects that belong to a node.
    • Unclear if this one can be stateless.

All of this should make it easier to grab vertices/indices/triangles out of a model.

First Class Vertex Animation

Currently Model animation is "magical". LÖVR doesn't provide built in tools to do skeletal animation or blend shapes, it's baked into the Model object. It would be nice if this functionality was exposed on Mesh objects as well, so people can easily do animation even if they aren't using Model.

  • Mesh can have skinning info (VertexJoints and VertexWeights attributes).
  • Add a Skeleton object. Contains a tree of joints, each with an inverse bind matrix and local/global transforms.
  • A Skeleton can be attached to a Mesh, either with Mesh:setSkeleton or Pass:draw(Mesh, Skeleton, ...transform). Mesh vertices will be skinned to the joints in the skeleton.
  • Model creates a Skeleton for every skin in the glTF.
  • When you :animate a model or move its nodes manually, the Model will also update any affected skeletons.
  • Drawing an animated model just draws its meshes with whatever skeletons are assigned to those nodes. No magic.
  • Skeletons are included when iterating meshes manually:
for node, mesh, skeleton in model:meshes() do
  pass:draw(mesh, skeleton, model:getNodeTransform(node))
end

Similarly, Meshes can have blend shapes and blend weights. Adding blend shapes to the mesh and setting its blend weights will morph the vertices with a compute shader before rendering.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions