Hunting Voxel Editor Bug

Voxel Grids Debug.zip (815.7 KB)

The above project is demonstrated in the video below.

There’s a bug from the original Voxel Editor that I’ve been trying to track down and solve, because I’d like to fix it in my mod of the Editor.

Hopefully the project’s been simplified enough that someone with knowledge of raycasting can see how it works at a glance.

@UberGoober I am not familiar with the voxel stuff…is your grid object actually declared as a voxel object? If not it may not be ‘registered’ to work with the voxel raycast? I see the idInCallback always comes back nil.

@UberGoober If I make the grid a physics rigid body then a physics raycast does work.

@piinthesky It’s good to know that works, but using the voxel system is essential for the purposes of the voxel editor.

To know where to put a new block, the raycast has to detect the voxel-volume coordinates of the location that was touched. A physics rigidbody isn’t helpful there, I don’t think.

@piintheskyit doesn’t matter what the grids are made of because the raycast isn’t interacting with the grids in the first place. The grid is just a visual representation of a voxel boundary. It could be completely invisible, or not there at all, and the bug would persist. The raycast is failing to detect voxels that it should be detecting—the grid visually is irrelevant to that.

@UberGoober I don’t know if this will help any, but here’s some code I have that uses voxels and raycasts. The green block is the origin and the red squares are where the raycasts hit the white wall. I also print the coordinates where they hit.


viewer.mode=STANDARD

function setup()
    assert(OrbitViewer, "Please include Cameras as a dependency")      
    scene=craft.scene()
    craft.scene.main=scene      
    v=scene.camera:add(OrbitViewer,vec3(20,0,0),100,0,200)
    v.rx=10     
    scene.voxels.blocks:addAssetPack("Blocks")    
    scene.voxels:resize(vec3(16,1,16))          
    scene.voxels.coordinates=vec3(20,0,20) 
    
    -- draw a floor 40x40
    scene.voxels:fill(BLOCK_NAME,"Solid",COLOR,color(255,0,0))
    scene.voxels:box(0,0,0,40,0,40)
    
    -- draw a wall 40x40
    scene.voxels:fill(BLOCK_NAME,"Solid",COLOR,color(255))
    scene.voxels:box(0,0,40,40,40,40)
    
    -- do a raycast
    origin=vec3(20,20,0)
    doRaycast(origin,vec3(2,2,10),60)
    doRaycast(origin,vec3(1,1,10),60)
    doRaycast(origin,vec3(-12,15,40),60)
    
    -- draw the origin box
    scene.voxels:fill(BLOCK_NAME,"Solid",COLOR,color(0, 255, 22))
    scene.voxels:block(origin)
end

function draw()
    scene:draw()
end

function doRaycast(origin,dir,dist)
    scene.voxels:raycast(origin,dir,dist, 
    function(coord, id, face)
        if coord and id ~= 0 then
            --print(origin)
            print(coord)
            --print(face)
            scene.voxels:set(coord,"color",color(255,0,0))  
            return true
        end
    end)
end

@piinthesky voxel volumes have their own raycasting function, which returns every block hit by the raycast unless the callback function returns false. The ids that get returned are user-defined ids, if any. Nil just means there’s no user-defined block with a blockId in that voxel coordinate.

@dave1707 I’ve done a little more looking into this and there’s a mystery here in that when a hit is returned, the raycast only fires the callback around 30-50 times, but when a nil is returned, the raycast fires the callback anywhere from 120-170+ times. So it seems like the failing raycasts are… not sure… maybe they’re going between the target blocks, or something, so no boundary blocks are detected and the raycast just keeps going until it hits the end of the chunk… or something like that?

@John is it possible for raycasts to go between voxels without a callback being called on the voxel on either side?

@UberGoober When I use this example with the ray going directly to the wall, the callback is called for each z position that the ray goes thru and returns false. It stops as soon as the ray hits a voxel. So the number of calls is determined by the number of voxel positions the ray goes thru until it reaches the max distance or hits a voxel.

PS. If the ray is going off at some angle, then it can go thru a lot of positions before reaching max or a hit.


viewer.mode=STANDARD

function setup()
    assert(OrbitViewer, "Please include Cameras as a dependency")      
    scene=craft.scene()
    craft.scene.main=scene      
    v=scene.camera:add(OrbitViewer,vec3(20,0,0),100,0,200)
    v.rx=10     
    scene.voxels.blocks:addAssetPack("Blocks")    
    scene.voxels:resize(vec3(16,1,16))          
    scene.voxels.coordinates=vec3(20,0,20) 
    
    -- draw a floor 40x40
    scene.voxels:fill(BLOCK_NAME,"Solid",COLOR,color(255,0,0))
    scene.voxels:box(0,0,0,40,0,40)
    
    -- draw a wall 40x40
    scene.voxels:fill(BLOCK_NAME,"Solid",COLOR,color(255))
    scene.voxels:box(0,0,40,40,40,40)
    
    -- do a raycast
    origin=vec3(20,20,0)
    rt,rf=0,0   -- return true, return false
    doRaycast(origin,vec3(0,0,40),60)
    
    -- draw the origin box
    scene.voxels:fill(BLOCK_NAME,"Solid",COLOR,color(0, 255, 22))
    scene.voxels:block(origin)
end

function draw()
    scene:draw()
end

function doRaycast(origin,dir,dist)
    scene.voxels:raycast(origin,dir,dist, 
    function(coord, id, face)
        if coord and id ~= 0 then
            scene.voxels:set(coord,"color",color(255,0,0))  
            rt=rt+1 -- return true
            print("rt ",rt,coord)
            return true
        end
        rf=rf+1 -- return false
        print("rf ",rf,coord)
    end)
end

