Gnurfos / transvoxel_rs

Rust implementation of the Transvoxel algorithm
Apache License 2.0
23 stars 6 forks source link

[Proposal] Include LOD information into VoxelData trait #7

Open davids91 opened 5 months ago

davids91 commented 5 months ago

To help with deciding on how detailed the density information should be, the VoxelData trait could be updated: https://github.com/Gnurfos/transvoxel_rs/blob/9043633b82f40c44230202d233e380443853dde4/src/traits.rs#L39

pub trait VoxelData: Default + Clone + Copy {
    /// The type that acts as density
    type Density: Density;
    /// How to get the density from a given data object
    //From:  fn density(&self ) -> Self::Density;
    fn density(&self, cell_size:  f32) -> Self::Density;
}

The function usage inside the library contains this information in let rot = self.current_rotation;: https://github.com/Gnurfos/transvoxel_rs/blob/9043633b82f40c44230202d233e380443853dde4/src/implementation/algorithm.rs#L469

This would make it possible to provide better representative density information through VoxelData by enabling the trait implementers to use the size of the iterating cube.

Gnurfos commented 5 months ago

I understand the need for different density information quality (and cost) for different LOD levels, and didn't think about this before. Your proposition raises some concerns to me, though, after a very quick consideration:

In my mind, VoxelData was to be kind of inert and just the result of evaluating a field at a given position. Would it work for your use case, if the field itself was responsible for handling the LOD / density details ? You would keep in VoxelData only things that transvoxel_rs cares about, and where today you might have something like:

fn f(x, y, z) -> VoxelData;
extract_from_fn(f, ...);

You would wrap f in a (LOD-customizable) field object, and do something like:


let field_for_current_lod = MyField::new(f, Lod::VERY_DETAILED); // Or some less static value like Lod::new(3);
extract_from_field(field_for_current_lod, ...);

Do you think that could work, and makes sense?

davids91 commented 5 months ago

I think your proposition makes sense!

In that case the responsibility to align the generated meshes so there are no unintentional holes would fall into the implemented of VoxelData, which brings some additional questions into play though...

Thank you for considering it!

Gnurfos commented 5 months ago

Did you manage to get something ?

It's going to be hard any way, if the f(x,y,z)->density function depends on the LOD, but I really feel like the "voxel source" is the right place to handle this. That said, I'm not sure the current implementation provides you enough information to do it properly: to avoid holes, you need all densities to coincide between neighboring blocks, for a given queried point in space. The issue is that the algorithm will sometimes call VoxelSource::get_regular_voxel even for a high-res face (assuming it saves memory potentially, but that's maybe not smart). In this case for example:

     LOD N+1          LOD N with transition

   ┌───────┬──────a   A───A────────────D
   │       │      │   │   │            │
   │       │      │   │   │            │
   │       │      │   │   │            │
   │       │      │   │   │            │
   ├───────┼──────b   B   │            │
   │       │      │   │   │            │
   │       │      │   │   │            │
   │       │      │   │   │            │
   │       │      │   │   │            │
   └───────┴──────c   C───C────────────E

You want a==A, b==B, c==C. But the density for A and C will be queried through get_regular_voxel, so you have no way of knowing that you need to match the higher LOD precision, in this case.

Maybe I should just drop this "optimization", or create a 3rd function (or pass down some flag telling you which resolution is queried). Would any of these help ?

Out of curiosity, are you doing this because you hit a point where generating density is a bottleneck ? What kind of application are you making ?

davids91 commented 5 months ago

I've been thinking about this problem and for sure the information is available to the user through the subdivisions field from block: https://github.com/Gnurfos/transvoxel_rs/blob/9043633b82f40c44230202d233e380443853dde4/src/voxel_source.rs#L34

If I as a user am calling the extract function I have explicit control over the number of subdivisions there will be inside a field; Which correlates directly with LOD, as for smaller subdivisions lower LOD values match.

I managed to work around most of the limitations by having a reference to the field included inside the mesh builder, so it is possible to query the field itself, and the interpolated positions are available in the call of add_vertex_between.

As for the usage, I generate terrain mesh from voxel information, and currently I am able to generate a panorama for a 256256256 block ( one voxel has 101010 size, so it's a 256025602560 landscape), but it's not nearly enough to have the immersion I was looking for so I'm looking to 10x that with optimizations, and pre-rendering ( in progress ), and one of the optimization possibilities is to have the field in different LODs. I was also experimenting with https://crates.io/crates/meshopt, but it messes up the connecting vertex positions so I'm not sure if that's a plausible use here.

Screenshot_2024-02-20_13-40-27

Gnurfos commented 5 months ago

I think reading subdivisions is not enough to avoid holes, as it is constant across the block. But it won't tell you what resolution of voxel is being queried by the algorithm. In my example above, voxels A/B/C, despite being used within the block of lod N, have to produce densities equal to the ones produced by a/b/c (in a block of lod N+1).

davids91 commented 5 months ago

Well in itself it's a really complex unsolved problem; All one can do is approximate I think