Gnurfos / transvoxel_rs

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

Triangle mesh seems incoherent with surface value threshold other, than 0 #3

Closed davids91 closed 1 year ago

davids91 commented 1 year ago

Extracted field with surface value threshold 0.0 image

Same field extracted with surface value threshold 0.01 image

Same field extracted with surface value threshold 0.1 image

Have tried using any other value, but it only works with 0.0. The greater the value, the greater the mess The images describe it better than I ever could :)

Gnurfos commented 1 year ago

This one is not so obvious as a crash, I don't see anything that would behave specially for threshold 0, and I personally used other values like 0.5 (although not a lot). Are you sure your density function is continuous around 0.1 and 0.01 ? The meshes look not so great on the 0 screenshot too, to me.

The way to investigate this would be to reduce the reproducible case with as few blocks and subdivisions as possible, and hardcoded values.

davids91 commented 1 year ago

The function is exactly like this, but it should be continous, albeit the gradient might be a bit egzotic..

Gnurfos commented 1 year ago

My comment about the density having to be continuous was probably misleading or false, I don't think it actually has to be. However, the gradient has to "make sense", otherwise the normals will be "wrong" (but only the normals should be impacted). Note that for estimating the gradient, the algorithm looks for densities quite far away (the dx,dy,dz are one full voxel grid step instead of being epsilons), and it will look outside of the block.

From a quick look, I think that in your case it means it will return 0 instead of noise values (for x/y/z being -1 and size+1 but also 0, which will impact more than the normals), so that could explain a different behavior with a threshold of 0.

Typically, for a block to be a proper "slice" of the world, you have to allow density queries outside of the block (and return values consistent with neighboring blocks).

To make it easier to investigate (for you or me when I get more time), you could try reducing the case to extracting a single block, and to describe what seems to be wrong.

I notice that your NoiseContainer seems to serve a similar purpose to the internal PreCachingVoxelSource, so you could try to just provide your density function instead. But then you lose some control and the caching policy might not suit you (that's probably the part where refactoring is needed).

Gnurfos commented 1 year ago

And just now I notice that on your screenshots some point seems to be way out of range. So that definitely seems like a bug. It could also be reduced to a simpler case, given some time.

davids91 commented 1 year ago

Unfortunately my usecase doesn't permit to provide only a function, as I plan to set / erase values by hand. I am pretty sure I can reduce this to a single block though.

Gnurfos commented 1 year ago

If you could, it would make it more likely that I find a time slot to investigate soner rather than later.

For your use case, it looks like it would get messy with the current interface: if you use one noise container per block , you need to update several such containers when you edit a voxel (because they have to overlap). Probably cleaner if you could provide the caching yourself, but that's the refactor thing

davids91 commented 1 year ago

So I couldn't reproduce with one block, because it will not show with a simple triangle, but I did it with a 3x3x3 matrix: threshold 0: image threshold 0.5: image threshold 1.0: image threshold 1.1: image threshold 1.3: image threshold 1.6: image

I do hope the wireframe is not too distracting, but I can render this without it even. Does this help?

Gnurfos commented 1 year ago

It sounds much simpler than the previous one. Could you share the code?

davids91 commented 1 year ago

ah sure! Although it's pretty simple:

use transvoxel::density::ScalarField;
impl ScalarField<f32, f32> for MyStruct {
    fn get_density(&self, x: f32, y: f32, z: f32) -> f32 {
        let ix = x.round() as i32;
        let iy = y.round() as i32;
        let iz = z.round() as i32;
        if (ix < 0 || ix >= 3)
            || (iy < 0 || iy >= 3)
            || (iz < 0 || iz >= 3)
        {
            return 0.;
        }
        1.
    }
}

for some other extremes here is the field with threshold 6 image

and -0.5: image

and -2: image

the circle ( at approx (-5,5,-5 ) is emitting red light, that is why the surface looks strange. The structure of the mesh can be clearly seen though. Here's a shot from another angle in threshold -2:

image

Gnurfos commented 1 year ago

Oops there was something still hardcoded with threshold 0:

impl Density for f32 {

    type F = f32;

    fn inside(&self) -> bool {
        self > &0f32
    }

It's now fixed in version 0.6.0 Thanks for reporting and helping pinpoint the issue!

davids91 commented 1 year ago

Thank you for your help!