I googled this error I have started recieving today from ReadVoxels and WriteVoxels usage in my code and found this Bug Report from last year reporting it and it being fixed. But now it has reappeared. and is breaking everything
In all my years of development in ROBLOX I have not seen an update like this. Is this a bug?
I have gone through my code and updated it with :Expand to grid where necessary but Iâm not sure what kind of behavioral or performance drawbacks this has.
Hi @Magus_ArtStudios, can you please elaborate in which ways is this breaking things? It is supposed to be only a warning message, nothing should be changed in the actual code behavior, and itâs only enabled for Studio for now, while we wait for everyone to fix the problems. If that is not the case, please let us know where so we can fix this. Thanks!
Please be advised that the line you posted, the one which reads from -2 to +2⌠thatâs not aligned to 4. Because 2 % 4 is not 0. Using such ranges means that the code internally snaps it to the closest values that are aligned, but unfortunately this is an old oversight, and the rounding mode is inconsistent and imprecise.
We recommend using ExpandToGrid as we cannot guarantee that the current âaccidentalâ rounding will stay for long. It is not officially supported as you can see in the docs. ExpandToGrid is just a simple rounding calculation and jf you are calling any voxel r/w functions, this should be a negligible overhead.
I have fixed all the issues with my code concerning this, but it caused a âstack endâ not just a warning. Before the update, I could input a small region such as -2 ,+2 which equals one voxel the region has x of 4 y of 4 and z 4, Which I thought was correct but I have changed it to a 4 size and it doesnât throw the warning anymore.
I have also read the documentation on these methods and have noticed that it now says Must be snapped to the voxel grid. The issue is workspace.Terrain:FillBlock, :FillCylinder, :FillBall, :FillWedge etc ALL internally snap to the voxel grid so this new behavior is actually somewhat inconsistent with the other Terrain methods. It also breaks a lot of the resources and games that use :ReplaceMaterial, :ReadVoxels, and :WriteVoxels.
In conclusion, this causes a stack end warning that stops the behavior of the code beyond that point. Itâs inconsistent with the :FillBlock, :FillWedge, :FillBall,:FillCylinder methods that snap to the grid automatically. I would rather the behavior remained the same becuase now I have some public resources that I have created that I have to update. Then, there are the public resources that wonât be updated by the OP and left in a broken state. Additionally, this situation is almost the same as a bug report last year that was fixed concerning all terrain methods requiring :ExpandToGrid all of a sudden.
Iâm pretty sure this has changed the behavior of :ReadVoxels because I canât use it on a 4x4x4 voxel region. Thus it has decreased the accuracy of :ReadVoxels
I just tested it and the smallest region it can expand to with ExpandToGrid(4) is a region that is at least 12x12x12 when before you could Read a much smaller region such as 4x4x4 recieve a small array check the occupancy be done with it. But now you cannot do that⌠Thus this picture where the boids are stuck getting within 10 units above the terrain when before it could detect a voxel in a 4x4x4 space and go just above the terrain. Because I had to increase the size of the region they detect for terrain they area it detects is larger, the overhead is larger, the algorithm no longer works as intended and thereâs nothing I can do about it but change methods to raycast.
Also, the rounding mode present in the Terrain by default should be a feature because I use it a lot and the imprecisity of the rounding it makes it more accurate because if you ExpandToGrid cannot attempt to read just an individual 4x4x4 Voxel, while before it worked with the smallest region being 4x4x4 and now it only works with the smallest region being 16x16x16 or something which is 4 voxels.
My point is this is update has changed the behavior of the API and it no longer works as it did before. The performance overhead is 4x worse because you cannot read a small region of 4x4x4.
I will keep this updated as I further suffer from this update, just please revert it, itâs not consistent with the other terrain:FIll methods and it has changed how useful these apis are since you cannot query an individual voxel anymore for occupancy and you have to check a larger array.
âStack endâ just refers to the trace that tells you where the warning is coming from, itâs the same as debug.traceback. It doesnât stop the Script
Exactly. This is just to make it easier for the developer to find where the code that produces the problem is called from.
Looking at the code, this shouldnât be so. But of course, I donât have the same test examples you have. Can you please provide a minimal reproduction sample that shows exact calls and numbers that you are trying to read, and from which terrain, so we can reproduce and debug this?
Well the issue was that ExpandToGrid broke my CheckTerrain function because we can no longer check an individual voxel for occupancy. Via ReadVoxels due to the ExpandToGrid requirement doesnât work with a 4x4x4 region.
local function detecter(region)
local material = workspace.Terrain:ReadVoxels(region, 4)
local check=false
local size = material.Size
local goal={["Water"]=true,["Air"]=true}
for x = 1, size.X, 1 do
for y = 1, size.Y, 1 do
for z = 1, size.Z, 1 do
if goal[material[x][y][z].Name]==nil then -- == "Water" then
return true
end
end
end
end
return check
end
local function CheckTerrain(GoalPosition)
local region = Region3.new(GoalPosition-Vector3.new(2,2,2),GoalPosition+Vector3.new(2,2,2)):ExpandToGrid(4)
while detecter(region) do
GoalPosition=GoalPosition+Vector3.new(0,2,0)
region = Region3.new(GoalPosition-Vector3.new(2,2,2),GoalPosition+Vector3.new(2,2,2)):ExpandToGrid(4)
end
return GoalPosition-Vector3.new(0,4,0)--GoalPosition
end
This method worked, and by using a small region you only get a very short array which allows you to quickly check if the space is occupied by terrain.
So, in conclusion this update has made :ReadVoxels less useful for checking an individual voxel for occupancy.
But, I can just use raycasting to do a similar thing. Except it works slightly different because it finds intersections between the ray and the goal positionâŚ
local params = RaycastParams.new()
params.FilterDescendantsInstances = {workspace.Terrain}
params.FilterType = Enum.RaycastFilterType.Include
params.IgnoreWater = true
local function setRaycastParams(Params:RaycastParams)
params=Params
end
local function raycasts(raycastOrigin:Vector3, raycastDirection:Vector3)
local raycastResult = workspace:Raycast(raycastOrigin, raycastDirection, params)
if raycastResult then
return raycastResult.Position
end
return raycastOrigin
end
local raycastDirection=Vector3.new(0, -20, 0)
--require(game.ReplicatedStorage.GlobalSpells.Questing.Util.CheckTerrain)
return function (raycastOrigin:Vector3,direction:Vector3,IgnoreWater:boolean)
if IgnoreWater then params.IgnoreWater=IgnoreWater end
return raycasts(raycastOrigin, direction or raycastDirection)
end
The new rquirements has made the regions too large to be useful which is why my function is not working as it did before. I could offset the position by an increment so that Iâm still reading a larger region but get the correct y position.
But, using this alternative method by raycasting I know works perfectly, from my more recent projects where I had to perfectly find the surface of the terrain.
Iâm going to just convert my check terrain to this function. It might be more performant than my legacy method of checking the terrain with :ReadVoxels.
But this update extends past :ReadVoxels into :WriteVoxels and :ReplaceMaterial.
Even though it was not aligned to 4 it should still work because the size of the chunk is exactly 4, and a 4x4x4 region doesnât work with expand to grid if the Region is for example Region3.new(Vector3.new(-2,-2,-2),Vector3.new(2,2,2)) yet it would work Region3.new(Vector3.new(0,0,0),Vector3.new(4,4,4)) because that is snapped perfectly to the grid? That isnât very robust with a dynamic position. Yet it works if you have a Region that is twice as large Region3.new(Vector3.new(-4,-4,-4),Vector3.new(4,4,4))? So you would have to snap the original positions used to make the region to a 4x4x4 grid before creating a 4x4x4 region?
My point is itâs not very consistent. the old behavior was fine because itâs still used for the terrain via :FIllBlock, :FillCylinder, :FillWedge, :FillBall, etc this new behavior concerning just :ReadVoxels, :WriteVoxels and :ReplaceMaterial is inconsistent.
It felt like more of a feature, itâs been around for over 8 years. That being said I have replaced my old problem function with a drop in module that does a raycast instead. So Iâm more or less over it. But I still donât think itâs consistent with the Terrain:Fill methods besides them not using a Region3.
But this new solution using raycast is a lot more efficient and accurate. All of my buildings and trees, plants are placed perfectly on top of the terrain.
For posterity here is the technical explanation of what happens:
The docs explicitly say:
Must be aligned to the voxel grid
The fact that this wasnât properly enforced was a bug. It just let the input âfall throughâ while throwing errors about numeric incorrectness internally, which were not surfaced to the API interface. As we are now clearing up those cases, we have to start enforcing this.
The fact that it âmostly worked okâ is pure coincidence, and your code was probably relying on exactly how wrong that worked, but thatâs not a guarantee can keep. Hence this warning.
As for exact numeric errors the -2 .. +2 centered case, if the center is 16, it gives 14 .. 18 and that actually converts to 12 .. 16. Which gives you one voxel, but itâs off-center from where you though youâd get.
That kind of âshiftsâ your entire âworld modelâ is by half a voxel. But thatâs not the worst part of it. The worst part is that the negative numbers are converted with âtowards zeroâ, so if your center is -16, you get -16 .. -12. So itâs shifted in different directions in different octants.
And in the end - the really worst part of it is that, around 0, you can get an empty box. Because -2 .. +2 literally converts to 0âŚ0.
Expand to grid does correct expansion, meaning that the minimum corner of the box snaps downwards (Math.floor()) to the nearest voxel edge, and maximum corner snaps upwards (Math.ceil()). So -2 .. +2 snaps to -4 .. +4. so you get 2 voxels instead of 1. But it is centered where you asked for, and you will never get an empty box (unless you pass in an empty region - where min == max literally).
This has nothing to do with functions like FillBall() - for a simple reason: those donât operate on whole voxels. Your radius can literally be 10.25 and that gives a different ball than 10.0. Or even the coordinates can be non-integer. This warning only applies to functions that operate on whole voxels by manipulating their values directly.