Reading material of terrain at a specific vector

Hi,

recently I was wresting a lot with the Roblox’s Terrain:ReadVoxels() function. Normally it returns you an array but I’ve been trying to use that system (since it’s the only one available) to read a material of the terrain at a specific Vector3 (or position). I tried many things like half voxel region expansion and even adjusting the input position value with offsets and I found my way through with some manual offsets and all that but it works for only one direction. For example if I want to check if the camera is underwater, sometimes it would detect the camera as being underwater even if it’s like a few studs above the water, so I adjusted the inputed camera position to be “lower” so it detects the camera being underwater perfectly, but that’s a lot of hardcoding with specific values that could change over time which isn’t really something I like. And, sure that works when looking at the Y axis, but if the thing is still broken on the X and Z axis (offset with values). I really did try to make it the best possible but yeah the minimum resolution for a region is 4 and idk, maybe someone found a trick to find the middle of this material region/array.

1 Like

Try attaching this script to a part, make sure the script is RunContext Client:

--!strict

-- get the position of the sample point, in this case using a part
local partPosition = script.Parent.Position

-- make up a tiny region around the sample point
local partRegionStart = Vector3.new(partPosition.X - 0.1, partPosition.Y - 0.1, partPosition.Z - 0.1)
local partRegionEnd = Vector3.new(partPosition.X + 0.1, partPosition.Y + 0.1, partPosition.Z + 0.1)

-- create the real sample region by expanding the sample point region
local region = Region3.new(partRegionStart, partRegionEnd):ExpandToGrid(4)

-- work out where the base of the test region is in Y (it may be 1 voxel or 2 or 4 if very near a voxel boundary)
local regionBaseY = region.CFrame.Position.Y - 2 * region.Size.Y / 4 

-- get the material and occupancy arrays for the region
local materials, occupancies = workspace.Terrain:ReadVoxels(region, 4)

-- get the size of the returned arrays from the region
local size = materials.Size

--parse the returned arrays and work out where the sample point is
for x = 1, size.X, 1 do
	for y = 1, size.Y, 1 do
		for z = 1, size.Z, 1 do
			local yPos = regionBaseY + (y - 1) * 4
			if partPosition.Y > yPos and partPosition.Y <= yPos + occupancies[x][y][z] * 4 then
				print("I am in: " .. materials[x][y][z].Name)
			end
		end
	end
end



It’s a bit messy, but I think it should work for situations where the ground is level, when there is the slope of a hill the render surface is interpolated between voxels based on their occupancy and that would require additional code.

1 Like

I was pondering the same particular camera-in-water problem a bit ago, and concluded that the only workaround I could find was to check if the modelled height of the water is above your camera’s position. Note that this is not perfectly accurate and will result in approximation errors at points farther from the model’s origin.

I haven’t researched for any better solutions, and don’t plan on doing so, so this is all I can give for now.

As for finding the material of terrain (let alone at a specific point), I’m not sure how someone could do that without reading voxels :sob:

If anyone has any better ideas, please do share them! I’m also interested in seeing what other solutions people have come up with for this problem!

Sick of editing my clumsy post now, just to add, you could remove the x and z loops to clean it up, reduce the cpu cost and remove the possibility of getting more than one output, it’s really only significant if your sample point is near a voxel boundary. I could have thought about this a lot more and probably should have, but hopefully it illustrates a solution method to the original problem.