Runtime based meshes

(Optimistic Peach) #1

Hi there!
I really love Rust, and Amethyst seems to be the perfect project to work on/with! I want to get accustomed to it, so I was looking to do something similar to what I was doing in C# with MonoGame, generating a height map/perlin noise and making it change over time. So I’ve got a few questions on how to do some things:

  • In the case that I want to edit the specific points in a mesh I wrote a system like so:
pub struct Flattener;

impl<'a> System<'a> for Flattener {
    type SystemData = WriteStorage<'a, MeshData>;

    fn run(&mut self, mut meshes: Self::SystemData) {
        for i in Join::join(&mut meshes) {
            match i {
                MeshData::PosNormTex(v) => {
                    for j in v.iter_mut() {
                        j.position.y *= 0.5;
                    }
                }
                _ => {}
            }
        }
    }
} //By the way it seems that the discourse syntax highlighter doesn't work with rust in this forum :/
  • Unfortunately that doesn’t yield any results, as inserting a println! right after the join doesn’t print anything. It seems that just joining on a MeshData doesn’t really work.
  • Edit: after some further looking into, I have found that I’m actually inserting a Handle<Mesh> into the entity, and joining on that works. But unfortunately I can’t find a way to access a Mesh from a Handle<Mesh>
  • I create a mesh and insert it like so:
world
    .create_entity()
    .with(Terrain) //Terrain is a Component meant to identify this entity
    .with(mesh) 
    .with(mtl)
    .with(trans)
    .build();
  • Just as shown in the asset loader example
  • I’ve done some research which has pointed me to the fact that amethyst doesn’t support indexing its meshes. Because I’m also doing runtime mesh building using a custom format, as also shown in the asset loader example, I’m fine with this. I just use an algorithm to turn a set of points into a mesh like I’ve done before. What I used to do was cache the indices that corresponded to every instance of a point in the buffer. That way, if I wanted to edit something at a position (x, y) I would look at the list in my cache, and for every index in it, I would edit the element in the big “buffer” under the index. My current function signature to generate the triangles looks like so:
pub fn gen_perlin() -> (Vec<PosNormTex>, IndexRegistry)
  • This is then passed through a struct acting as a SimpleFormat implementer:
impl SimpleFormat<Mesh> for TerrainGen {
    const NAME: &'static str = "TERRAIN";

    type Options = ();

    /// Reads the given bytes and produces asset data.
    fn import(&self, _: Vec<u8>, _: ()) -> Result<MeshData, Error> {
        Ok(gen_simplex().0.into())
    }
}
  • But this way, I lose the cache (IndexRegistry in the signature), any way to keep that? I’ve looked into generating the mesh manually and not going through the SimpleFormat way, but it looks daunting, and not everything that it seems I’d need is public so, that wouldn’t work. :confused:

Sorry for the huge wall of text! I hope that this hasn’t been asked before, but there a google search doesn’t turn up many results, given that Amethyst isn’t too old and there aren’t too many issues so far about it.

2 Likes

(Kel) #2

Welcome to the forum!

So to start with, MeshData is the Prefab meant for loading data that becomes a Mesh. If you’d like to access existing mesh data it’s not the type that you’re looking for. To access a loaded Mesh, you want AssetStorage<Mesh>::get(handle: &Handle<Mesh>). Your entity should itself have a Handle<Mesh> (like you found out) so if you join over the Storage for the Handle<Mesh> you can then use that to access the mesh itself inside the AssetStorage:

impl<'a> System<'a> for MySystem {
    type SystemData = (
        Read<'a, AssetStorage<Mesh>>,
        ReadStorage<'a, Handle<Mesh>>,
    );

    fn run(&mut self, data: &mut Self::SystemData) {
        let (mesh_storage, mesh_handles) = data;

        for (handle) in (mesh_handles).join() {
            let mesh: &Option<Mesh> = mesh_storage.get(handle);
        }
    }
}

However, as you might’ve found out now, you cannot modify Mesh with current renderer (I believe this may be different with new renderer?), so your current approach of generating new Mesh with a Prefab seems right to me but I might recommend actually using MeshData here? You wouldn’t have to create your own prefab then. MeshBuilder also seems like it’d work nicely here. In both cases the result is simply an internal renderer call to factory.create_buffer_immutable_raw and you swapping out the handle on the relevant entity to point at the new vertex data.

2 Likes

(Optimistic Peach) #3

Thanks for the great reply! Also thanks for the welcome!
This seems to be great! I didn’t know that you could also add Read and Writes alongside ReadStorage and WriteStorages. I came up with this code:

pub struct Flattener;

impl<'a> System<'a> for Flattener {
    type SystemData = (
        Write<'a, AssetStorage<Mesh>>,
        ReadStorage<'a, Handle<Mesh>>
    );

    fn run(&mut self, mut data: Self::SystemData) {
        let (mut storage, mut handles) = data;
        for handle in handles.join() {
            let mesh: Option<&mut Mesh> = storage.get_mut(handle);
            if let Some(m) = mesh {
                println!("Found it!");
            } else {
                println!("Didn't find it!");
            }
        }
    }
}

That would work well, as m in the if let statement is a &mut Mesh, so I could just replace the underlying Mesh. On the topic of creating a Mesh, I would like to use MeshBuilder, but it seems that I can’t get a hold of a Factory anywhere in amethyst_renderer, and it doesn’t implement either Asset or Component because it’s the HAL factory. So where would I pass the MeshBuilder or where would I get a Factory to feed it?

Also, upon adding a MeshData (Which I tried a while ago) I don’t get any visual output, so that wouldn’t work :confused:

Thanks!

0 Likes

(Kel) #4