@dave1707 I think your demo is using the voxel system and not the voxel volume system. I’m not positive it makes a difference but in practice I’ve found lots of gotchas in trying to use them interchangeably. I’ll try to adjust my demo to match yours more closely but with a voxel volume.

@UberGoober I added a voxel volume to my program and it appears that the voxel volume isn’t detected. The ray passes thru the volume and hits the back wall where that is detected. The raycast is listed under Craft.voxels and not under Craft.volume. So that leads me to believe it isn’t supposed to work with volume.

@dave1707 I think that’s because you’re using voxel raycasting, not volume raycasting.

…hmm, you’re right, though, it’s not in the documentation. Another undocumented Craft feature. Trust me, though, volume:raycast is a thing. I’m using it in my attached project above if you want to see it in action.

See what I mean about there being a lot of gotchas? It’s hard when there are two systems so close in name and functionality.

And FWIW the whole volume-and-raycast system is directly lifted from the Voxel Editor built-in example, it’s John’s code at the core.

@UberGoober Here a modification of my original code showing the volume raycast. I draw a Craft volume wall and do the raycast towards it. Looking at the print window you’ll see it returns true and stops when it reaches the volume wall. So the volume raycast is working. I made the floor “ error “ so you can count the blocks. The blocks start at 0 which puts the volume wall at 20 where it returned true.


viewer.mode=STANDARD

function setup()
    assert(OrbitViewer, "Please include Cameras as a dependency")      
    scene=craft.scene()
    craft.scene.main=scene      
    v=scene.camera:add(OrbitViewer,vec3(20,0,0),100,0,200)
    v.rx=10     
    scene.voxels.blocks:addAssetPack("Blocks") 
    
    error=scene.voxels.blocks:new("Error")
    error.setTexture(ALL,"Blocks:Error")
    
    scene.voxels:resize(vec3(16,1,16))          
    scene.voxels.coordinates=vec3(20,0,20) 
    
    -- draw a red error floor 40x40
    scene.voxels:fill(BLOCK_NAME,"Error",COLOR,color(255,0,0))
    scene.voxels:box(0,0,0,40,0,40)
    
    -- draw a white wall 40x40
    scene.voxels:fill(BLOCK_NAME,"Solid",COLOR,color(255))
    scene.voxels:box(0,0,40,40,40,40)
    
    -- draw a volume wall   
    volume = scene:entity():add(craft.volume,25,40,30) 
    for x=10,25 do
        for y=0,40 do
            for z=20,20 do
                volume:set(x,y,z,COLOR,color(0,222,255),BLOCK_NAME,"Solid") 
            end
        end
    end 
    
    -- do a raycast
    origin=vec3(20,20,0)
    rt,rf=0,0   -- return true, return false
    doRaycast(origin,vec3(0,0,40),60)
    
    -- draw the origin box
    scene.voxels:fill(BLOCK_NAME,"Solid",COLOR,color(0, 255, 22))
    scene.voxels:block(origin)
end

function draw()
    scene:draw()
end

function doRaycast(origin,dir,dist)
    volume:raycast(origin,dir,dist, 
    function(coord, id, face)
        if coord and id ~= 0 then
            rt=rt+1 -- return true
            print("rt ",rt,coord)
            return true
        end
        rf=rf+1 -- return false
        print("rf ",rf,coord)
    end)
end

@dave1707 I’m still not sure your demo code is applicable, though, because it just does a single raycast head-on to the wall.

In my project, the raycasts sometimes work and sometimes don’t, so for all I know a single raycast head-on to the wall might work, or might work if you just happen to have it in the right location. So one test from one location isn’t a valid comparison to my issue.

I’m trying to simplify “my” (really John’s) code until it’s as close to yours as possible while still showing the bug. I’ll get back to you on it. I do appreciate your help a great deal. Your example encourages me to get things as simple as possible and that’s really helpful.

@UberGoober The code was just to see if volume raycast would work. You would need to do a double for loop (x,y) and sweep out an area looking for a hit.

@UberGoober Here’s some code that sweeps out an area to see if there’s any volume cubes in there.


viewer.mode=STANDARD

function setup()
    assert(OrbitViewer, "Please include Cameras as a dependency")      
    scene=craft.scene()
    craft.scene.main=scene      
    v=scene.camera:add(OrbitViewer,vec3(20,0,0),100,0,200)
    v.rx=10     
    scene.voxels.blocks:addAssetPack("Blocks") 

    scene.voxels:resize(vec3(16,1,16))          
    scene.voxels.coordinates=vec3(40,0,40) 
    
    -- draw a red error floor 40x40
    scene.voxels:fill(BLOCK_NAME,"Solid",COLOR,color(255,0,0))
    scene.voxels:box(0,0,0,40,0,40)
    
    -- create volume area   
    volume = scene:entity():add(craft.volume,100,100,100)
     
    -- draw sime volume boxes
    volume:set(22,20,38,COLOR,color(0,222,255),BLOCK_NAME,"Solid") 
    volume:set(15,17,35,COLOR,color(0,222,255),BLOCK_NAME,"Solid") 
    volume:set(21,14,32,COLOR,color(0,222,255),BLOCK_NAME,"Solid") 
    
    -- set origin value
    origin=vec3(0,1,0)
    
    -- do a raycast range
    for x=0,50 do
        for y=0,50 do
            doRaycast(origin,vec3(x,y,25),80)
        end
    end
        
    -- draw the origin box
    scene.voxels:fill(BLOCK_NAME,"Solid",COLOR,color(0, 255, 22))
    scene.voxels:block(origin)
end

function draw()
    --scene:draw()
end

function doRaycast(origin,dir,dist)
    volume:raycast(origin,dir,dist, 
    function(coord, id, face)
        if coord and id ~= 0 then
            print("rt ",coord)
            return true
        end
    end)
end