Terrain: Write/Read Voxels not perfect

I’m trying to read a region of terrain and copy it over to somewhere else in the game: (original on the right, copied version on the left)

And whilst this is mostly working, it isn’t perfect - in some places the terrain is not perfectly copied over, in these two images for example:
image
image

The way the script works is that it reads one region, takes the materials & occupancies and applies that to another region.
The reason that I think is is something to do with Roblox instead of me is because once given the materials and occupancies, I’m never changing it, but once applied the values seem to have changed.

I also do a check within the script to confirm that they’ve changed, comparing the original material and occupany values to the new ones (gained from reading this new region). The script also shows that both values are not the same.

Some other things:

  • Both regions are the same size
  • Both regions are the same orientation (different position, ofcourse)
  • Both regions fit within the region size boundary
  • Region covers all the terrain

Here is also the script I used if necessary: (the only script in the whole game)

Script
--// Services

local Terrain = workspace.Terrain


--// Variables

local region1Part = workspace.Region1
local region2Part = workspace.Region2


--// Constants

local RESOLUTION = 4


--// Functions

local function roundVector3(vector, toNearest)
	return Vector3.new(
		math.round(vector.X / toNearest) * toNearest,
		math.round(vector.Y / toNearest) * toNearest,
		math.round(vector.Z / toNearest) * toNearest
	)
end

local function roundSize(part, toNearest)
	part.Size = roundVector3(part.Size, RESOLUTION)
end


local function getRegionVolumeVoxels(region)
	local size = region.Size
	return (size.x / RESOLUTION) * (size.y / RESOLUTION) * (size.z / RESOLUTION)
end

local function isRegionTooLargeForReadWriteVoxels(region)
	return getRegionVolumeVoxels(region) > 4_194_304
end

local function isRegionTooLarge(region)
	return getRegionVolumeVoxels(region) > 67_108_864
end


--// Main

roundSize(region1Part, RESOLUTION)
roundSize(region2Part, RESOLUTION)

local readPos1 = roundVector3(region1Part.Position - (region1Part.Size/2), RESOLUTION)
local readPos2 = roundVector3(region1Part.Position + (region1Part.Size/2), RESOLUTION)


local readRegion = Region3.new(readPos1, readPos2)
local materials, occupancies = workspace.Terrain:ReadVoxels(readRegion, RESOLUTION)



local writePos1 = roundVector3(region2Part.Position - (region2Part.Size/2), RESOLUTION)
local writePos2 = roundVector3(region2Part.Position + (region2Part.Size/2), RESOLUTION)

local writeRegion = Region3.new(writePos1, writePos2)
workspace.Terrain:WriteVoxels(writeRegion, RESOLUTION, materials, occupancies)


--// Checks

task.wait(2)
local _materials, _occupancies = workspace.Terrain:ReadVoxels(writeRegion, RESOLUTION)

warn(materials == _materials, occupancies == _occupancies) -- Prints false false (different)
print(isRegionTooLargeForReadWriteVoxels(readRegion)) -- Within boundaries
print(isRegionTooLargeForReadWriteVoxels(writeRegion)) -- Within boundaries

P.S. I also made a #help-and-feedback:scripting-support post, but nobody knew why this was happening. (Post)

Is it identical if you write it back to the same world position?
You might be running into the basic jagged randomness of the obsidian material…

If I wrote it back to the same position, it would delete the old terrain and replace it with the new stuff.

I’m not using the obsidian material here, but I didn’t know that was a thing. Could be down to some other quirk of terrain since I haven’t tested with it too much.

Thanks for the reply though

Looks like the general shape works perfectly, except the visualization of it, they both take up the same amount of space but roblox could be randomizing the jags on the rocks, because if not randomized would be tiling. I don’t think this is fixible unless you paste it to the spot it was before, which defeats the purpose. :confused:

1 Like

This may be intended since Roblox calculates terrain in a 4x4x4 stud grid, this means if one is off by a few studs it will look different.

But shouldn’t the occupancies parameter make sure that all the terrain is in the right place?
Also an error will occur if you try to read/write to a region that isn’t correctly lined up with the terrain grid

What @powersaj is saying above is correct. The visual discrepancy you’re seeing between the two chunks of terrain is due to randomization when we render terrain.

The reason your script is telling you the occupancies and materials are different, is because you’re comparing the tables, rather than their contents. The following would tell you whether the tables are actually equal, or not:

local equal = true

for i, plane in ipairs(materials) do
    for j, row in ipairs(plane) do
        for k, material in ipairs(row) do
            equal = equal and material == _materials[i][j][k]
        end
    end
end

for i, plane in ipairs(occupancies) do
    for j, row in ipairs(plane) do
        for k, occupancy in ipairs(row) do
            equal = equal and occupancy == _occupancies[i][j][k]
        end
    end
end

assert(equal)
3 Likes

So just to be certain, if I were to paste the terrain in the exact same position (say, (0, 0, 0)) it would be the same every single time?

Yes, that should be the case seeming as you’re not actually modifying any existing data.

1 Like

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.