SetCell Alternative?

I’ve been trying to replicate the functionality of SetCell with non-deprecated API functions but I’ve been having a tough time. I’ve taken a look at a couple forum posts on this matter but I haven’t been able to find a good solution. Here is what I currently have

local TweenService = game:GetService("TweenService")
local terrain = game.Workspace.Terrain
local mouse = game.Players.LocalPlayer:GetMouse()

local clicked = Instance.new("Part")
clicked.Anchored = true
clicked.Parent = game.Workspace
clicked.Shape = Enum.PartType.Ball
clicked.Color = Color3.fromRGB(255, 0, 0)
clicked.CanCollide = false
clicked.Transparency = 1

local updated = Instance.new("Part")
updated.Anchored = true
updated.Parent = game.Workspace
updated.Shape = Enum.PartType.Ball
updated.Color = Color3.fromRGB(0, 255, 0)
updated.CanCollide = false
updated.Transparency = 1

local regionPart = Instance.new("Part")
regionPart.Anchored = true
regionPart.Parent = game.Workspace
regionPart.Shape = Enum.PartType.Block
regionPart.Color = Color3.fromRGB(0, 0, 255)
regionPart.CanCollide = false
regionPart.Transparency = 1

local debugParts = {clicked, updated, regionPart}

local function adjust(num)
  local floored = math.floor(num + 0.5)
  return floored - (floored % 4)
end

mouse.Button1Down:connect(function ()

  -- Get position of mouse click
  local recordedPosition = mouse.Hit.p
  clicked.Position = recordedPosition
  print(recordedPosition)
  
  -- Get position of mouse click adjusted to the voxel grid
  local updatedPosition = Vector3.new(adjust(recordedPosition.X), adjust(recordedPosition.Y), adjust(recordedPosition.Z))
  updated.Position = updatedPosition
  print(updatedPosition)
  
  -- Create a decent hole? TODO
  local region = Region3.new(updatedPosition + Vector3.new(0,-4,0), updatedPosition + Vector3.new(4,0,4))
  region = region:ExpandToGrid(4)
  regionPart.CFrame = region.CFrame
  regionPart.Size = region.Size

  -- Get the voxel map
  local materials, occupancies = terrain:ReadVoxels(region, 4)
  local size = materials.Size

  -- Replace the voxel map with air
  for x = 1, size.X do
    for y = 1, size.Y do
      for z = 1, size.Z do
        materials[x][y][1] = Enum.Material.Air
        occupancies[x][y][1] = 0
      end
    end
  end
  terrain:WriteVoxels(region, 4, materials, occupancies)
end)

local function tween(goal)
  local duration = TweenInfo.new(0.5)
  for _, part in ipairs(debugParts) do
    local tween = TweenService:Create(part, duration, goal)
    tween:Play()
  end
end

mouse.Button2Down:connect(function()
  tween({ Transparency = 0 })
end)

mouse.Button2Up:connect(function()
  tween({ Transparency = 1 })
end)

		

where MB1 removes terrain and holding MB2 lets you see where you clicked (red), where my rounder snapped the position to (green), and the region created (blue).

Would appreciate improvements on the code or an alternative approach.

1 Like

Well it took some time but I figured it out.

local TweenService = game:GetService("TweenService")
local Terrain = game.Workspace.Terrain
local Player = game.Players.LocalPlayer
local Mouse = Player:GetMouse()
local Character = Player.CharacterAdded:Wait()

local RANGE = 10

Mouse.Button1Down:connect(function ()
  -- Get position of the terrain clicked on
  local rayOrigin = Character.Head.Position
  local rayDirection = (Mouse.Hit.p - rayOrigin).Unit * RANGE

  local raycastParams = RaycastParams.new()
	raycastParams.FilterDescendantsInstances = {Character}
	raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
  local raycastResult = workspace:Raycast(rayOrigin, rayDirection, raycastParams)

  if not raycastResult or not raycastResult.Instance:IsA("Terrain") then return end

  local recordedPosition = raycastResult.Position
  
  -- Convert from world coordinate to voxel grid position terrain and back to world coordinates for accurate region
  local updatedPosition = Terrain:WorldToCellPreferSolid(recordedPosition) * 4

  -- Get the terrain
  local region = Region3.new(updatedPosition, updatedPosition + Vector3.new(4,4,4)) -- convert vector to voxel region
  region = region:ExpandToGrid(4)
  local materials, occupancies = Terrain:ReadVoxels(region, 4)

  -- Change the terrain
  materials[1][1][1] = Enum.Material.Air
  occupancies[1][1][1] = 0

  -- Commit changes
  Terrain:WriteVoxels(region, 4, materials, occupancies)
end)

There is some extra code in here if anyone would like to make changes to the terrain based on the material or occupancy of the terrain read, so enjoy.

1 Like