null
nil
-
0
0
0
1
0
0
0
1
0
0
0
1
Terrain Tools
[null]
-
false
TerrainBrushScript
= 1 then
fullNeighbor = true
end
if neighbor <= 0 then
emptyNeighbor = true
end
totalNeighbors = totalNeighbors + 1
neighborOccupancies = neighborOccupancies + neighbor
end
end
end
return neighborOccupancies / (totalNeighbors ~= 0 and totalNeighbors or getCell(list, x, y, z, materialsList)), fullNeighbor, emptyNeighbor
end
local function round(n)
return floor(n + .5)
end
function findFace()
local cameraLookVector = game.Workspace.CurrentCamera.CoordinateFrame.lookVector
--[[local absx = abs(cameraLookVector.x) --this code is for 90 plane locking
local absy = abs(cameraLookVector.y)
local absz = abs(cameraLookVector.z)
if absy >= absx and absy >= absz then --preference towards y axis planes
return Vector3.new(0, cameraLookVector.y / absy, 0)
elseif absx >= absz then
return Vector3.new(cameraLookVector.x / absx, 0, 0)
end
return Vector3.new(0, 0, cameraLookVector.z / absz)]]
return Vector3.new(round(cameraLookVector.x), round(cameraLookVector.y), round(cameraLookVector.z)).unit --this code is for 45 degree plane locking
end
function lineToPlaneIntersection(linePoint, lineDirection, planePoint, planeNormal)
local denominator = lineDirection:Dot(planeNormal)
if denominator == 0 then
return linePoint
end
local distance = ((planePoint - linePoint):Dot(planeNormal)) / denominator
return linePoint + lineDirection * distance
end
function updateUsabilityLocks()
if currentTool then
forceSnapToGrid = currentTool.usesMaterials and materialSelection.forceSnapToGrid
updateSnapToGrid()
forcePlaneLock = currentTool.name == 'Add' or currentTool.name == 'Subtract'
updatePlaneLock()
forceDynamicMaterial = currentTool.name == 'Subtract' or currentTool.name == 'Erode' or currentTool.name == 'Paint' or currentTool.name == 'Smooth' or currentTool.name == 'Smoother'
forceDynamicMaterialTo = not (forceDynamicMaterial and currentTool.name == 'Paint')
isDynamic = dynamicMaterial
if forceDynamicMaterial then
isDynamic = forceDynamicMaterialTo
end
forceIgnoreWater = materialSelection.forceIgnoreWater and not isDynamic
if materialSelection.forceIgnoreWater then
forceIgnoreWaterTo = materialSelection.forceIgnoreWaterTo
end
isIgnoreWater = ignoreWater
if forceIgnoreWater then
isIgnoreWater = forceIgnoreWaterTo
end
updateIgnoreWater()
updateDynamicMaterial()
end
end
function operation(centerPoint)
local desiredMaterial = isDynamic and nearMaterial or materialSelection.enum
local radius = selectionSize * .5 * resolution
local minBounds = Vector3.new(
floor((centerPoint.x - radius) / resolution) * resolution,
floor((centerPoint.y - radius) / resolution) * resolution,
floor((centerPoint.z - radius) / resolution) * resolution)
local maxBounds = Vector3.new(
ceil((centerPoint.x + radius) / resolution) * resolution,
ceil((centerPoint.y + radius) / resolution) * resolution,
ceil((centerPoint.z + radius) / resolution) * resolution)
local region = Region3.new(minBounds, maxBounds)
local materials, occupancies = terrain:ReadVoxels(region, resolution)
if modules[currentTool.name] then
if modules[currentTool.name]['operation'] then
local middle = materials[ceil(#materials * .5)] --This little section of code sets nearMaterial to middle of matrix
if middle then --dig X
local middle = middle[ceil(#middle * .5)]
if middle then --dig Y
local middle = middle[ceil(#middle * .5)]
if middle and middle ~= materialAir and middle ~= materialWater then --dig Z
nearMaterial = middle
desiredMaterial = isDynamic and nearMaterial or desiredMaterial
end
end
end
modules[currentTool.name]['operation'](centerPoint, materials, occupancies, resolution, selectionSize, strength, desiredMaterial, brushShape, minBounds, maxBounds)
end
else
local airFillerMaterial = materialAir
local waterHeight = 0
if isIgnoreWater and (currentTool.name == 'Erode' or currentTool.name == 'Subtract') then
--[[local centerPointCell = Vector3.new(floor((centerPoint.x+.5)/resolution) * resolution, floor((centerPoint.y+.5)/resolution) * resolution, floor((centerPoint.z+.5)/resolution) * resolution)
local sampleRegion = Region3.new(centerPointCell - Vector3.new(resolution,resolution,resolution), centerPointCell + Vector3.new(resolution,resolution,resolution))
local sampleMaterials, sampleOccupancies = terrain:ReadVoxels(sampleRegion, resolution)]]
for ix,vx in ipairs(materials) do
for iy,vy in ipairs(vx) do
for iz, vz in ipairs(vy) do
if vz == materialWater then
airFillerMaterial = materialWater
if iy > waterHeight then
waterHeight = iy
end
end
end
end
end
end
for ix, vx in ipairs(occupancies) do
local cellVectorX = minBounds.x + (ix - .5) * resolution - centerPoint.x
for iy, vy in pairs(vx) do
local cellVectorY = minBounds.y + (iy - .5) * resolution - centerPoint.y
for iz, cellOccupancy in pairs(vy) do
local cellVectorZ = minBounds.z + (iz - .5) * resolution - centerPoint.z
local cellMaterial = materials[ix][iy][iz]
local distance = sqrt(cellVectorX * cellVectorX + cellVectorY * cellVectorY + cellVectorZ * cellVectorZ)
local magnitudePercent = 1
local brushOccupancy = 1
if brushShape == 'Sphere' then
magnitudePercent = cos(min(1, distance / (radius + resolution * .5)) * pi * .5)
brushOccupancy = max(0, min(1, (radius + .5 * resolution - distance) / resolution))
elseif brushShape == 'Box' then
if not (snapToGrid or forceSnapToGrid) then
local xOutside = 1 - max(0, abs(cellVectorX / resolution) + .5 - selectionSize * .5)
local yOutside = 1 - max(0, abs(cellVectorY / resolution) + .5 - selectionSize * .5)
local zOutside = 1 - max(0, abs(cellVectorZ / resolution) + .5 - selectionSize * .5)
brushOccupancy = xOutside * yOutside * zOutside
end
end
if cellMaterial ~= materialAir and cellMaterial ~= materialWater and cellMaterial ~= nearMaterial then
nearMaterial = cellMaterial
if isDynamic then
desiredMaterial = nearMaterial
end
end
if isIgnoreWater and cellMaterial == materialWater then
cellMaterial = materialAir
cellOccupancy = 0
end
local airFillerMaterial = waterHeight >= iy and airFillerMaterial or materialAir
if currentTool.name == 'Add' then
if selectionSize <= 2 then
if brushOccupancy >= .5 then
if cellMaterial == materialAir or cellOccupancy <= 0 then
materials[ix][iy][iz] = desiredMaterial
end
occupancies[ix][iy][iz] = 1
end
else
if brushOccupancy > cellOccupancy then
occupancies[ix][iy][iz] = brushOccupancy
end
if brushOccupancy >= .5 and cellMaterial == materialAir then
materials[ix][iy][iz] = desiredMaterial
end
end
elseif currentTool.name == 'Subtract' then
if cellMaterial ~= materialAir then
if selectionSize <= 2 then
if brushOccupancy >= .5 then
occupancies[ix][iy][iz] = airFillerMaterial == materialWater and 1 or 0
materials[ix][iy][iz] = airFillerMaterial
end
else
local desiredOccupancy = max(0,1 - brushOccupancy)
if desiredOccupancy < cellOccupancy then
if desiredOccupancy <= one256th then
occupancies[ix][iy][iz] = airFillerMaterial == materialWater and 1 or 0
materials[ix][iy][iz] = airFillerMaterial
else
occupancies[ix][iy][iz] = min(cellOccupancy, desiredOccupancy)
end
end
end
end
elseif currentTool.name == 'Grow' then
if brushOccupancy >= .5 then --working on
local desiredOccupancy = cellOccupancy
local neighborOccupancies, fullNeighbor, emptyNeighbor = getNeighborOccupancies(occupancies, ix, iy, iz, isIgnoreWater and materials)
if cellOccupancy > 0 or fullNeighbor then --problem if selection size is small.
desiredOccupancy = desiredOccupancy + neighborOccupancies * (strength + .1) * .25 * brushOccupancy * magnitudePercent
end
if cellMaterial == materialAir and desiredOccupancy > 0 then
materials[ix][iy][iz] = desiredMaterial
end
if desiredOccupancy ~= cellOccupancy then
occupancies[ix][iy][iz] = desiredOccupancy
end
end
elseif currentTool.name == 'Erode' then
if cellMaterial ~= materialAir then
local flippedBrushOccupancy = 1 - brushOccupancy
if flippedBrushOccupancy <= .5 then
local desiredOccupancy = cellOccupancy
local emptyNeighbor = false
local neighborOccupancies = 6
for axis = 1, 3 do
for offset = -1, 1, 2 do
local neighbor = nil
local neighborMaterial = nil
if axis == 1 then
neighbor = occupancies[ix + offset] and occupancies[ix + offset][iy][iz]
neighborMaterial = materials[ix + offset] and materials[ix + offset][iy][iz]
elseif axis == 2 then
neighbor = occupancies[ix][iy + offset] and occupancies[ix][iy + offset][iz]
neighborMaterial = materials[ix][iy + offset] and materials[ix][iy + offset][iz]
elseif axis == 3 then
neighbor = occupancies[ix][iy][iz + offset]
neighborMaterial = materials[ix][iy][iz + offset]
end
if neighbor then
if isIgnoreWater and neighborMaterial == materialWater then
neighbor = 0
end
if neighbor <= 0 then
emptyNeighbor = true
end
neighborOccupancies = neighborOccupancies - neighbor
end
end
end
if cellOccupancy < 1 or emptyNeighbor then
desiredOccupancy = max(0,desiredOccupancy - (neighborOccupancies / 6) * (strength + .1) * .25 * brushOccupancy * magnitudePercent)
end
if desiredOccupancy <= one256th then
occupancies[ix][iy][iz] = airFillerMaterial == materialWater and 1 or 0
materials[ix][iy][iz] = airFillerMaterial
else
occupancies[ix][iy][iz] = desiredOccupancy
end
end
end
elseif currentTool.name == 'Paint' then
if brushOccupancy > 0 and cellOccupancy > 0 then
materials[ix][iy][iz] = desiredMaterial
end
end
end
end
end
end
terrain:WriteVoxels(region, resolution, materials, occupancies)
end
function Selected(tool)
if plugin then
plugin:Activate(true)
end
if tool.button then
tool.button:SetActive(true)
lastTool = tool
end
if not userInput.MouseEnabled then
prevCameraType = game.Workspace.CurrentCamera.CameraType
game.Workspace.CurrentCamera.CameraType = Enum.CameraType.Fixed
end
on = true
currentTool = tool
updateUsabilityLocks()
if modules[tool.name] and modules[tool.name]['On'] then
modules[tool.name].On(mouse,Deselected)
end
if not modules[tool.name] or modules[tool.name]['operation'] then
resizeGuiFrame()
titlelabel.Text = tool.name
gui.Parent = coreGui
gui.Frame.Visible = true
local loopTag = {} --using table as a unique value for debouncing
currentLoopTag = loopTag
while currentLoopTag and currentLoopTag == loopTag do
local t = tick()
local radius = selectionSize * .5 * resolution
local cameraPos = mouse.Origin.p
local ignoreModel = nil
if game.Players.LocalPlayer and game.Players.LocalPlayer.Character then
ignoreModel = game.Players.LocalPlayer.Character
end
local mouseRay = Ray.new(cameraPos, mouse.UnitRay.Direction*10000)
local hitObject, mainPoint = game.Workspace:FindPartOnRay(mouseRay, ignoreModel, false, isIgnoreWater)
if tool.name == 'Add' then
mainPoint = mainPoint - mouse.UnitRay.Direction * .05
elseif tool.name == 'Subtract' or tool.name == 'Paint' or tool.name == 'Grow' then
mainPoint = mainPoint + mouse.UnitRay.Direction * .05
end
if mouse.Target == nil then --cage the cursor so that it does not fly away
mainPoint = cameraPos + mouse.UnitRay.Direction * lastCursorDistance --limits the distance of the mainPoint if the mouse is not hitting an object
end
if not mouseDown or click then
lastPlanePoint = mainPoint
lastNormal = findFace()
end
if planeLock or forcePlaneLock then
mainPoint = lineToPlaneIntersection(cameraPos, mouse.UnitRay.Direction, lastPlanePoint, lastNormal)
end
if snapToGrid or forceSnapToGrid then
local snapOffset = Vector3.new(1, 1, 1) * (radius % resolution) --in studs
local tempMainPoint = (mainPoint - snapOffset) / resolution + Vector3.new(.5, .5, .5) --in voxels
mainPoint = Vector3.new(floor(tempMainPoint.x), floor(tempMainPoint.y), floor(tempMainPoint.z)) * resolution + snapOffset
end
if mouseDown then
if click then
firstOperation = t
lastMainPoint = mainPoint
end
if click or t > firstOperation + clickThreshold then
click = false
if downKeys[Enum.KeyCode.LeftAlt] or downKeys[Enum.KeyCode.RightAlt] then
--pick color
local function filterNonTerrain(thing)
if thing and thing == terrain then
return false
end
return true
end
local hit, hitPosition, normal, foundMaterial = deepCast(cameraPos, cameraPos + mouse.UnitRay.Direction*10000, {}, filterNonTerrain, true)
if hit then
for _, materialTable in pairs(materialsTable) do
if materialTable.enum == foundMaterial then
setMaterialSelection(materialTable)
break
end
end
end
else
local difference = mainPoint - lastMainPoint
local dragDistance = (difference).magnitude
local crawlDistance = radius * .5 --Maybe adjustable setting? Considering using a different method of crawling, with a percent rather than a finite distance.
if dragDistance > crawlDistance then
local differenceVector = difference.unit
local dragDistance = min(dragDistance, crawlDistance * 2 + 20) --limiting this so that it does not attempt too many operations within a single drag.
local samples = ceil(dragDistance / crawlDistance - .1)
for i = 1, samples do
operation(lastMainPoint + differenceVector * dragDistance * (i / samples))
end
mainPoint = lastMainPoint + differenceVector * dragDistance
else
operation(mainPoint)
end
lastMainPoint = mainPoint
end
end
end
if not selectionPart then
selectionPart = Instance.new('Part')
selectionPart.Name = 'SelectionPart'
selectionPart.Transparency = 1
selectionPart.TopSurface = 'Smooth'
selectionPart.BottomSurface = 'Smooth'
selectionPart.Anchored = true
selectionPart.CanCollide = false
selectionPart.formFactor = 'Custom'
selectionPart.Size = Vector3.new(1, 1, 1) * selectionSize * resolution + Vector3.new(.1, .1, .1)
selectionPart.Parent = gui
end
if not selectionObject then
selectionObject = Instance.new(brushShape == 'Sphere' and 'SelectionSphere' or 'SelectionBox')
selectionObject.Name = 'SelectionObject'
selectionObject.Color = BrickColor.new('Toothpaste')
selectionObject.SurfaceTransparency = .95 - strength * .3
selectionObject.SurfaceColor = BrickColor.new('Toothpaste')
selectionObject.Adornee = selectionPart
selectionObject.Parent = selectionPart
end
if not userInput.TouchEnabled or mouseDown then
selectionPart.CFrame = CFrame.new(mainPoint)
if planeLock or forcePlaneLock then
local mainPointIntersect = lineToPlaneIntersection(mainPoint, mouse.UnitRay.Direction, lastPlanePoint, lastNormal) --we need to get this otherwise the plane can shift whiel drawing
drawGrid(mainPointIntersect, lastNormal, mouseDown and .8)
end
end
lastCursorDistance = max(20 + selectionSize * resolution * 1.5,(mainPoint - cameraPos).magnitude)
quickWait()
end
end
end
function Deselected()
if not userInput.MouseEnabled then
game.Workspace.CurrentCamera.CameraType = prevCameraType
end
currentLoopTag = nil
gui.Parent = script.Parent
gui.Frame.Visible = false
clearSelection()
clearGrid()
if lastTool then
lastTool.button:SetActive(false)
end
mouseDown = false
on = false
local lastCurrentTool = currentTool
currentTool = nil
if lastCurrentTool and modules[lastCurrentTool.name] and modules[lastCurrentTool.name]['Off'] then
modules[lastCurrentTool.name].Off()
end
end
closeButton.MouseButton1Down:connect(Deselected)
--Touch controls
local fingerTouches = {}
local NumUnsunkTouches = 0
local StartingDiff = nil
local startingSelectionSize = nil
local function OnTouchBegan(input, processed)
fingerTouches[input] = processed
if not processed then
click = true
NumUnsunkTouches = NumUnsunkTouches + 1
end
end
local function OnTouchChanged(input, processed)
if fingerTouches[input] == nil then
fingerTouches[input] = processed
if not processed then
NumUnsunkTouches = NumUnsunkTouches + 1
end
end
if NumUnsunkTouches == 1 then
if fingerTouches[input] == false then
mouseDown = true
end
else
mouseDown = false
end
if NumUnsunkTouches == 2 then
local unsunkTouches = {}
for touch, wasSunk in pairs(fingerTouches) do
if not wasSunk then
table.insert(unsunkTouches, touch)
end
end
if #unsunkTouches == 2 then
local difference = (unsunkTouches[1].Position - unsunkTouches[2].Position).magnitude
if StartingDiff and startingSelectionSize then
local scale = difference/max(0.01, StartingDiff)
selectionSize = max(minSelectionSize, min(maxSelectionSize, startingSelectionSize/scale))
selectionSizeValue.Value = selectionSize
else
StartingDiff = difference
startingSelectionSize = selectionSizeValue.Value
end
end
else
StartingDiff = nil
startingSelectionSize = nil
end
end
local function OnTouchEnded(input, processed)
if fingerTouches[input] == false then
if NumUnsunkTouches == 1 then
mouseDown = false
elseif NumUnsunkTouches == 2 then
StartingDiff = nil
startingSelectionSize = nil
mouseDown = true
end
end
if fingerTouches[input] ~= nil and fingerTouches[input] == false then
NumUnsunkTouches = NumUnsunkTouches - 1
end
fingerTouches[input] = nil
end
-- Input Handling
userInput.InputBegan:connect(function(event, soaked)
downKeys[event.KeyCode] = true
if event.UserInputType == Enum.UserInputType.MouseButton1 and not soaked and on then
mouseDown = true
click = true
elseif event.UserInputType == Enum.UserInputType.Touch and on then
OnTouchBegan(event, soaked)
end
end)
userInput.InputChanged:connect(function(input, processed)
if input.UserInputType == Enum.UserInputType.Touch then
OnTouchChanged(input, processed)
end
end)
userInput.InputEnded:connect(function(event, soaked)
downKeys[event.KeyCode] = nil
if event.UserInputType == Enum.UserInputType.MouseButton1 and mouseDown then
mouseDown = false
if changeHistory then
changeHistory:SetWaypoint('Terrain '..currentTool.name)
end
elseif event.UserInputType == Enum.UserInputType.Touch then
OnTouchEnded(event, soaked)
end
end)
function scrollwheel(change)
if on then
if downKeys[Enum.KeyCode.LeftShift] or downKeys[Enum.KeyCode.RightShift] then
selectionSize = max(minSelectionSize, min(maxSelectionSize, selectionSize + change))
selectionSizeValue.Value = selectionSize
end
if downKeys[Enum.KeyCode.LeftControl] or downKeys[Enum.KeyCode.RightControl] then
strength = max(0, min(1, strength + change * (1/(maxSelectionSize-minSelectionSize))))
strengthValue.Value = round(strength * 100 + 1)
end
end
end
mouse.WheelForward:connect(function()
scrollwheel(1)
end)
mouse.WheelBackward:connect(function()
scrollwheel(-1)
end)
if plugin then
plugin.Deactivation:connect(function()
if on then
Deselected()
end
end)
end
setBrushShape(brushShape)
setMaterialSelection(materialSelection)
updatePlaneLock()
updateSnapToGrid()
updateDynamicMaterial()
-- Reset keyboard status on lost focus as key release may never come blocked by popups etc.
userInput.WindowFocusReleased:connect(function()
downKeys = {}
end)
end
]]>
-
SmootherModule
= 1 then
fullNeighbor = true
end
if neighbor <= 0 then
emptyNeighbor = true
end
neighbor = extendRange(neighbor)
totalNeighbors = totalNeighbors + 1
neighborOccupancies = neighborOccupancies + neighbor
end
end
end
return neighborOccupancies / (totalNeighbors > 0 and totalNeighbors or extendRange(getCell(list, x, y, z))), fullNeighbor, emptyNeighbor
end
function getNeighborOccupancies(list, x, y, z, includeSelf, range)
local fullNeighbor = false
local emptyNeighbor = false
local range = range or 1
local neighborOccupancies = 0
local totalNeighbors = 0
local sqrt = math.sqrt
for ix = -range, range do
for iy = -range, range do
for iz = -range, range do
if includeSelf or not (ix == 0 and iy == 0 and iz == 0) then
local neighbor = getCell(list, x + ix, y + iy, z + iz)
if neighbor then
local distanceScale = 1 - (sqrt(ix * ix + iy * iy + iz * iz) / (range * 2))
if neighbor >= 1 then
fullNeighbor = true
end
if neighbor <= 0 then
emptyNeighbor = true
end
neighbor = extendRange(neighbor)
totalNeighbors = totalNeighbors + 1 * distanceScale
neighborOccupancies = neighborOccupancies + neighbor * distanceScale
end
end
end
end
end
return neighborOccupancies / (totalNeighbors > 0 and totalNeighbors or extendRange(getCell(list, x, y, z))), fullNeighbor, emptyNeighbor
end
function operation(centerPoint, materials, occupancies, resolution, selectionSize, strength, desiredMaterial, brushType, minBounds, maxBounds)
local region = Region3.new(minBounds, maxBounds)
local readMaterials, readOccupancies = terrain:ReadVoxels(region, resolution)
local radius = selectionSize * .5 * resolution
local materialAir = Enum.Material.Air
for ix, vx in ipairs(readOccupancies) do
local cellVectorX = minBounds.x + (ix - .5) * resolution - centerPoint.x
for iy, vy in pairs(vx) do
local cellVectorY = minBounds.y + (iy - .5) * resolution - centerPoint.y
for iz, cellOccupancy in pairs(vy) do
local cellVectorZ = minBounds.z + (iz - .5) * resolution - centerPoint.z
local cellMaterial = materials[ix][iy][iz]
local distance = math.sqrt(cellVectorX * cellVectorX + cellVectorY * cellVectorY + cellVectorZ * cellVectorZ)
local magnitudePercent = 1
local brushOccupancy = 1
if brushType == 'Sphere' then
magnitudePercent = math.cos(math.min(1, distance / (radius + resolution * .5)) * math.pi * .5)
brushOccupancy = math.max(0, math.min(1, (radius + .5 * resolution - distance) / resolution))
elseif brushType == 'Box' then
--leave as default
end
if brushOccupancy >= .5 then
local neighborOccupancies, fullNeighbor, emptyNeighbor = getNeighborOccupancies(readOccupancies, ix, iy, iz, true, 1)
local difference = (neighborOccupancies - cellOccupancy) * (strength + .1) * .5 * brushOccupancy * magnitudePercent
if not fullNeighbor and difference > 0 then
difference = 0
elseif not emptyNeighbor and difference < 0 then
difference = 0
end
if readMaterials[ix][iy][iz] == materialAir or cellOccupancy <= 0 and difference > 0 then
materials[ix][iy][iz] = desiredMaterial
end
if difference ~= 0 then
occupancies[ix][iy][iz] = math.max(0, math.min(1, cellOccupancy + difference))
end
end
end
end
end
end
return {
['operation'] = operation
}
]]>
-
RegionEditorModule
1 and 1) or unclamped
--At first I thought this didn't need to be clamped because the terrain clamps that anways.
--But I then realized I am using this number a bit more before handing it to terrain.
--After doing some tests. Clamping is necessary for artificial structures being streched. If unclamped, rounding of artificial edges occurs.
--return (a+(b-a)*p-.5)*exaggeration+.5
--Maybe this extra dimension of unclamping might be desired for natural terrain, but not artificuial?
end
function updateDragOperation()
local dragVector = dragVector or Vector3.new(0,0,0)
local temporaryStart = selectionStart
local temporaryEnd = selectionEnd
if tool == 'Resize' then
if dragStart then
temporaryStart = Vector3.new(
math.min(
math.max(temporaryStart.x+dragVector.x,temporaryEnd.x-regionLengthLimit),
temporaryEnd.x),
math.min(
math.max(temporaryStart.y+dragVector.y,temporaryEnd.y-regionLengthLimit),
temporaryEnd.y),
math.min(
math.max(temporaryStart.z+dragVector.z,temporaryEnd.z-regionLengthLimit),
temporaryEnd.z)
)
else
temporaryEnd = Vector3.new(
math.max(
math.min(temporaryEnd.x+dragVector.x,temporaryStart.x+regionLengthLimit),
temporaryStart.x),
math.max(
math.min(temporaryEnd.y+dragVector.y,temporaryStart.y+regionLengthLimit),
temporaryStart.y),
math.max(
math.min(temporaryEnd.z+dragVector.z,temporaryStart.z+regionLengthLimit),
temporaryStart.z)
)
end
if mode == 'Edit' then
--[[local loopx = #lockedMaterials --Tiling resize --fun but not too many use cases with natural terrain.
local loopy = #lockedMaterials[1]
local loopz = #lockedMaterials[1][1]
local tempRegionSize = Vector3.new(1,1,1) + temporaryEnd - temporaryStart
local newMat = {}
local newOcc = {}
local offsetx = -1
local offsety = -1
local offsetz = -1
if dragStart then
offsetx = offsetx + (-tempRegionSize.x % loopx)
offsety = offsety + (-tempRegionSize.y % loopy)
offsetz = offsetz + (-tempRegionSize.z % loopz)
end
for x=1, tempRegionSize.x do
local targetx = (offsetx + x) % loopx + 1
local xtm = {}
local xto = {}
for y=1, tempRegionSize.y do
local targety = (offsety + y) % loopy + 1
local ytm = {}
local yto = {}
for z=1, tempRegionSize.z do
local targetz = (offsetz + z) % loopz + 1
ytm[z] = lockedMaterials[targetx][targety][targetz]
yto[z] = lockedOccupancies[targetx][targety][targetz]
end
xtm[y] = ytm
xto[y] = yto
end
newMat[x] = xtm
newOcc[x] = xto
end]]
--[[local loopx = #lockedMaterials --Scaling closest neightbor resize --not perfect
local loopy = #lockedMaterials[1]
local loopz = #lockedMaterials[1][1]
local tempRegionSize = Vector3.new(1,1,1) + temporaryEnd - temporaryStart
local tempSizeX = tempRegionSize.x
local tempSizeY = tempRegionSize.y
local tempSizeZ = tempRegionSize.z
local roundx = tempSizeX < loopx and math.floor or math.ceil
local roundy = tempSizeY < loopy and math.floor or math.ceil
local roundz = tempSizeZ < loopz and math.floor or math.ceil
local newMat = {}
local newOcc = {}
for x=1, tempSizeX do
local targetx = roundx(x/tempSizeX*loopx)
local xtm = {}
local xto = {}
for y=1, tempSizeY do
local targety = roundy(y/tempSizeY*loopy)
local ytm = {}
local yto = {}
for z=1, tempSizeZ do
local targetz = roundz(z/tempSizeZ*loopz)
ytm[z] = lockedMaterials[targetx][targety][targetz]
yto[z] = lockedOccupancies[targetx][targety][targetz]
end
xtm[y] = ytm
xto[y] = yto
end
newMat[x] = xtm
newOcc[x] = xto
end]]
local region = Region3.new((temporaryStart - Vector3.new(1,1,1)) * resolution, temporaryEnd * resolution)
if behindThis then
terrain:WriteVoxels(behindThis.region, resolution, behindThis.materials, behindThis.occupancies)
else
if selectionStart and selectionEnd then
local region = Region3.new((selectionStart - Vector3.new(1,1,1)) * resolution, selectionEnd * resolution)
local regionSize = region.Size / resolution
terrain:WriteVoxels(region, resolution, make3DTable(regionSize,materialAir), make3DTable(regionSize,0))
end
end
behindThis = {}
behindThis.region = region
behindThis.materials, behindThis.occupancies = terrain:ReadVoxels(region, resolution)
local behindMaterials, behindOccupancies = behindThis.materials, behindThis.occupancies
local loopx = #lockedMaterials - 1
local loopy = #lockedMaterials[1] - 1
local loopz = #lockedMaterials[1][1] - 1
local tempRegionSize = Vector3.new(1,1,1) + temporaryEnd - temporaryStart
local tempSizeX = tempRegionSize.x
local tempSizeY = tempRegionSize.y
local tempSizeZ = tempRegionSize.z
local newMat = {}
local newOcc = {}
for x=1, tempSizeX do
local scalex = (x-1)/(tempSizeX-1)*loopx
if scalex ~= scalex then
scalex = 0
end
local startx = floor(scalex)+1
local endx = startx+1
local interpScalex = scalex-startx+1
if startx > loopx then
endx = startx
end
local xtm = {}
local xto = {}
for y=1, tempSizeY do
local scaley = (y-1)/(tempSizeY-1)*loopy
if scaley ~= scaley then
scaley = 0
end
local starty = floor(scaley)+1
local endy = starty+1
local interpScaley = scaley-starty+1
if starty > loopy then
endy = starty
end
local ytm = {}
local yto = {}
for z=1, tempSizeZ do
local scalez = (z-1)/(tempSizeZ-1)*loopz --consider adding 1 here and removing +1's elsewhere
if scalez ~= scalez then --undefined check
scalez = 0
end
local startz = floor(scalez)+1
local endz = startz+1
local interpScalez = scalez-startz+1
if startz > loopz then
endz = startz
end
local interpz1 = exaggeratedLinInterp(lockedOccupancies[startx][starty][startz],lockedOccupancies[startx][starty][endz],interpScalez, tempSizeZ/(loopz+1))
local interpz2 = exaggeratedLinInterp(lockedOccupancies[startx][endy][startz],lockedOccupancies[startx][endy][endz],interpScalez, tempSizeZ/(loopz+1))
local interpz3 = exaggeratedLinInterp(lockedOccupancies[endx][starty][startz],lockedOccupancies[endx][starty][endz],interpScalez, tempSizeZ/(loopz+1))
local interpz4 = exaggeratedLinInterp(lockedOccupancies[endx][endy][startz],lockedOccupancies[endx][endy][endz],interpScalez, tempSizeZ/(loopz+1))
local interpy1 = exaggeratedLinInterp(interpz1,interpz2,interpScaley, tempSizeY/(loopy+1))
local interpy2 = exaggeratedLinInterp(interpz3,interpz4,interpScaley, tempSizeY/(loopy+1))
local interpx1 = exaggeratedLinInterp(interpy1,interpy2,interpScalex, tempSizeX/(loopx+1))
local newMaterial = lockedMaterials[round(scalex)+1][round(scaley)+1][round(scalez)+1]
if fillAir and newMaterial == materialAir then
ytm[z]=behindMaterials[x][y][z]
yto[z]=behindOccupancies[x][y][z]
elseif fillWater and newMaterial == materialWater and behindMaterials[x][y][z] ~= materialAir then
ytm[z]=behindMaterials[x][y][z]
yto[z]=behindOccupancies[x][y][z]
else
ytm[z]=newMaterial
yto[z]=interpx1
end
end
xtm[y] = ytm
xto[y] = yto
end
newMat[x] = xtm
newOcc[x] = xto
end
terrain:WriteVoxels(region, resolution, newMat, newOcc)
else
behindThis = nil
end
elseif tool == 'Move' then
temporaryStart = temporaryStart + dragVector
temporaryEnd = temporaryEnd + dragVector
if mode == 'Edit' then
local region = Region3.new((temporaryStart - Vector3.new(1,1,1)) * resolution, temporaryEnd * resolution)
if behindThis then
terrain:WriteVoxels(behindThis.region, resolution, behindThis.materials, behindThis.occupancies)
else
if selectionStart and selectionEnd then
local region = Region3.new((selectionStart - Vector3.new(1,1,1)) * resolution, selectionEnd * resolution)
local regionSize = region.Size / resolution
terrain:WriteVoxels(region, resolution, make3DTable(regionSize,materialAir), make3DTable(regionSize,0))
end
end
behindThis = {}
behindThis.region = region
behindThis.materials, behindThis.occupancies = terrain:ReadVoxels(region, resolution)
local behindMaterials, behindOccupancies = behindThis.materials, behindThis.occupancies
if not (fillAir or fillWater) then
terrain:WriteVoxels(region, resolution, lockedMaterials, lockedOccupancies)
else
local newMat = {}
local newOcc = {}
for x,xv in ipairs(lockedMaterials) do
local xtm = {}
local xto = {}
for y,yv in ipairs(xv) do
local ytm = {}
local yto = {}
for z,zv in ipairs(yv) do
if fillAir and zv == materialAir then
ytm[z]=behindMaterials[x][y][z]
yto[z]=behindOccupancies[x][y][z]
elseif fillWater and zv == materialWater and behindMaterials[x][y][z] ~= materialAir then
ytm[z]=behindMaterials[x][y][z]
yto[z]=behindOccupancies[x][y][z]
else
ytm[z]=lockedMaterials[x][y][z]
yto[z]=lockedOccupancies[x][y][z]
end
end
xtm[y] = ytm
xto[y] = yto
end
newMat[x] = xtm
newOcc[x] = xto
end
terrain:WriteVoxels(region, resolution, newMat, newOcc)
end
end
end
renderSelection(temporaryStart,temporaryEnd)
end
function dragHandles(face, delta)
local normal = faceToNormal[face]
local delta = delta
local newDragVector = normal * floor((delta + .5) / resolution)
dragStart = normal.x < 0 or normal.y < 0 or normal.z < 0 --This determines if we are dragging a side on the min or max bounds
if newDragVector ~= dragVector then
dragVector = newDragVector
updateDragOperation()
end
end
local function rotate(mx,x,my,y,rotation)
if rotation == 1 then
return my + 1 - y, x
elseif rotation == 2 then
return mx + 1 - x, my + 1 - y
elseif rotation == 3 then
return y, mx + 1 - x
end
return x,y
end
function updateRotateOperation()
local dragAngle = dragAngle or 0
local rotationCFrame = CFrame.Angles(
axis ~= 'X' and 0 or dragAngle * rotationInterval,
axis ~= 'Y' and 0 or dragAngle * rotationInterval,
axis ~= 'Z' and 0 or dragAngle * rotationInterval
)
local temporarySize = Vector3.new(1,1,1) + selectionEnd - selectionStart
local centerOffset = Vector3.new(ceil(temporarySize.x * .5), ceil(temporarySize.y * .5), ceil(temporarySize.z * .5))
temporarySize = rotationCFrame * temporarySize
local temporarySizeX = round(math.abs(temporarySize.x)) --I need to round these because of floating point imprecision
local temporarySizeY = round(math.abs(temporarySize.y))
local temporarySizeZ = round(math.abs(temporarySize.z))
centerOffset = centerOffset - Vector3.new(ceil(temporarySizeX * .5), ceil(temporarySizeY * .5), ceil(temporarySizeZ * .5))
local temporaryEnd = selectionStart + centerOffset + Vector3.new(temporarySizeX, temporarySizeY, temporarySizeZ) - Vector3.new(1, 1, 1)
local temporaryStart = selectionStart + centerOffset
if mode == 'Edit' then
local region = Region3.new((temporaryStart - Vector3.new(1,1,1)) * resolution, temporaryEnd * resolution)
if behindThis then
terrain:WriteVoxels(behindThis.region, resolution, behindThis.materials, behindThis.occupancies)
else
if selectionStart and selectionEnd then
local region = Region3.new((selectionStart - Vector3.new(1,1,1)) * resolution, selectionEnd * resolution)
local regionSize = region.Size / resolution
terrain:WriteVoxels(region, resolution, make3DTable(regionSize,materialAir), make3DTable(regionSize,0))
end
--local regionSize = lockedRegion.Size / resolution
--terrain:WriteVoxels(lockedRegion, resolution, make3DTable(regionSize,materialAir), make3DTable(regionSize,0))
end
behindThis = {}
behindThis.region = region
behindThis.materials, behindThis.occupancies = terrain:ReadVoxels(region, resolution)
local newMat = {}
local newOcc = {}
for x=1, temporarySizeX do
local xtm = {}
local xto = {}
for y=1, temporarySizeY do
local ytm = {}
local yto = {}
for z=1, temporarySizeZ do
local targetx = x
local targety = y
local targetz = z
if axis == 'Y' then --prioritize y because I know this is the primary rotation axis
targetx, targetz = rotate(temporarySizeX, x, temporarySizeZ, z, dragAngle)
elseif axis == 'X' then
targetz, targety = rotate(temporarySizeZ, z, temporarySizeY, y, dragAngle)
elseif axis == 'Z' then
targety, targetx = rotate(temporarySizeY, y, temporarySizeX, x, dragAngle)
end
local newMaterial = lockedMaterials[targetx][targety][targetz]
if fillAir and newMaterial == materialAir then
ytm[z]=behindThis.materials[x][y][z]
yto[z]=behindThis.occupancies[x][y][z]
elseif fillWater and newMaterial == materialWater and behindThis.materials[x][y][z] ~= materialAir then
ytm[z]=behindThis.materials[x][y][z]
yto[z]=behindThis.occupancies[x][y][z]
else
ytm[z]=newMaterial
yto[z]=lockedOccupancies[targetx][targety][targetz]
end
end
xtm[y] = ytm
xto[y] = yto
end
newMat[x] = xtm
newOcc[x] = xto
end
terrain:WriteVoxels(region, resolution, newMat, newOcc)
end
renderSelection(temporaryStart,temporaryEnd,rotationCFrame)
end
function dragArcHandles(rotationAxis,relativeAngle,deltaRadius)
axis = rotationAxis.Name
local newDragAngle = round(relativeAngle / rotationInterval) % 4
if newDragAngle ~= dragAngle then
dragAngle = newDragAngle
updateRotateOperation()
end
end
buttonCopy.MouseButton1Down:connect(function()
if selectionStart and selectionEnd then
local selectionStartInt16=Vector3int16.new(selectionStart.x-1,selectionStart.y-1,selectionStart.z-1)
local selectionEndInt16=Vector3int16.new(selectionEnd.x-1,selectionEnd.y-1,selectionEnd.z-1)
local region = Region3int16.new(selectionStartInt16,selectionEndInt16)
copyRegion = terrain:CopyRegion(region)
selectionEffect(nil,nil,'New Yeller',1,1.2,.5)
end
end)
buttonPaste.MouseButton1Down:connect(function()
if copyRegion then
selectionEnd=selectionStart+copyRegion.SizeInCells-Vector3.new(1,1,1)
local region = Region3.new((selectionStart - Vector3.new(1,1,1)) * resolution, selectionEnd * resolution)
behindThis = {}
behindThis.region = region
behindThis.materials, behindThis.occupancies = terrain:ReadVoxels(region, resolution)
terrain:PasteRegion(copyRegion,Vector3int16.new(selectionStart.x-1,selectionStart.y-1,selectionStart.z-1),true)
setButton('Move')
if utilityModule:GetUsingAsPlugin() then
changeHistory:SetWaypoint('Terrain Paste')
end
selectionEffect(nil,nil,'Lime green',1.2,1,.5)
end
end)
buttonDelete.MouseButton1Down:connect(function()
if selectionStart and selectionEnd then
local region = Region3.new((selectionStart - Vector3.new(1,1,1)) * resolution, selectionEnd * resolution)
local regionSize = region.Size / resolution
local emptyMaterialMap = make3DTable(regionSize,materialAir)
local emptyOccupancyMap = make3DTable(regionSize,0)
--[[behindThis = {}
behindThis.region = region
behindThis.materials, behindThis.occupancies = emptyMaterialMap, emptyOccupancyMap
terrain:WriteVoxels(region, resolution, emptyMaterialMap, emptyOccupancyMap)]]
if behindThis then
terrain:WriteVoxels(behindThis.region, resolution, behindThis.materials, behindThis.occupancies)
else
if selectionStart and selectionEnd then
terrain:WriteVoxels(region, resolution, emptyMaterialMap, emptyOccupancyMap)
end
end
behindThis = {}
behindThis.region = region
behindThis.materials, behindThis.occupancies = terrain:ReadVoxels(region, resolution)
--[[lockedRegion = region
lockedMaterials, lockedOccupancies = emptyMaterialMap, emptyOccupancyMap]]
local oldStart, oldEnd = selectionStart, selectionEnd
selectionStart, selectionEnd = nil, nil
setButton('Select')
if utilityModule:GetUsingAsPlugin() then
changeHistory:SetWaypoint('Terrain Delete')
end
selectionEffect(oldStart,oldEnd,'Really red',1,1.2,.5)
end
end)
buttonFill.MouseButton1Down:connect(function()
fillFrame.Visible = not fillFrame.Visible
end)
buttonFillFrameClose.MouseButton1Down:connect(function()
fillFrame.Visible = false
end)
buttonFillConfirm.MouseButton1Down:connect(function()
if selectionStart and selectionEnd then
local region = Region3.new((selectionStart - Vector3.new(1,1,1)) * resolution, selectionEnd * resolution)
local regionSize = region.Size / resolution
local beforeMaterialMap, beforeOccupancyMap = terrain:ReadVoxels(region, resolution)
local newMaterialMap = {}
local newOccupancyMap = {}
for x = 1, regionSize.x do
local xtm = {}
local xto = {}
for y = 1, regionSize.y do
local ytm = {}
local yto = {}
for z = 1, regionSize.z do
local beforeMaterial = beforeMaterialMap[x][y][z]
if beforeMaterial == materialAir or beforeOccupancyMap[x][y][z] == 0 or not fillAir then --'fillAir' variable is actually 'Merge Empty' to the user
ytm[z] = materialSelection.enum
else
ytm[z] = beforeMaterial
end
yto[z] = 1
end
xtm[y] = ytm
xto[y] = yto
end
newMaterialMap[x] = xtm
newOccupancyMap[x] = xto
end
terrain:WriteVoxels(region, resolution, newMaterialMap, newOccupancyMap)
behindThis = {}
behindThis.region = region
behindThis.materials, behindThis.occupancies = terrain:ReadVoxels(region, resolution)
fillFrame.Visible = false
if utilityModule:GetUsingAsPlugin() then
changeHistory:SetWaypoint('Terrain Fill')
end
selectionEffect(nil,nil,'Lime green',1.2,1,.5)
end
end)
function selectionEffect(temporaryStart,temporaryEnd,color,sizeFrom,sizeTo,effectTime)
local temporaryStart = temporaryStart or selectionStart
local temporaryEnd = temporaryEnd or selectionEnd
local effectPart = Instance.new('Part')
effectPart.Name = 'EffectPart'
effectPart.Transparency = 1
effectPart.TopSurface = 'Smooth'
effectPart.BottomSurface = 'Smooth'
effectPart.Anchored = true
effectPart.CanCollide = false
effectPart.formFactor = 'Custom'
effectPart.Parent = gui
local selectionEffectObject = Instance.new('SelectionBox')
selectionEffectObject.Name = 'SelectionObject'
selectionEffectObject.Transparency = 1
selectionEffectObject.SurfaceTransparency = .75
selectionEffectObject.SurfaceColor = BrickColor.new(color)
selectionEffectObject.Adornee = effectPart
selectionEffectObject.Parent = effectPart
local baseSize = ((temporaryEnd - temporaryStart + Vector3.new(1,1,1)) * resolution + Vector3.new(.21,.21,.21))
effectPart.CFrame = CFrame.new((temporaryStart + temporaryEnd - Vector3.new(1, 1, 1)) * .5 * resolution)
effectPart.Size = baseSize * sizeFrom
local endTick=tick()+effectTime
while endTick>tick() do
local percent=1-(endTick-tick())/effectTime
selectionEffectObject.SurfaceTransparency = .75 + percent*.25
effectPart.Size = baseSize * (sizeFrom+(sizeTo-sizeFrom)*percent)
wait()
end
effectPart:Destroy()
end
function renderSelection(temporaryStart,temporaryEnd,rotation)
local temporaryStart = temporaryStart or selectionStart
local temporaryEnd = temporaryEnd or selectionEnd
local seeable = false
if temporaryStart and temporaryEnd and selectionPart then
seeable = true
local temporarySize = ((temporaryEnd - temporaryStart + Vector3.new(1,1,1)) * resolution + Vector3.new(.2,.2,.2))
if rotation then
local rotatedSize = rotation * temporarySize
temporarySize = Vector3.new(math.abs(rotatedSize.x), math.abs(rotatedSize.y), math.abs(rotatedSize.z))
end
selectionPart.Size = temporarySize
selectionPart.CFrame = CFrame.new((temporaryStart + temporaryEnd - Vector3.new(1, 1, 1)) * .5 * resolution) * (rotation or CFrame.new(0,0,0))
end
if selectionObject then
selectionObject.Visible = seeable
selectionObject.Color = BrickColor.new(mode == 'Select' and 'Toothpaste' or editColor1)
selectionObject.SurfaceColor = BrickColor.new(mode == 'Select' and 'Toothpaste' or editColor1)
end
if selectionHandles then
selectionHandles.Visible = seeable and (tool == 'Move' or tool == 'Resize')
selectionHandles.Color = BrickColor.new(mode == 'Select' and 'Cyan' or editColor2)
selectionHandles.Style = tool == 'Move' and Enum.HandlesStyle.Movement or Enum.HandlesStyle.Resize
end
if selectionArcHandles then
selectionArcHandles.Visible = seeable and tool == 'Rotate'
selectionArcHandles.Color = BrickColor.new(mode == 'Select' and 'Cyan' or editColor2)
end
end
function Selected()
--plugin:Activate(true)
--regionEditButton:SetActive(true)
on = true
gui.Parent = coreGui
gui.Frame.Visible = true
if not selectionPart then
selectionPart = Instance.new('Part')
selectionPart.Name = 'SelectionPart'
selectionPart.Transparency = 1
selectionPart.TopSurface = 'Smooth'
selectionPart.BottomSurface = 'Smooth'
selectionPart.Anchored = true
selectionPart.CanCollide = false
selectionPart.formFactor = 'Custom'
selectionPart.Parent = gui
end
if not selectionObject then
selectionObject = Instance.new('SelectionBox')
selectionObject.Name = 'SelectionObject'
selectionObject.Color = BrickColor.new(mode == 'Select' and 'Toothpaste' or editColor1)
selectionObject.SurfaceTransparency = .85
selectionObject.SurfaceColor = BrickColor.new(mode == 'Select' and 'Toothpaste' or editColor1)
selectionObject.Adornee = selectionPart
selectionObject.Visible = false
selectionObject.Parent = selectionPart
end
if not selectionHandles then
selectionHandles = Instance.new('Handles')
selectionHandles.Name = 'SelectionHandles'
selectionHandles.Color = BrickColor.new(mode == 'Select' and 'Toothpaste' or editColor2)
selectionHandles.Adornee = selectionPart
selectionHandles.Visible = false
selectionHandles.Parent = coreGui--game.Workspace--guiFrame--selectionPart
selectionHandles.MouseDrag:connect(dragHandles)
end
if not selectionArcHandles then
selectionArcHandles = Instance.new('ArcHandles')
selectionArcHandles.Name = 'SelectionArcHandles'
selectionArcHandles.Color = BrickColor.new(mode == 'Select' and 'Toothpaste' or editColor2)
selectionArcHandles.Adornee = selectionPart
selectionArcHandles.Visible = false
selectionArcHandles.Parent = coreGui--game.Workspace--guiFrame--selectionPart
selectionArcHandles.MouseDrag:connect(dragArcHandles)
end
renderSelection()
setButton(button)
end
function Deselected()
setButton('Select')
gui.Parent = script.Parent
gui.Frame.Visible = false
clearSelection()
--regionEditButton:SetActive(false)
behindThis = nil
on = false
if turnOff then
turnOff()
end
end
mouse.Button1Down:connect(function()
if on and mode == 'Select' then
mouseDown = true
behindThis = nil
local mousePos = mouse.Hit.p + mouse.UnitRay.Direction * .05
if mouse.Target == nil then --cage the cursor so that it does not fly away
mousePos = game.Workspace.CurrentCamera.CoordinateFrame.p + mouse.UnitRay.Direction * 100
end
clickStart = positionWorldToVoxel(mousePos)
local thisDownLoop = {}
downLoop = thisDownLoop
while thisDownLoop == downLoop and mouseDown and on and mode == 'Select' do
local mousePos = mouse.Hit.p + mouse.UnitRay.Direction * .05
if mouse.Target == nil then --cage the cursor so that it does not fly away
mousePos = game.Workspace.CurrentCamera.CoordinateFrame.p + mouse.UnitRay.Direction * 100
end
local voxelCurrent = positionWorldToVoxel(mousePos)
voxelCurrent = Vector3.new(
math.max(math.min(voxelCurrent.x,clickStart.x+regionLengthLimit),clickStart.x-regionLengthLimit),
math.max(math.min(voxelCurrent.y,clickStart.y+regionLengthLimit),clickStart.y-regionLengthLimit),
math.max(math.min(voxelCurrent.z,clickStart.z+regionLengthLimit),clickStart.z-regionLengthLimit))
selectionStart = Vector3.new(math.min(clickStart.x, voxelCurrent.x), math.min(clickStart.y, voxelCurrent.y), math.min(clickStart.z, voxelCurrent.z))
selectionEnd = Vector3.new(math.max(clickStart.x, voxelCurrent.x), math.max(clickStart.y, voxelCurrent.y), math.max(clickStart.z, voxelCurrent.z))
renderSelection()
quickWait()
end
end
end)
mouse.Button1Up:connect(function()
mouseDown = false
if dragVector and dragVector.magnitude > 0 then
if tool == 'Resize' then
--[[if dragStart then
selectionStart = Vector3.new(math.min(selectionStart.x+dragVector.x,selectionEnd.x),math.min(selectionStart.y+dragVector.y,selectionEnd.y),math.min(selectionStart.z+dragVector.z,selectionEnd.z))
else
selectionEnd = Vector3.new(math.max(selectionEnd.x+dragVector.x,selectionStart.x),math.max(selectionEnd.y+dragVector.y,selectionStart.y),math.max(selectionEnd.z+dragVector.z,selectionStart.z))
end]]
if dragStart then
selectionStart = Vector3.new(
math.min(
math.max(selectionStart.x+dragVector.x,selectionEnd.x-regionLengthLimit),
selectionEnd.x),
math.min(
math.max(selectionStart.y+dragVector.y,selectionEnd.y-regionLengthLimit),
selectionEnd.y),
math.min(
math.max(selectionStart.z+dragVector.z,selectionEnd.z-regionLengthLimit),
selectionEnd.z)
)
else
selectionEnd = Vector3.new(
math.max(
math.min(selectionEnd.x+dragVector.x,selectionStart.x+regionLengthLimit),
selectionStart.x),
math.max(
math.min(selectionEnd.y+dragVector.y,selectionStart.y+regionLengthLimit),
selectionStart.y),
math.max(
math.min(selectionEnd.z+dragVector.z,selectionStart.z+regionLengthLimit),
selectionStart.z)
)
end
elseif tool == 'Move' then
selectionStart = selectionStart + dragVector
selectionEnd = selectionEnd + dragVector
end
if utilityModule:GetUsingAsPlugin() then
changeHistory:SetWaypoint('Terrain '..button)
end
end
if dragAngle and dragAngle ~= 0 then
local rotationCFrame = CFrame.Angles(
axis ~= 'X' and 0 or dragAngle * rotationInterval,
axis ~= 'Y' and 0 or dragAngle * rotationInterval,
axis ~= 'Z' and 0 or dragAngle * rotationInterval
)
local temporarySize = Vector3.new(1,1,1) + selectionEnd - selectionStart
local centerOffset = Vector3.new(ceil(temporarySize.x * .5), ceil(temporarySize.y * .5), ceil(temporarySize.z * .5))
temporarySize = rotationCFrame * temporarySize
local temporarySizeX = round(math.abs(temporarySize.x)) --I need to round these because of floating point imprecision
local temporarySizeY = round(math.abs(temporarySize.y))
local temporarySizeZ = round(math.abs(temporarySize.z))
centerOffset = centerOffset - Vector3.new(ceil(temporarySizeX * .5), ceil(temporarySizeY * .5), ceil(temporarySizeZ * .5))
selectionEnd = selectionStart + centerOffset + Vector3.new(temporarySizeX, temporarySizeY, temporarySizeZ) - Vector3.new(1, 1, 1)
selectionStart = selectionStart + centerOffset
lockInMap()
if utilityModule:GetUsingAsPlugin() then
changeHistory:SetWaypoint('Terrain '..button)
end
end
dragVector = nil
dragAngle = nil
renderSelection()
--lockInMap()
end)
closeButton.MouseButton1Down:connect(Deselected)
--[[plugin.Deactivation:connect(function()
if on then
Deselected()
end
end)]]
local function historyChanged()
selectionStart = nil
selectionEnd = nil
lockedMaterials = nil
lockedOccupancies = nil
setButton('Select')
end
if utilityModule:GetUsingAsPlugin() then
changeHistory.OnUndo:connect(historyChanged)
changeHistory.OnRedo:connect(historyChanged)
end
end
function On(mouseHandMeDown,turnOffHandMeDown)
mouse = mouseHandMeDown
turnOff = turnOffHandMeDown
if not setup then --I do this so that things only get set up when this plugin is used.
FirstTimeSetUp()
end
Selected()
end
function Off()
if Deselected then
Deselected()
end
end
return {
['On'] = On,
['Off'] = Off,
}
]]>
-
Utility
-
TerrainRegionGui
-
true
4294967295
0
4279970357
1
false
false
Frame
[null]
[null]
[null]
[null]
0
0
0
0
0
false
[null]
0
180
0
370
0
6
false
1
-
true
true
4294967295
0
4279970357
1
false
false
4
6
false
ButtonSelect
[null]
[null]
[null]
[null]
0
10
0
35
0
true
false
[null]
1
-20
0
30
0
5
Select
4282946004
false
4278190080
1
0
false
2
1
true
2
-
true
true
4294967295
0
4279970357
1
false
false
4
6
false
ButtonRotate
[null]
[null]
[null]
[null]
0
10
0
125
0
true
false
[null]
1
-20
0
30
0
4
Rotate
4293848814
false
4278190080
1
0
false
2
1
true
2
-
true
true
4294967295
0
4279970357
1
false
false
4
6
false
ButtonResize
[null]
[null]
[null]
[null]
0
10
0
95
0
true
false
[null]
1
-20
0
30
0
4
Resize
4293848814
false
4278190080
1
0
false
2
1
true
2
-
false
4294967295
0.600000024
4279970357
0
false
false
4
6
Divider2
[null]
[null]
[null]
[null]
0
0
0
165
0
false
[null]
1
0
0
2
0
4294967295
false
4278190080
1
0
false
2
1
true
2
-
true
true
4294967295
0
4279970357
1
false
false
4
6
false
ButtonCopy
[null]
[null]
[null]
[null]
0
10
0
175
0
true
false
[null]
1
-20
0
30
0
4
Copy
4293848814
false
4278190080
1
0
false
2
1
true
2
-
true
true
4294967295
0
4279970357
1
false
false
4
6
false
ButtonPaste
[null]
[null]
[null]
[null]
0
10
0
205
0
true
false
[null]
1
-20
0
30
0
4
Paste
4293848814
false
4278190080
1
0
false
2
1
true
2
-
true
true
4294967295
0
4279970357
1
false
false
4
6
false
ButtonFillAir
[null]
[null]
[null]
[null]
0
10
0
315
0
true
false
[null]
0
30
0
30
0
4
X
4293848814
false
4278190080
1
0
false
2
1
true
2
-
false
4294967295
1
4279970357
0
false
false
4
6
Label
[null]
[null]
[null]
[null]
0
30
0.5
-10
0
false
[null]
0
90
0
20
0
Merge Empty
4294967295
false
4278190080
1
0
false
0
1
true
3
-
false
4294967295
0.600000024
4279970357
0
false
false
4
6
Divider3
[null]
[null]
[null]
[null]
0
0
0
305
0
false
[null]
1
0
0
2
0
4294967295
false
4278190080
1
0
false
2
1
true
2
-
true
true
4294967295
0
4279970357
1
false
false
4
6
false
ButtonFillWater
[null]
[null]
[null]
[null]
0
10
0
315
0
true
false
[null]
0
30
0
30
0
4
X
4293848814
false
4278190080
1
0
false
2
1
false
2
-
false
4294967295
1
4279970357
0
false
false
4
6
Label
[null]
[null]
[null]
[null]
0
30
0.5
-10
0
false
[null]
0
90
0
20
0
Fill Water
4294967295
false
4278190080
1
0
false
0
1
true
3
-
true
4294967295
0
4279970357
1
false
false
DoubleBacking
[null]
[null]
[null]
[null]
0
-7
0
-7
0
false
[null]
1
16
1
16
0
6
false
1
-
true
true
4294967295
0
4279970357
1
false
false
4
6
false
ButtonMove
[null]
[null]
[null]
[null]
0
10
0
65
0
true
false
[null]
1
-20
0
30
0
4
Move
4293848814
false
4278190080
1
0
false
2
1
true
2
-
true
true
4294967295
0
4279970357
1
false
false
4
6
false
ButtonDelete
[null]
[null]
[null]
[null]
0
10
0
235
0
true
false
[null]
1
-20
0
30
0
4
Delete
4293848814
false
4278190080
1
0
false
2
1
true
2
-
false
4294967295
0.75
4279970357
0
false
false
4
6
TitleLabel
[null]
[null]
[null]
[null]
0
0
0
0
0
false
[null]
1
0
0
25
0
Regions
4294967295
false
4278190080
1
0
false
2
1
true
2
-
true
false
4294967295
1
4279970357
0
false
false
0
4
false
CloseButton
[null]
[null]
[null]
[null]
1
-26
0
0
0
true
false
[null]
0
25
0
25
0
0
X
4294967295
false
4278190080
1
0
true
2
1
true
2
-
true
true
4294967295
0
4279970357
1
false
false
4
6
false
ButtonFill
[null]
[null]
[null]
[null]
0
10
0
265
0
true
false
[null]
1
-20
0
30
0
4
Fill
4293848814
false
4278190080
1
0
false
2
1
true
2
-
true
4294967295
0
4279970357
1
false
false
FillFrame
[null]
[null]
[null]
[null]
0
180
0
0
0
false
[null]
0
180
0
295
0
6
false
1
-
true
false
4294967295
1
4279970357
0
false
false
0
4
false
CloseButton
[null]
[null]
[null]
[null]
1
-26
0
0
0
true
false
[null]
0
25
0
25
0
0
X
4294967295
false
4278190080
1
0
true
2
1
true
2
-
false
4294967295
0.75
4279970357
0
false
false
4
6
TitleLabel
[null]
[null]
[null]
[null]
0
0
0
0
0
false
[null]
1
0
0
25
0
Fill Selection
4294967295
false
4278190080
1
0
false
2
1
true
2
-
true
4294967295
0
4279970357
1
false
false
DoubleBacking
[null]
[null]
[null]
[null]
0
-7
0
-7
0
false
[null]
1
16
1
16
0
6
false
1
-
true
true
4294967295
0
4279970357
1
false
false
4
6
false
ButtonFillConfirm
[null]
[null]
[null]
[null]
0
10
1
-40
0
true
false
[null]
1
-20
0
30
0
4
Fill
4293848814
false
4278190080
1
0
false
2
1
true
2
-
MaterialsList
-
TerrainBrushGui
-
true
4294967295
0
4279970357
1
false
false
Frame
[null]
[null]
[null]
[null]
0
0
0
0
0
false
[null]
0
180
0
325
0
6
true
1
-
true
false
4294967295
1
4279970357
0
false
false
0
4
false
CloseButton
[null]
[null]
[null]
[null]
1
-26
0
0
0
true
false
[null]
0
25
0
25
0
0
X
4294967295
false
4278190080
1
0
true
2
1
true
2
-
false
4294967295
0.75
4279970357
0
false
false
4
6
TitleLabel
[null]
[null]
[null]
[null]
0
0
0
0
0
false
[null]
1
0
0
25
0
Terrain Brush
4294967295
false
4278190080
1
0
false
2
1
true
2
-
false
4294967295
1
4279970357
0
false
false
4
5
Label1
[null]
[null]
[null]
[null]
0
10
0
30
0
false
[null]
0
47
0
20
0
Size
4294967295
false
4278190080
1
0
false
2
1
true
2
-
false
4294967295
1
4279970357
0
false
false
4
5
Label2
[null]
[null]
[null]
[null]
0
10
0
55
0
false
[null]
0
47
0
20
0
Strength
4294967295
false
4278190080
1
0
false
2
1
true
2
-
false
4294967295
1
4279970357
0
false
false
4
5
Label3
[null]
[null]
[null]
[null]
0
10
0
85
0
false
[null]
0
47
0
20
0
Shape
4294967295
false
4278190080
1
0
false
2
1
true
2
-
false
4294967295
1
4279970357
0
false
false
4
6
Label4
[null]
[null]
[null]
[null]
0
10
0
233
0
false
[null]
0
60
0
20
0
Material
4294967295
false
4278190080
1
0
false
0
1
true
2
-
true
true
4294967295
0
4279970357
1
false
false
4
6
false
CheckBox2
[null]
[null]
[null]
[null]
0
10
0
160
0
true
false
[null]
0
26
0
26
0
4
X
4293848814
false
4278190080
1
0
false
2
1
true
2
-
false
4294967295
1
4279970357
0
false
false
4
5
Label
[null]
[null]
[null]
[null]
0
25
0.5
-10
0
false
[null]
0
90
0
20
0
Snap to Grid
4294967295
false
4278190080
1
0
false
0
1
true
2
-
true
false
4294967295
1
4279970357
0
false
false
http://www.roblox.com/asset/?id=225799533
4294967295
0
0
0
0
0
false
ShapeButton1
[null]
[null]
[null]
[null]
0
73
0
81
0
0
true
false
[null]
0
32
0
32
0
0
0
0
0
0
true
2
-
true
false
4294967295
1
4279970357
0
false
false
http://www.roblox.com/asset/?id=225799696
4294967295
0
0
0
0
0
false
ShapeButton2
[null]
[null]
[null]
[null]
0
118
0
81
0
0
true
false
[null]
0
32
0
32
0
0
0
0
0
0
true
2
-
false
4294967295
0.600000024
4279970357
0
false
false
4
6
Divider2
[null]
[null]
[null]
[null]
0
0
0
225
0
false
[null]
1
0
0
2
0
4294967295
false
4278190080
1
0
false
2
1
true
2
-
false
4294967295
0.600000024
4279970357
0
false
false
4
6
SubDivider
[null]
[null]
[null]
[null]
0.5
-1
0
2
0
false
[null]
0
2
0
30
0
4294967295
false
4278190080
1
0
false
2
1
false
2
-
false
4286479998
0.600000024
4279970357
0
false
false
4
6
Blocker
[null]
[null]
[null]
[null]
1
0
0
0
0
false
[null]
0
81
0
30
0
4294967295
false
4278190080
1
0
false
2
1
false
3
-
false
4294967295
0.600000024
4279970357
0
false
false
4
6
Divider1
[null]
[null]
[null]
[null]
0
0
0
120
0
false
[null]
1
0
0
2
0
4294967295
false
4278190080
1
0
false
2
1
true
2
-
true
true
4294967295
0
4279970357
1
false
false
4
6
false
CheckBox3
[null]
[null]
[null]
[null]
0
90
0
230
0
true
false
[null]
0
26
0
26
0
4
X
4293848814
false
4278190080
1
0
false
2
1
true
2
-
false
4294967295
1
4279970357
0
false
false
4
5
Label
[null]
[null]
[null]
[null]
0
20
0.5
-10
0
false
[null]
0
90
0
20
0
Auto
4294967295
false
4278190080
1
0
false
0
1
true
2
-
true
4294967295
0
4279970357
1
false
false
DoubleBacking
[null]
[null]
[null]
[null]
0
-7
0
-7
0
false
[null]
1
16
1
16
0
6
false
1
-
true
true
4294967295
0
4279970357
1
false
false
4
6
false
CheckBox1
[null]
[null]
[null]
[null]
0
10
0
130
0
true
false
[null]
0
26
0
26
0
4
X
4293848814
false
4278190080
1
0
false
2
1
true
2
-
false
4294967295
1
4279970357
0
false
false
4
5
Label
[null]
[null]
[null]
[null]
0
25
0.5
-10
0
false
[null]
0
90
0
20
0
Plane Lock
4294967295
false
4278190080
1
0
false
0
1
true
2
-
false
4294967295
1
4279970357
0
false
false
4
5
ToolTip2
[null]
[null]
[null]
[null]
1
10
0
55
0
false
[null]
0
47
0
20
0
Ctrl + mouse wheel
4294967295
false
4278190080
1
0.300000012
false
0
1
false
3
-
false
4294967295
1
4279970357
0
false
false
4
5
Shadow
[null]
[null]
[null]
[null]
0
1
0
1
0
false
[null]
0
47
0
20
0
Ctrl + mouse wheel
4282071867
false
4278190080
1
0.300000012
false
0
1
true
2
-
false
4294967295
1
4279970357
0
false
false
4
5
ToolTip1
[null]
[null]
[null]
[null]
1
10
0
30
0
false
[null]
0
47
0
20
0
Shift + mouse wheel
4294967295
false
4278190080
1
0.300000012
false
0
1
false
3
-
false
4294967295
1
4279970357
0
false
false
4
5
Shadow
[null]
[null]
[null]
[null]
0
1
0
1
0
false
[null]
0
47
0
20
0
Shift + mouse wheel
4282071867
false
4278190080
1
0.300000012
false
0
1
true
2
-
true
true
4294967295
0
4279970357
1
false
false
4
6
false
CheckBox4
[null]
[null]
[null]
[null]
0
10
0
190
0
true
false
[null]
0
26
0
26
0
4
4293848814
false
4278190080
1
0
false
2
1
true
2
-
false
4294967295
1
4279970357
0
false
false
4
5
Label
[null]
[null]
[null]
[null]
0
25
0.5
-10
0
false
[null]
0
90
0
20
0
Ignore Water
4294967295
false
4278190080
1
0
false
0
1
true
2
-
TerrainGenerationGui
-
true
4294967295
0
4279970357
1
false
false
MainFrame
[null]
[null]
[null]
[null]
0
0
0
0
0
false
[null]
0
200
0
430
0
6
true
1
-
true
true
4294967295
0
4279970357
1
false
false
4
6
false
ClearButton
[null]
[null]
[null]
[null]
0
10
1
-35
0
true
false
[null]
1
-20
0
30
0
4
Clear
4293848814
false
4278190080
1
0
false
2
1
true
2
-
false
4294967295
0.600000024
4279970357
0
false
false
4
6
Divider
[null]
[null]
[null]
[null]
0
0
1
-80
0
false
[null]
1
0
0
2
0
4294967295
false
4278190080
1
0
false
2
1
true
2
-
true
4294967295
0
4279970357
1
false
false
DoubleBacking
[null]
[null]
[null]
[null]
0
-7
0
-7
0
false
[null]
1
16
1
16
0
6
false
1
-
false
4294967295
0.75
4279970357
0
false
false
4
6
TitleLabel
[null]
[null]
[null]
[null]
0
0
0
0
0
false
[null]
1
0
0
25
0
Generation
4294967295
false
4278190080
1
0
false
2
1
true
2
-
true
false
4294967295
1
4279970357
0
false
false
0
4
false
CloseButton
[null]
[null]
[null]
[null]
1
-26
0
0
0
true
false
[null]
0
25
0
25
0
0
X
4294967295
false
4278190080
1
0
true
2
1
true
2
-
false
4294967295
1
4279970357
0
ayaasset://textures/ui/studs.png
0
0
0
0
0
495
true
false
ayaasset://textures/ui/smooth.png
ScrollingFrame
[null]
[null]
[null]
[null]
0
0
0
30
0
12
true
true
[null]
1
0
1
-115
0
ayaasset://textures/ui/studs.png
true
2
-
false
4294967295
1
4279970357
0
true
false
Canvas
[null]
[null]
[null]
[null]
0
0
0
0
0
false
[null]
1
-13
1
0
0
0
true
1
-
true
true
4294967295
0
4279970357
1
false
false
4
6
false
GenerateButton
[null]
[null]
[null]
[null]
0
10
1
-70
0
true
false
[null]
1
-20
0
30
0
4
Generate
4293848814
false
4278190080
1
0
false
2
1
true
2
-
false
4278190080
0.699999988
4279970357
0
false
false
ScrollBackground
[null]
[null]
[null]
[null]
1
-12
0
30
0
false
[null]
0
12
1
-115
0
0
true
1
-
true
true
4294967295
0
4279970357
1
false
false
4
6
false
TemplateCheckBox
[null]
[null]
[null]
[null]
0
5
1
-36
0
true
false
[null]
0
26
0
26
0
4
X
4293848814
false
4278190080
1
0
false
2
1
false
2
-
false
4294967295
1
4279970357
0
false
false
2
5
Label
[null]
[null]
[null]
[null]
0
20
0.5
-10
0
false
[null]
0
90
0
20
0
snowcap mountains
4294111986
false
4278190080
1
0
false
0
1
true
2
-
true
false
4294967295
0.75
4279970357
0
false
false
4
6
false
TemplateSection
[null]
[null]
[null]
[null]
0
0
0
0
0
true
false
[null]
1
0
0
20
0
0
Section
4294967295
false
4278190080
1
0
false
2
1
false
1
-
false
4294967295
0.899999976
4279970357
0
false
false
Frame
[null]
[null]
[null]
[null]
0
0
1
0
0
false
[null]
1
0
0
70
0
0
true
1
-
true
4294967295
0
4279970357
1
false
false
ProgressFrame
[null]
[null]
[null]
[null]
0.5
-200
0
0
0
false
[null]
0
400
0
160
0
6
false
1
-
true
true
4294967295
0
4279970357
1
false
false
4
6
false
CancelButton
[null]
[null]
[null]
[null]
0.5
8
1
-35
0
true
false
[null]
0.5
-20
0
30
0
4
Cancel
4293848814
false
4278190080
1
0
false
2
1
true
2
-
true
4294967295
0
4279970357
1
false
false
DoubleBacking
[null]
[null]
[null]
[null]
0
-7
0
-7
0
false
[null]
1
16
1
16
0
6
false
1
-
false
4294967295
0.75
4279970357
0
false
false
4
6
TitleLabel
[null]
[null]
[null]
[null]
0
0
0
0
0
false
[null]
1
0
0
25
0
Progress
4294967295
false
4278190080
1
0
false
2
1
true
2
-
true
true
4294967295
0
4279970357
1
false
false
4
6
false
PuaseButton
[null]
[null]
[null]
[null]
0
12
1
-35
0
true
false
[null]
0.5
-20
0
30
0
4
Pause
4293848814
false
4278190080
1
0
false
2
1
true
2
-
false
4294967295
0.5
4292401368
2
false
false
Bar
[null]
[null]
[null]
[null]
0
10
0.389999986
0
0
false
[null]
1
-20
0
25
0
0
true
1
-
false
4294967295
0
4279970357
0
false
false
ayaassetid://67599350
4278248807
0
256
0
-100
0
Fill
[null]
[null]
[null]
[null]
0
0
0
0
0
0
false
[null]
0.381965995
0
1
0
0
0
0
0
0
true
2
-
true
4294967295
0
4279970357
1
false
false
MultipleChoiceFrame
[null]
[null]
[null]
[null]
0
200
0
0
0
false
[null]
0
180
0
295
0
6
false
1
-
true
false
4294967295
1
4279970357
0
false
false
0
4
false
CloseButton
[null]
[null]
[null]
[null]
1
-26
0
0
0
true
false
[null]
0
25
0
25
0
0
X
4294967295
false
4278190080
1
0
true
2
1
true
2
-
false
4294967295
0.75
4279970357
0
false
false
4
6
TitleLabel
[null]
[null]
[null]
[null]
0
0
0
0
0
false
[null]
1
0
0
25
0
Multiple Choice
4294967295
false
4278190080
1
0
false
2
1
true
2
-
true
4294967295
0
4279970357
1
false
false
DoubleBacking
[null]
[null]
[null]
[null]
0
-7
0
-7
0
false
[null]
1
16
1
16
0
6
false
1
-
true
true
4294967295
0
4279970357
1
false
false
4
6
false
TemplateButton
[null]
[null]
[null]
[null]
0
10
0
35
0
true
false
[null]
1
-20
0
30
0
4
Clear
4293848814
false
4278190080
1
0
false
2
1
false
2
-
true
false
4294967295
0.899999976
4279970357
0
false
false
4
6
false
TemplateMultiChoice
[null]
[null]
[null]
[null]
0
0
0
0
0
true
false
[null]
1
0
0
55
0
0
Map Size
4294967295
false
4278190080
1
0
false
2
0
false
1
-
true
true
4294967295
0
4279970357
1
false
false
4
6
false
Button
[null]
[null]
[null]
[null]
0
10
0
20
0
true
false
[null]
1
-20
0
30
0
4
Medium (256)
4293848814
false
4278190080
1
0
false
0
1
true
2
-
false
4294967295
1
4279970357
0
false
false
ayaasset://textures/ui/dropdown_arrow.png
4294967295
0
0
0
0
0.300000012
ArrowLabel
[null]
[null]
[null]
[null]
1
-13
0.5
-4
0
0
false
[null]
0
16
0
10
2
0
0
0
0
true
3
-
false
4294967295
1
4279970357
0
false
false
4
6
TemplateTextBox
[null]
[null]
[null]
[null]
0
0
0
0
0
false
[null]
1
0
0
28
0
Seed:
4294967295
false
4278190080
1
0
false
0
1
false
1
-
true
4282339151
0
4279970357
0
true
true
false
3
6
false
TextBox
[null]
[null]
[null]
[null]
0
60
0
2
0
true
[null]
1
-70
1
-4
0
618033988
4294967295
false
4278190080
1
0
false
2
1
true
2
-
GenerationModule
range.max then
range.max = v
end
end
local function getPerlin(x,y,z,seed,scale,raw)
local seed = seed or 0
local scale = scale or 1
if not raw then
return noise(x/scale+(seed*17)+masterSeed,y/scale-masterSeed,z/scale-seed*seed)*.5 + .5 -- accounts for bleeding from interpolated line
else
return noise(x/scale+(seed*17)+masterSeed,y/scale-masterSeed,z/scale-seed*seed)
end
end
randomseed(6180339)
theseed={}
for i=1,999 do
table.insert(theseed,math.random())
end
local function getNoise(x,y,z,seed1)
local x = x or 0
local y = y or 0
local z = z or 0
local seed1 = seed1 or 7
local wtf=x+y+z+seed1+masterSeed + (masterSeed-x)*(seed1+z) + (seed1-y)*(masterSeed+z) -- + x*(y+z) + z*(masterSeed+seed1) + seed1*(x+y) --x+y+z+seed1+masterSeed + x*y*masterSeed-y*z+(z+masterSeed)*x --((x+y)*(y-seed1)*seed1)-(x+z)*seed2+x*11+z*23-y*17
return theseed[(floor(wtf%(#theseed)))+1]
end
local function thresholdFilter(value, bottom, size)
if value <= bottom then
return 0
elseif value >= bottom+size then
return 1
else
return (value-bottom)/size
end
end
local function ridgedFilter(value) --absolute and flip for ridges. and normalize
return value<.5 and value*2 or 2-value*2
end
local function ridgedFlippedFilter(value) --unflipped
return value < .5 and 1-value*2 or value*2-1
end
local function advancedRidgedFilter(value, cutoff)
local cutoff = cutoff or .5
value = value - cutoff
return 1 - (value < 0 and -value or value) * 1/(1-cutoff)
end
local function fractalize(operation,x,y,z, operationCount, scale, offset, gain)
local operationCount = operationCount or 3
local scale = scale or .5
local offset = 0
local gain = gain or 1
local totalValue = 0
local totalScale = 0
for i=1, operationCount do
local thisScale = scale^(i-1)
totalScale = totalScale + thisScale
totalValue = totalValue + (offset + gain * operation(x,y,z,i))*thisScale
end
return totalValue/totalScale
end
local function mountainsOperation(x,y,z,i)
return ridgedFilter(getPerlin(x,y,z,100+i,(1/i)*160))
end
local canyonBandingMaterial = {rock,mud,sand,sand,sandstone,sandstone,sandstone,sandstone,sandstone,sandstone,}
local function findBiomeInfo(choiceBiome,x,y,z,verticalGradientTurbulence)
local choiceBiomeValue = .5
local choiceBiomeSurface = grass
local choiceBiomeFill = rock
if choiceBiome == 'City' then
choiceBiomeValue = .55
choiceBiomeSurface = concrete
choiceBiomeFill = slate
elseif choiceBiome == 'Water' then
choiceBiomeValue = .36+getPerlin(x,y,z,2,50)*.08
choiceBiomeSurface =
(1-verticalGradientTurbulence < .44 and slate)
or sand
elseif choiceBiome == 'Marsh' then
local preLedge = getPerlin(x+getPerlin(x,0,z,5,7,true)*10+getPerlin(x,0,z,6,30,true)*50,0,z+getPerlin(x,0,z,9,7,true)*10+getPerlin(x,0,z,10,30,true)*50,2,70) --could use some turbulence
local grassyLedge = thresholdFilter(preLedge,.65,0)
local largeGradient = getPerlin(x,y,z,4,100)
local smallGradient = getPerlin(x,y,z,3,20)
local smallGradientThreshold = thresholdFilter(smallGradient,.5,0)
choiceBiomeValue = waterLevel-.04
+preLedge*grassyLedge*.025
+largeGradient*.035
+smallGradient*.025
choiceBiomeSurface =
(grassyLedge >= 1 and grass)
or (1-verticalGradientTurbulence < waterLevel-.01 and mud)
or (1-verticalGradientTurbulence < waterLevel+.01 and ground)
or grass
choiceBiomeFill = slate
elseif choiceBiome == 'Plains' then
local rivulet = ridgedFlippedFilter(getPerlin(x+getPerlin(x,y,z,17,40)*25,0,z+getPerlin(x,y,z,19,40)*25,2,200))
local rivuletThreshold = thresholdFilter(rivulet,.01,0)
local rockMap = thresholdFilter(ridgedFlippedFilter(getPerlin(x,0,z,101,7)),.3,.7) --rocks
* thresholdFilter(getPerlin(x,0,z,102,50),.6,.05) --zoning
choiceBiomeValue = .5 --.51
+getPerlin(x,y,z,2,100)*.02 --.05
+rivulet*.05 --.02
+rockMap*.05 --.03
+rivuletThreshold*.005
local verticalGradient = 1-((y-1)/(mapHeight-1))
local surfaceGradient = verticalGradient*.5 + choiceBiomeValue*.5
local thinSurface = surfaceGradient > .5-surfaceThickness*.4 and surfaceGradient < .5+surfaceThickness*.4
choiceBiomeSurface =
(rockMap>0 and rock)
or (not thinSurface and mud)
or (thinSurface and rivuletThreshold <=0 and water)
or (1-verticalGradientTurbulence < waterLevel-.01 and sand)
or grass
choiceBiomeFill =
(rockMap>0 and rock)
or sandstone
elseif choiceBiome == 'Canyons' then
local canyonNoise = ridgedFlippedFilter(getPerlin(x,0,z,2,200))
local canyonNoiseTurbed = ridgedFlippedFilter(getPerlin(x+getPerlin(x,0,z,5,20,true)*20,0,z+getPerlin(x,0,z,9,20,true)*20,2,200))
local sandbank = thresholdFilter(canyonNoiseTurbed,0,.05)
local canyonTop = thresholdFilter(canyonNoiseTurbed,.125,0)
local mesaSlope = thresholdFilter(canyonNoise,.33,.12)
local mesaTop = thresholdFilter(canyonNoiseTurbed,.49,0)
choiceBiomeValue = .42
+getPerlin(x,y,z,2,70)*.05
+canyonNoise*.05
+sandbank*.04 --canyon bottom slope
+thresholdFilter(canyonNoiseTurbed,.05,0)*.08 --canyon cliff
+thresholdFilter(canyonNoiseTurbed,.05,.075)*.04 --canyon cliff top slope
+canyonTop*.01 --canyon cliff top ledge
+thresholdFilter(canyonNoiseTurbed,.0575,.2725)*.01 --plane slope
+mesaSlope*.06 --mesa slope
+thresholdFilter(canyonNoiseTurbed,.45,0)*.14 --mesa cliff
+thresholdFilter(canyonNoiseTurbed,.45,.04)*.025 --mesa cap
+mesaTop*.02 --mesa top ledge
choiceBiomeSurface =
(1-verticalGradientTurbulence < waterLevel+.015 and sand) --this for biome blending in to lakes
or (sandbank>0 and sandbank<1 and sand) --this for canyonbase sandbanks
--or (canyonTop>0 and canyonTop<=1 and mesaSlope<=0 and grass) --this for grassy canyon tops
--or (mesaTop>0 and mesaTop<=1 and grass) --this for grassy mesa tops
or sandstone
choiceBiomeFill = canyonBandingMaterial[ceil((1-getNoise(1,y,2))*10)]
elseif choiceBiome == 'Hills' then
local rivulet = ridgedFlippedFilter(getPerlin(x+getPerlin(x,y,z,17,20)*20,0,z+getPerlin(x,y,z,19,20)*20,2,200))^(1/2)
local largeHills = getPerlin(x,y,z,3,60)
choiceBiomeValue = .48
+largeHills*.05
+(.05
+largeHills*.1
+getPerlin(x,y,z,4,25)*.125)
*rivulet
local surfaceMaterialGradient = (1-verticalGradientTurbulence)*.9 + rivulet*.1
choiceBiomeSurface =
(surfaceMaterialGradient < waterLevel-.015 and mud)
or (surfaceMaterialGradient < waterLevel and ground)
or grass
choiceBiomeFill = slate
elseif choiceBiome == 'Dunes' then
local duneTurbulence = getPerlin(x,0,z,227,20)*24
local layer1 = ridgedFilter(getPerlin(x,0,z,201,40))
local layer2 = ridgedFilter(getPerlin(x/10+duneTurbulence,0,z+duneTurbulence,200,48))
choiceBiomeValue = .4+.1*(layer1 + layer2)
choiceBiomeSurface = sand
choiceBiomeFill = sandstone
elseif choiceBiome == 'Mountains' then
local rivulet = ridgedFlippedFilter(getPerlin(x+getPerlin(x,y,z,17,20)*20,0,z+getPerlin(x,y,z,19,20)*20,2,200))
choiceBiomeValue = -.4 --.3
+fractalize(mountainsOperation,x,y/20,z, 8, .65)*1.2
+rivulet*.2
choiceBiomeSurface =
(verticalGradientTurbulence < .275 and snow)
or (verticalGradientTurbulence < .35 and rock)
or (verticalGradientTurbulence < .4 and ground)
or (1-verticalGradientTurbulence < waterLevel and rock)
or (1-verticalGradientTurbulence < waterLevel+.01 and mud)
or (1-verticalGradientTurbulence < waterLevel+.015 and ground)
or grass
elseif choiceBiome == 'Lavaflow' then
local crackX = x+getPerlin(x,y*.25,z,21,8,true)*5
local crackY = y+getPerlin(x,y*.25,z,22,8,true)*5
local crackZ = z+getPerlin(x,y*.25,z,23,8,true)*5
local crack1 = ridgedFilter(getPerlin(crackX+getPerlin(x,y,z,22,30,true)*30,crackY,crackZ+getPerlin(x,y,z,24,30,true)*30,2,120))
local crack2 = ridgedFilter(getPerlin(crackX,crackY,crackZ,3,40))*(crack1*.25+.75)
local crack3 = ridgedFilter(getPerlin(crackX,crackY,crackZ,4,20))*(crack2*.25+.75)
local generalHills = thresholdFilter(getPerlin(x,y,z,9,40),.25,.5)*getPerlin(x,y,z,10,60)
local cracks = max(0,1-thresholdFilter(crack1,.975,0)-thresholdFilter(crack2,.925,0)-thresholdFilter(crack3,.9,0))
local spireVec = CFrame.Angles(.7,.7,0)*Vector3.new(crackX,crackY,crackZ)
local spires = thresholdFilter(getPerlin(spireVec.x/40,spireVec.y/300,spireVec.z/30,123,1),.6,.4)
choiceBiomeValue = waterLevel+.02
+cracks*(.5+generalHills*.5)*.02
+generalHills*.05
+spires*.3
+((1-verticalGradientTurbulence > waterLevel+.01 or spires>0) and .04 or 0) --This lets it lip over water
choiceBiomeFill = (spires>0 and rock) or (cracks<1 and lava) or basalt
choiceBiomeSurface = (choiceBiomeFill == lava and 1-verticalGradientTurbulence < waterLevel and basalt) or choiceBiomeFill
elseif choiceBiome == 'Arctic' then
local preBoundary = getPerlin(x+getPerlin(x,0,z,5,8,true)*5,y/8,z+getPerlin(x,0,z,9,8,true)*5,2,20)
--local cliffs = thresholdFilter(preBoundary,.5,0)
local boundary = ridgedFilter(preBoundary)
local roughChunks = getPerlin(x,y/4,z,436,2)
local boundaryMask = thresholdFilter(boundary,.8,.1) --,.7,.25)
local boundaryTypeMask = getPerlin(x,0,z,6,74)-.5
local boundaryComp = 0
if boundaryTypeMask < 0 then --divergent
boundaryComp = (boundary > (1+boundaryTypeMask*.5) and -.17 or 0)
--* boundaryTypeMask*-2
else --convergent
boundaryComp = boundaryMask*.1*roughChunks
* boundaryTypeMask
end
choiceBiomeValue = .55
+boundary*.05*boundaryTypeMask --.1 --soft slope up or down to boundary
+boundaryComp --convergent/divergent effects
+getPerlin(x,0,z,123,25)*.025 --*cliffs --gentle rolling slopes
choiceBiomeSurface = (1-verticalGradientTurbulence < waterLevel-.1 and ice) or (boundaryMask>.6 and boundaryTypeMask>.1 and roughChunks>.5 and ice) or snow
choiceBiomeFill = ice
end
return choiceBiomeValue, choiceBiomeSurface, choiceBiomeFill
end
function findBiomeTransitionValue(biome,weight,value,averageValue)
if biome == 'Arctic' then
return (weight>.2 and 1 or 0)*value
elseif biome == 'Canyons' then
return (weight>.7 and 1 or 0)*value
elseif biome == 'Mountains' then
local weight = weight^3 --This improves the ease of mountains transitioning to other biomes
return averageValue*(1-weight)+value*weight
else
return averageValue*(1-weight)+value*weight
end
end
function updatePausedButton()
pauseButton.Style = paused and 'RobloxRoundButton' or 'RobloxRoundDefaultButton'
pauseButton.Text = paused and 'Paused' or 'Pause'
end
function generate()
if not generating and not clearing then
generating = true
paused = false
updatePausedButton()
cancelIt = false
progressFrame.Visible = true
local mapWidth = mapWidth
local biomeSize = biomeSize
local biomeBlendPercent = .25 --(biomeSize==50 or biomeSize == 100) and .5 or .25
local biomeBlendPercentInverse = 1-biomeBlendPercent
local biomeBlendDistortion = biomeBlendPercent
local smoothScale = .5/mapHeight
local startTime = tick()
biomes = {}
for i,v in pairs(allBiomes) do
if v then
table.insert(biomes,i)
end
end
if #biomes<=0 then
table.insert(biomes,'Hills')
end
--local oMap = {}
--local mMap = {}
for x = 1, mapWidth do
local oMapX = {}
--oMap[x] = oMapX
local mMapX = {}
--mMap[x] = mMapX
for z = 1, mapWidth do
local biomeNoCave = false
local cellToBiomeX = x/biomeSize + getPerlin(x,0,z,233,biomeSize*.3)*.25 + getPerlin(x,0,z,235,biomeSize*.05)*.075
local cellToBiomeZ = z/biomeSize + getPerlin(x,0,z,234,biomeSize*.3)*.25 + getPerlin(x,0,z,236,biomeSize*.05)*.075
local closestPoint = Vector3.new(0,0,0)
local closestDistance = 1000000
local biomePoints = {}
for vx=-1,1 do
for vz=-1,1 do
local gridPointX = floor(cellToBiomeX+vx+.5)
local gridPointZ = floor(cellToBiomeZ+vz+.5)
--local pointX, pointZ = getBiomePoint(gridPointX,gridPointZ)
local pointX = gridPointX+(getNoise(gridPointX,gridPointZ,53)-.5)*.75 --de-uniforming grid for vornonoi
local pointZ = gridPointZ+(getNoise(gridPointX,gridPointZ,73)-.5)*.75
local dist = sqrt((pointX-cellToBiomeX)^2 + (pointZ-cellToBiomeZ)^2)
if dist < closestDistance then
closestPoint = Vector3.new(pointX,0,pointZ)
closestDistance = dist
end
table.insert(biomePoints,{
x = pointX,
z = pointZ,
dist = dist,
biomeNoise = getNoise(gridPointX,gridPointZ),
weight = 0
})
end
end
local weightTotal = 0
local weightPoints = {}
for _,point in pairs(biomePoints) do
local weight = point.dist == closestDistance and 1 or ((closestDistance / point.dist)-biomeBlendPercentInverse)/biomeBlendPercent
if weight > 0 then
local weight = weight^2.1 --this smooths the biome transition from linear to cubic InOut
weightTotal = weightTotal + weight
local biome = biomes[ceil(#biomes*(1-point.biomeNoise))] --inverting the noise so that it is limited as (0,1]. One less addition operation when finding a random list index
weightPoints[biome] = {
weight = weightPoints[biome] and weightPoints[biome].weight + weight or weight
}
end
end
for biome,info in pairs(weightPoints) do
info.weight = info.weight / weightTotal
if biome == 'Arctic' then --biomes that don't have caves that breach the surface
biomeNoCave = true
end
end
for y = 1, mapHeight do
local oMapY = oMapX[y] or {}
oMapX[y] = oMapY
local mMapY = mMapX[y] or {}
mMapX[y] = mMapY
--[[local oMapY = {}
oMapX[y] = oMapY
local mMapY = {}
mMapX[z] = mMapY]]
local verticalGradient = 1-((y-1)/(mapHeight-1))
local caves = 0
local verticalGradientTurbulence = verticalGradient*.9 + .1*getPerlin(x,y,z,107,15)
local choiceValue = 0
local choiceSurface = lava
local choiceFill = rock
if verticalGradient > .65 or verticalGradient < .1 then
--under surface of every biome; don't get biome data; waste of time.
choiceValue = .5
elseif #biomes == 1 then
choiceValue, choiceSurface, choiceFill = findBiomeInfo(biomes[1],x,y,z,verticalGradientTurbulence)
else
local averageValue = 0
--local findChoiceMaterial = -getNoise(x,y,z,19)
for biome,info in pairs(weightPoints) do
local biomeValue, biomeSurface, biomeFill = findBiomeInfo(biome,x,y,z,verticalGradientTurbulence)
info.biomeValue = biomeValue
info.biomeSurface = biomeSurface
info.biomeFill = biomeFill
local value = biomeValue * info.weight
averageValue = averageValue + value
--[[if findChoiceMaterial < 0 and findChoiceMaterial + weight >= 0 then
choiceMaterial = biomeMaterial
end
findChoiceMaterial = findChoiceMaterial + weight]]
end
for biome,info in pairs(weightPoints) do
local value = findBiomeTransitionValue(biome,info.weight,info.biomeValue,averageValue)
if value > choiceValue then
choiceValue = value
choiceSurface = info.biomeSurface
choiceFill = info.biomeFill
end
end
end
local preCaveComp = verticalGradient*.5 + choiceValue*.5
local surface = preCaveComp > .5-surfaceThickness and preCaveComp < .5+surfaceThickness
if generateCaves --user wants caves
and (not biomeNoCave or verticalGradient > .65) --biome allows caves or deep enough
and not (surface and (1-verticalGradient) < waterLevel+.005) --caves only breach surface above waterlevel
and not (surface and (1-verticalGradient) > waterLevel+.58) then --caves don't go too high so that they don't cut up mountain tops
local ridged2 = ridgedFilter(getPerlin(x,y,z,4,30))
local caves2 = thresholdFilter(ridged2,.84,.01)
local ridged3 = ridgedFilter(getPerlin(x,y,z,5,30))
local caves3 = thresholdFilter(ridged3,.84,.01)
local ridged4 = ridgedFilter(getPerlin(x,y,z,6,30))
local caves4 = thresholdFilter(ridged4,.84,.01)
local caveOpenings = (surface and 1 or 0) * thresholdFilter(getPerlin(x,0,z,143,62),.35,0) --.45
caves = caves2 * caves3 * caves4 - caveOpenings
caves = caves < 0 and 0 or caves > 1 and 1 or caves
end
local comp = preCaveComp - caves
local smoothedResult = thresholdFilter(comp,.5,smoothScale)
---below water level -above surface -no terrain
if 1-verticalGradient < waterLevel and preCaveComp <= .5 and smoothedResult <= 0 then
smoothedResult = 1
choiceSurface = water
choiceFill = water
surface = true
end
oMapY[z] = (y == 1 and 1) or smoothedResult
mMapY[z] = (y == 1 and lava) or (smoothedResult <= 0 and air) or (surface and choiceSurface) or choiceFill
end
end
local regionStart = Vector3.new(mapWidth*-2+(x-1)*4,mapHeight*-2,mapWidth*-2)
local regionEnd = Vector3.new(mapWidth*-2+x*4,mapHeight*2,mapWidth*2)
local mapRegion = Region3.new(regionStart, regionEnd)
terrain:WriteVoxels(mapRegion, 4, {mMapX}, {oMapX})
local completionPercent = x/mapWidth
barFill.Size = UDim2.new(completionPercent,0,1,0)
wait()
while paused and not cancelIt do
wait()
end
if cancelIt then
break
end
end
changeHistoryService:SetWaypoint('Terrain Generation')
progressFrame.Visible = false
generating = false
print('Generation Complete',tick()-startTime)
end
end
--------------------------------------------------------------------------------------------------------------------
------------------------------------------------Setup User Interface------------------------------------------------
local mapSizes = {128,256,512,1024}
local mapSizeChangeFunction = function(index,text)
mapWidth = mapSizes[index]
end
createMultiChoice('Map Size', 5, canvas, {'Small (128)','Medium (256)','Large (512)','Massive (1024)'}, 2, mapSizeChangeFunction)
local seedChangeFunction = function(text)
local compositeNumber = 0
for i=1,#text do
local character = string.sub(text,i,i)
local number = tonumber(character)
if number then
compositeNumber = (compositeNumber+6)*(number+5)
else
compositeNumber = (compositeNumber+7)*(string.byte(character)+3)
end
compositeNumber = compositeNumber%61803 --yes, this does need to be done after every character iteration, otherwise number loses precision by the end
end
masterSeed = compositeNumber
end
createTextBox('Seed', 65, canvas, masterSeed, seedChangeFunction)
local cavesChangeFunction = function(value)
generateCaves = value
end
local cavesCheckBox = createCheckBox('Caves', 100, canvas, generateCaves, cavesChangeFunction)
local biomeSizes = {50,100,200,500}
local biomeSizeChangeFunction = function(index,text)
biomeSize = biomeSizes[index]
end
createMultiChoice('Biome Size', 135, canvas, {'Small (50)','Medium (100)','Large (200)','Massive (500)'}, 2, biomeSizeChangeFunction)
local biomesSettingsButton, biomesFrame = createSection('Biomes',200,275,canvas)
local waterChangeFunction = function(value)
allBiomes['Water'] = value
end
local waterCheckBox = createCheckBox('Water', 5, biomesFrame, allBiomes['Water'], waterChangeFunction)
local marshChangeFunction = function(value)
allBiomes['Marsh'] = value
end
local marshCheckBox = createCheckBox('Marsh', 35, biomesFrame, allBiomes['Marsh'], marshChangeFunction)
local plainsChangeFunction = function(value)
allBiomes['Plains'] = value
end
local plainsCheckBox = createCheckBox('Plains', 65, biomesFrame, allBiomes['Plains'], plainsChangeFunction)
local hillsChangeFunction = function(value)
allBiomes['Hills'] = value
end
local hillsCheckBox = createCheckBox('Hills', 95, biomesFrame, allBiomes['Hills'], hillsChangeFunction)
local dunesChangeFunction = function(value)
allBiomes['Dunes'] = value
end
local dunesCheckBox = createCheckBox('Dunes', 125, biomesFrame, allBiomes['Dunes'], dunesChangeFunction)
local canyonsChangeFunction = function(value)
allBiomes['Canyons'] = value
end
local canyonsCheckBox = createCheckBox('Canyons', 155, biomesFrame, allBiomes['Canyons'], canyonsChangeFunction)
local mountainsChangeFunction = function(value)
allBiomes['Mountains'] = value
end
local mountainsCheckBox = createCheckBox('Mountains', 185, biomesFrame, allBiomes['Mountains'], mountainsChangeFunction)
local lavaflowChangeFunction = function(value)
allBiomes['Lavaflow'] = value
end
local lavaflowCheckBox = createCheckBox('Lavascape', 215, biomesFrame, allBiomes['Lavaflow'], lavaflowChangeFunction)
local arcticChangeFunction = function(value)
allBiomes['Arctic'] = value
end
local arcticCheckBox = createCheckBox('Arctic', 245, biomesFrame, allBiomes['Arctic'], arcticChangeFunction)
--------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------
function On(mouseHandMeDown,turnOffHandMeDown)
gui.Parent = coreGui
on = true
if mouseHandMeDown then
mouse = mouseHandMeDown
end
if turnOffHandMeDown then
turnOff = turnOffHandMeDown
end
end
function Off()
closeMultiChoiceFrame()
if turnOff then
turnOff()
end
on = false
gui.Parent = nil
end
function clearTerrain()
if not generating and not clearing then
clearing = true
terrain:Clear()
changeHistoryService:SetWaypoint('Terrain Clear')
clearing = false
end
end
pauseButton.MouseButton1Down:connect(function()
paused = not paused
updatePausedButton()
end)
cancelButton.MouseButton1Down:connect(function()
if not cancelIt then
cancelIt = true
print('Canceled')
end
end)
mainFrame:WaitForChild('CloseButton').MouseButton1Down:connect(function()
Off()
end)
mainFrame:WaitForChild('GenerateButton').MouseButton1Down:connect(function()
generate()
end)
mainFrame:WaitForChild('ClearButton').MouseButton1Down:connect(function()
clearTerrain()
end)
return {
['On'] = On,
['Off'] = Off,
}
]]>