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, } ]]>