Well remember MeshData doesn’t render anything itself it’s just a prefab. You have to turn it into a Mesh to get something rendered!

0 Likes

(Optimistic Peach) #5

Yes, but essentially what I was asking in my previous post is how I turn it into a Mesh if I can’t use MeshBuilder? I can’t use a MeshBuilder because I don’t see any way to access a Factory, given that the System runs on another thread, and Factory isn’t Send + Sync so I can’t access it through setup to cache it, or through run. ReadStorage/WriteStorage wouldn’t work because Factory doesn’t impl the required Component trait and it wouldn’t work with Read/Write because it doesn’t implement Default and therefore doesn’t want to cooperate with System:

error[E0277]: the trait bound `gfx_device_gl::factory::Factory: std::default::Default` is not satisfied
  --> src\systems\flatten.rs:33:10
   |
33 | impl<'a> System<'a> for Flattener {
   |          ^^^^^^^^^^ the trait `std::default::Default` is not implemented for `gfx_device_gl::factory::Factory`
   |
   = note: required because of the requirements on the impl of `shred::res::setup::SetupHandler<gfx_device_gl::factory::Factory>` for `shred::res::setup::DefaultProvider`
   = note: required because of the requirements on the impl of `shred::system::SystemData<'_>` for `shred::res::data::Write<'_, gfx_device_gl::factory::Factory>`
   = note: required because of the requirements on the impl of `shred::system::SystemData<'_>` for `(shred::res::data::Write<'_, gfx_device_gl::factory::Factory>, shred::res::data::Write<'_, amethyst_assets::storage::AssetStorage<amethyst_renderer::mesh::Mesh>>, specs::storage::Storage<'_, amethyst_assets::storage::Handle<amethyst_renderer::mesh::Mesh>, shred::res::Fetch<'_, specs::storage::MaskedStorage<amethyst_assets::storage::Handle<amethyst_renderer::mesh::Mesh>>>>)`
   = note: required because of the requirements on the impl of `shred::system::DynamicSystemData<'a>` for `(shred::res::data::Write<'_, gfx_device_gl::factory::Factory>, shred::res::data::Write<'_, amethyst_assets::storage::AssetStorage<amethyst_renderer::mesh::Mesh>>, specs::storage::Storage<'_, amethyst_assets::storage::Handle<amethyst_renderer::mesh::Mesh>, shred::res::Fetch<'_, specs::storage::MaskedStorage<amethyst_assets::storage::Handle<amethyst_renderer::mesh::Mesh>>>>)`

and it still isn’t thread-safe:

error[E0277]: `std::rc::Rc<gfx_device_gl::Share>` cannot be sent between threads safely
  --> src\systems\flatten.rs:33:10
   |
33 | impl<'a> System<'a> for Flattener {
   |          ^^^^^^^^^^ `std::rc::Rc<gfx_device_gl::Share>` cannot be sent between threads safely
   |
   = help: within `gfx_device_gl::factory::Factory`, the trait `std::marker::Send` is not implemented for `std::rc::Rc<gfx_device_gl::Share>`
   = note: required because it appears within the type `gfx_device_gl::factory::Factory`
   = note: required because of the requirements on the impl of `shred::res::Resource` for `gfx_device_gl::factory::Factory`
   = note: required because of the requirements on the impl of `shred::system::SystemData<'_>` for `shred::res::data::Write<'_, gfx_device_gl::factory::Factory>`
   = note: required because of the requirements on the impl of `shred::system::SystemData<'_>` for `(shred::res::data::Write<'_, gfx_device_gl::factory::Factory>, shred::res::data::Write<'_, amethyst_assets::storage::AssetStorage<amethyst_renderer::mesh::Mesh>>, specs::storage::Storage<'_, amethyst_assets::storage::Handle<amethyst_renderer::mesh::Mesh>, shred::res::Fetch<'_, specs::storage::MaskedStorage<amethyst_assets::storage::Handle<amethyst_renderer::mesh::Mesh>>>>)`
   = note: required because of the requirements on the impl of `shred::system::DynamicSystemData<'a>` for `(shred::res::data::Write<'_, gfx_device_gl::factory::Factory>, shred::res::data::Write<'_, amethyst_assets::storage::AssetStorage<amethyst_renderer::mesh::Mesh>>, specs::storage::Storage<'_, amethyst_assets::storage::Handle<amethyst_renderer::mesh::Mesh>, shred::res::Fetch<'_, specs::storage::MaskedStorage<amethyst_assets::storage::Handle<amethyst_renderer::mesh::Mesh>>>>)`

Aha! I found the api I’m probably expected to use as an end user: Renderer, now just to figure out how to acquire an instance/mutable reference to one.

And the realization just hit me :thinking: Renderer contains a Factory and still doesn’t implement the things I’d need it to. This is quite confusing to figure out, but I find the library to be quite nicely written, (Even if it’s missing some documentation)

0 Likes

(Kel) #6

It seems you’re right. There are quite a few things in Renderer that are public but are (currently) impossible to use… this might’ve been different previously but currently it sure says “spring cleaning” to me!

What you have to do here is call Loader::load_from_data like so:

let my_mesh_data: MeshData = /* . . . */;
let mesh_asset_storage: AssetStorage<Mesh> = /* get this as a Resource from your World */;
let loader: Loader = /* This is also stored in the World as a Resource */;

let new_handle: Handle<Mesh> = loader.load_from_data<Mesh, ()>(my_mesh_data, (), mesh_asset_storage);

Loader is added to the World by Amethyst so don’t worry about creating it yourself. You can also use a ProgressCounter if you’d like to wait until the mesh data is uploaded to the GPU to change out the handle on your entity.

1 Like