sinisterchipmunk / jax

Framework for creating rich WebGL-enabled applications using JavaScript and Ruby
http://jaxgl.com
MIT License
96 stars 16 forks source link

It should be possible to reuse mesh with different materials #89

Open sinisterchipmunk opened 11 years ago

sinisterchipmunk commented 11 years ago

Here's an example of adding 2 models to the world using the current API, both sharing the same mesh data:

mesh = new Jax.Mesh.OBJ
  path: "/path/to/mesh.obj"
  material: new Jax.Material.Surface

model1 = @world.addObject new Jax.Model mesh: mesh
model2 = @world.addObject new Jax.Model mesh: mesh

In the above example, it is not currently possible within the Jax API to render each of the two models with different materials.

Basically, if you want to render the same mesh twice -- for example, with each mesh using a different diffuse color -- you have to instantiate the mesh itself twice.

I think it's acceptable (even expected) to instantiate multiple models and multiple materials when representing different variations on the same mesh, but the models should be able to share the same mesh data especially when that data is identical between them.

Mesh data is usually going to occupy the single biggest chunk of memory, and it should be easy to be memory-efficient in Jax relative to this data. Currently it is not.

I am not clear on the best way to resolve this issue. My current thought is that the API should look like:

mesh = new Jax.Mesh.OBJ path: "/path/to/mesh.obj"
model1 = @world.addObject new Jax.Model
  mesh:
    data: mesh
    material: new Jax.Material.Surface(args...)
model2 = @world.addObject new Jax.Model
  mesh:
    data: mesh
    material: new Jax.Material.Surface(args...)

This is a little more verbose and much less intuitive IMHO, but it does get the job done. It decouples Material from Mesh, and it should be straightforward to stay backward compatible with the current API, to easily accommodate the "happy path" of always using the same material with a given mesh.

I'm open to suggestions and refinements, but will probably implement this as written if I don't get any negative feedback toward the idea.

sinisterchipmunk commented 11 years ago

Something that might tie into this: I've been thinking about bringing in the concept of Jax.Mesh.Loader, which would basically encapsulate what is today the init function of Jax.Mesh. Jax would interface with the loader and automate things like, for example, storing the mesh data into local storage for caching purposes. It would also be built to support Web Workers, which the rest of Jax at large does not at this time.

As to how the loader can help resolve this issue...

If Jax.Mesh.Loader could ensure that only one instance of Jax.Mesh.Data is ever constructed for a particular data set, then it could just return that instance to multiple instances of Jax.Mesh wrapped around it. This way, there would be a tiny memory footprint for the duplicate instances of Jax.Mesh, but that footprint would not include the bulky mesh data itself, only a reference to a single global instance of the mesh data.

I envision Jax.Mesh.Loader being used primarily to load mesh data via XHR (though it could be adapted to any purpose, including just chunking a bunch of random vertices into a mesh). This is significant in the sense that we don't have to break the current API in order to implement it. The existing init method could be kept around for light-weight meshes, and the average developer would be blissfully unaware of the existence of Jax.Mesh.Loader -- unless of course they need to implement some custom mesh loader. But, if they use a mesh format supported by Jax out of the box (e.g. PLY or OBJ or whatever gets implemented next), then they would never have to deal with implementing their own loader.

nat-n commented 11 years ago

I wrote a loader model to do something like this in my app. It manages a globally accessible directory of mesh data (and various relevant meta data), loading everything from IndexedDB on launch and fetching data via XHR as required (and caching it in indexedDB after download).

Of course my implementation is very specific to the peculiar structure of mesh data in my app. It'd be great if something similar were baked into Jax.Mesh. However I imagine the design challenge would be making it both usable without fuss in the simplest cases such as where users have a set of obj files they want to load n instances of into the scene, but still workable without too much hacking for cases such as mine in which the actual meshes loaded into the scene are the result of combining a number of meshes managed by the loader according to meta data also managed by the loader.

sinisterchipmunk commented 11 years ago

Thanks for the input. Hypothetically, I imagine in your use case you'd want to create a secondary loader, either inheriting from or just making use of the basic OBJ loader. Once all required meshes have been loaded, the custom loader would handle the merging. It may or may not be interested in clearing the caches for the individual meshes once finished (read: the loader needs a public API for doing so), and then it'd just emit the merged data.

This concept seems straightforward to me, unless I've missed something important that might throw a wrench into the works. It also raises some important points for consideration, such as: we need to be careful not to abuse the fact that the loading is done in a Worker. An example would be the calculation of normals, tangents and bitangents -- each of which is an expensive operation. Today, these are performed lazily -- only when accessed. Although the loader would offload the calculations to a worker, it should also continue doing so lazily. Your use case shows us that eagerly calculating such data would be a waste of cycles, because the merged mesh is just going to invalidate all of it anyway.

Also, falling into the "let's just eager load data we might never actually use" trap would be bad news for mobile, where memory is much more finite.

(The second 2 paragraphs were as much to remind myself, as for anyone else... because I was seriously considering eager calculating that data, earlier.)