Files
aya/client/studio/resources/BuiltInPlugins/terrain/03 - elevation.lua
2025-12-17 16:47:48 +00:00

557 lines
17 KiB
Lua

while game == nil do
wait(1/30)
end
---------------
--PLUGIN SETUP-
---------------
loaded = false
-- True if the plugin is on, false if not.
on = false
plugin = PluginManager():CreatePlugin()
mouse = plugin:GetMouse()
mouse.Button1Down:connect(function() onClicked(mouse) end)
toolbar = plugin:CreateToolbar("Terrain")
toolbarbutton = toolbar:CreateButton("Elevation Adjuster", "Elevation Adjuster", "elevation.png")
toolbarbutton.Click:connect(function()
if on then
Off()
elseif loaded then
On()
end
end)
game:WaitForChild("Workspace")
game.Workspace:WaitForChild("Terrain")
local c = game.Workspace.Terrain
local SetCell = c.SetCell
local SetWaterCell = c.SetWaterCell
local GetCell = c.GetCell
local GetWaterCell = c.GetWaterCell
local WorldToCellPreferSolid = c.WorldToCellPreferSolid
local WorldToCellPreferEmpty = c.WorldToCellPreferEmpty
local CellCenterToWorld = c.CellCenterToWorld
local AutoWedge = c.AutowedgeCell
-----------------
--DEFAULT VALUES-
-----------------
-- The ID number that represents water.
waterMaterialID = 17
-- Elevation related options. Contains everything needed for making the elevation.
elevationOptions = {
r = 0, -- Radius of the elevation area. The larger it is, the wider the top of the elevation will be.
s = 1, -- Slope of the elevation area. The larger it is, the steeper the slope.
defaultTerrainMaterial = 1, -- The material that the elevation should be made of.
waterForce = nil, -- What force the material has if it is water.
waterDirection = nil, -- What direction the material has if it is water.
autowedge = true -- Whether smoothing should be applied. True if it should, false if not.
}
-- What color to use for the mouse highlighter.
mouseHighlightColor = BrickColor.new("Lime green")
-- Used to create a highlighter that follows the mouse.
-- It is a class mouse highlighter. To use, call MouseHighlighter.Create(mouse) where mouse is the mouse to track.
MouseHighlighter = {}
MouseHighlighter.__index = MouseHighlighter
-- Create a mouse movement highlighter.
-- plugin - Plugin to get the mouse from.
function MouseHighlighter.Create(mouseUse)
local highlighter = {}
local mouse = mouseUse
highlighter.OnClicked = nil
highlighter.mouseDown = false
-- Store the last point used to draw.
highlighter.lastUsedPoint = nil
-- Will hold a part the highlighter will be attached to. This will be moved where the mouse is.
highlighter.selectionPart = nil
-- Hook the mouse up to check for movement.
mouse.Move:connect(function() MouseMoved() end)
mouse.Button1Down:connect(function() highlighter.mouseDown = true end)
mouse.Button1Up:connect(function() highlighter.mouseDown = false
end)
-- Create the part that the highlighter will be attached to.
highlighter.selectionPart = Instance.new("Part")
highlighter.selectionPart.Name = "SelectionPart"
highlighter.selectionPart.Archivable = false
highlighter.selectionPart.Transparency = 1
highlighter.selectionPart.Anchored = true
highlighter.selectionPart.Locked = true
highlighter.selectionPart.CanCollide = false
highlighter.selectionPart.FormFactor = Enum.FormFactor.Custom
highlighter.selectionBox = Instance.new("SelectionBox")
highlighter.selectionBox.Archivable = false
highlighter.selectionBox.Color = mouseHighlightColor
highlighter.selectionBox.Adornee = highlighter.selectionPart
mouse.TargetFilter = highlighter.selectionPart
setmetatable(highlighter, MouseHighlighter)
-- Function to call when the mouse has moved. Updates where to display the highlighter.
function MouseMoved()
if on then
UpdatePosition(mouse.Hit)
end
end
-- Do a line/plane intersection. The line starts at the camera. The plane is at y == 0, normal(0, 1, 0)
--
-- vectorPos - End point of the line.
--
-- Return:
-- success - Value is true if there was a plane intersection, false if not.
-- cellPos - Value is the terrain cell intersection point if there is one, vectorPos if there isn't.
function PlaneIntersection(vectorPos)
local currCamera = game.Workspace.CurrentCamera
local startPos = Vector3.new(currCamera.CoordinateFrame.p.X, currCamera.CoordinateFrame.p.Y, currCamera.CoordinateFrame.p.Z)
local endPos = Vector3.new(vectorPos.X, vectorPos.Y, vectorPos.Z)
local normal = Vector3.new(0, 1, 0)
local p3 = Vector3.new(0, 0, 0)
local startEndDot = normal:Dot(endPos - startPos)
local cellPos = vectorPos
local success = false
if startEndDot ~= 0 then
local t = normal:Dot(p3 - startPos) / startEndDot
if(t >=0 and t <=1) then
local intersection = ((endPos - startPos) * t) + startPos
cellPos = c:WorldToCell(intersection)
success = true
end
end
return success, cellPos
end
-- Update where the highlighter is displayed.
-- position - Where to display the highlighter, in world space.
function UpdatePosition(position)
if not position then
return
end
-- NOTE:
-- Change this gui to be the one you want to use.
highlighter.selectionBox.Parent = game:GetService("CoreGui")
local vectorPos = Vector3.new(position.x,position.y,position.z)
local cellPos = WorldToCellPreferEmpty(c, vectorPos)
local solidCell = WorldToCellPreferSolid(c, vectorPos)
local success = false
-- If nothing was hit, do the plane intersection.
if 0 == GetCell(c, solidCell.X, solidCell.Y, solidCell.Z).Value then
success, cellPos = PlaneIntersection(vectorPos)
if not success then
cellPos = solidCell
end
else
highlighter.lastUsedPoint = cellPos
end
local regionToSelect = nil
local lowVec = CellCenterToWorld(c, cellPos.x , cellPos.y - 1, cellPos.z)
local highVec = CellCenterToWorld(c, cellPos.x, cellPos.y + 1, cellPos.z)
regionToSelect = Region3.new(lowVec, highVec)
highlighter.selectionPart.Size = regionToSelect.Size - Vector3.new(-4, 4, -4)
highlighter.selectionPart.CFrame = regionToSelect.CFrame
if nil ~= highlighter.OnClicked and highlighter.mouseDown then
if nil == highlighter.lastUsedPoint then
highlighter.lastUsedPoint = WorldToCellPreferEmpty(c, Vector3.new(mouse.Hit.x, mouse.Hit.y, mouse.Hit.z))
else
cellPos = WorldToCellPreferEmpty(c, Vector3.new(mouse.Hit.x, mouse.Hit.y, mouse.Hit.z))
end
end
end
return highlighter
end
-- Hide the highlighter.
function MouseHighlighter:DisablePreview()
self.selectionBox.Parent = nil
end
-- Show the highlighter.
function MouseHighlighter:EnablePreview()
self.selectionBox.Parent = game:GetService("CoreGui") -- This will make it not show up in workspace.
end
-- Create the mouse movement highlighter.
mouseHighlighter = MouseHighlighter.Create(mouse)
------
--GUI-
------
--screengui
local g = Instance.new("ScreenGui", game:GetService("CoreGui"))
g.Name = 'ElevationGui'
-- UI gui load. Required for sliders.
local RbxGui = LoadLibrary("RbxGui")
-- Create a standard text label. Use this for all lables in the popup so it is easy to standardize.
-- labelName - What to set the text label name as.
-- pos - Where to position the label. Should be of type UDim2.
-- size - How large to make the label. Should be of type UDim2.
-- text - Text to display.
-- parent - What to set the text parent as.
-- Return:
-- Value is the created label.
function CreateStandardLabel(labelName,
pos,
size,
text,
parent)
local label = Instance.new("TextLabel", parent)
label.Name = labelName
label.Position = pos
label.Size = size
label.Text = text
label.TextColor3 = Color3.new(0.95, 0.95, 0.95)
label.Font = Enum.Font.ArialBold
label.FontSize = Enum.FontSize.Size14
label.TextXAlignment = Enum.TextXAlignment.Left
label.BackgroundTransparency = 1
label.Parent = parent
return label
end
-- Create a standardized slider.
-- name - Name to use for the slider.
-- pos - Position to display the slider at.
-- steps - How many steps there are in the slider.
-- funcOnChange - Function to call when the slider changes.
-- initValue - Initial value to set the slider to. If nil the slider stays at the default.
-- parent - What to set as the parent.
-- Return:
-- sliderGui - Slider gui object.
-- sliderPosition - Object that can set the slider value.
function CreateStandardSlider(name,
pos,
lengthBarPos,
steps,
funcOnChange,
initValue,
parent)
local sliderGui, sliderPosition = RbxGui.CreateSlider(steps, 0, UDim2.new(0,0,0,0))
sliderGui.Name = name
sliderGui.Parent = parent
sliderGui.Position = pos
sliderGui.Size = UDim2.new(1,0,0,20)
local lengthBar = sliderGui:FindFirstChild("Bar")
lengthBar.Size = UDim2.new(1, -20, 0, 5)
lengthBar.Position = lengthBarPos
if nil ~= funcOnChange then
sliderPosition.Changed:connect(function() funcOnChange(sliderPosition) end)
end
if nil ~= initValue then
sliderPosition.Value = initValue
end
return sliderGui, sliderPosition
end
-- Gui frame for the plugin.
elevationPropertiesDragBar, elevationFrame, elevationHelpFrame, elevationCloseEvent = RbxGui.CreatePluginFrame("Elevation Adjuster",UDim2.new(0,185,0,100),UDim2.new(0,0,0,0),false,g)
elevationPropertiesDragBar.Visible = false
elevationCloseEvent.Event:connect(function ( )
Off()
end)
elevationHelpFrame.Size = UDim2.new(0,300,0,130)
local elevationHelpText = Instance.new("TextLabel",elevationHelpFrame)
elevationHelpText.Name = "HelpText"
elevationHelpText.Font = Enum.Font.ArialBold
elevationHelpText.FontSize = Enum.FontSize.Size12
elevationHelpText.TextColor3 = Color3.new(227/255,227/255,227/255)
elevationHelpText.TextXAlignment = Enum.TextXAlignment.Left
elevationHelpText.TextYAlignment = Enum.TextYAlignment.Top
elevationHelpText.Position = UDim2.new(0,4,0,4)
elevationHelpText.Size = UDim2.new(1,-8,0,177)
elevationHelpText.BackgroundTransparency = 1
elevationHelpText.TextWrap = true
elevationHelpText.Text = [[
Use to drag terrain up or down. Hold the left mouse button down and drag the mouse up or down to create a mountain or valley.
Radius:
The larger it is, the wider the top of the elevation will be.
Slope:
The larger it is, the steeper the slope.]]
-- Slider for controlling radius.
radiusLabel = CreateStandardLabel("RadiusLabel", UDim2.new(0, 8, 0, 10), UDim2.new(0, 67, 0, 14), "", elevationFrame)
radiusSliderGui, radiusSliderPosition = CreateStandardSlider('radiusSliderGui', UDim2.new(0,1,0,26), UDim2.new(0,10,0.5,-2), 11,
function(radiusSliderPosition)
elevationOptions.r = radiusSliderPosition.Value -- 1
radiusLabel.Text = "Radius: "..elevationOptions.r
end, nil, elevationFrame)
radiusSliderPosition.Value = 1
-- Slider for controlling the z offset to generate terrain at.
slopeLabel = CreateStandardLabel("SlopeLabel", UDim2.new(0, 8, 0, 51), UDim2.new(0, 67, 0, 14), "", elevationFrame)
slopeSliderGui, slopeSliderPosition = CreateStandardSlider('slopeSliderGui', UDim2.new(0,1,0,67), UDim2.new(0,10,0.5,-2), 16,
function()
elevationOptions.s = slopeSliderPosition.Value / 10 + 0.4
slopeLabel.Text = "Slope: ".. elevationOptions.s
end, nil, elevationFrame)
slopeSliderPosition.Value = 1
-----------------------
--FUNCTION DEFINITIONS-
-----------------------
--find height at coordinate x, z
function findHeight(x, z)
h = 0
material, wedge, rotation = GetCell(c, x, h + 1, z)
while material.Value > 0 do
h = h + 1
material, wedge, rotation = GetCell(c, x, h + 1, z)
end
return h
end
--makes a shell around block at coordinate x, z using heightmap
function makeShell(x, z, heightmap, shellheightmap)
local originalheight = heightmap[x][z]
for i = x - 1, x + 1 do
for k = z - 1, z + 1 do
if shellheightmap[i][k] < originalheight then
for h = originalheight, shellheightmap[i][k] - 2, -1 do
if h > 0 then
if waterMaterialID == elevationOptions.defaultTerrainMaterial then
SetWaterCell(c, i, h, k, elevationOptions.waterForce, elevationOptions.waterDirection)
else
SetCell(c, i, h, k, elevationOptions.defaultTerrainMaterial, 0, 0)
end
end
end
shellheightmap[i][k] = originalheight
end
end
end
return shellheightmap
end
--elevates terrain at point (x, y, z) in cluster c
--within radius r1 from x, z the elevation should become y + d
--from radius r1 to r2 the elevation should be a gradient
function elevate(x, y, z, r1, r2, d, range)
for i = x - (range + 2), x + (range + 2) do
if oldheightmap[i] == nil then
oldheightmap[i] = {}
end
for k = z - (range + 2), z + (range + 2) do
if oldheightmap[i][k] == nil then
oldheightmap[i][k] = findHeight(i, k)
end
--figure out the height to make coordinate (i, k)
local distance = dist(i, k, x, z)
if distance < r1 then
height = y + d
elseif distance < r2 then
height = math.floor((y + d) * (1 - (distance - r1)/(r2 - r1)) + oldheightmap[i][k] * (distance - r1)/(r2 - r1))
else
height = oldheightmap[i][k]
end
if height == 0 then
height = -1
end
--heightmap[i][k] should be the current height of coordinate (i, k)
if heightmap[i] == nil then
heightmap[i] = {}
end
if heightmap[i][k] == nil then
heightmap[i][k] = oldheightmap[i][k]
end
--the height is either greater than or less than the current height
if height > heightmap[i][k] then
for h = heightmap[i][k] - 2, height do
SetCell(c, i, h, k, elevationOptions.defaultTerrainMaterial, 0, 0)
end
heightmap[i][k] = height
elseif height < heightmap[i][k] then
for h = heightmap[i][k], height + 1, -1 do
SetCell(c, i, h, k, 0, 0, 0)
end
heightmap[i][k] = height
end
end
end
--copy heightmap into shellheightmap
shellheightmap = {}
for i = x - (range + 2), x + (range + 2) do
if shellheightmap[i] == nil then
shellheightmap[i] = {}
end
for k = z - (range + 2), z + (range + 2) do
shellheightmap[i][k] = heightmap[i][k]
end
end
--shell everything
for i = x - range , x + range do
for k = z - range, z + range do
if shellheightmap[i][k] ~= oldheightmap[i][k] then
shellheightmap = makeShell(i, k, heightmap, shellheightmap)
end
end
end
for i = x - (range + 2), x + (range + 2) do
for k = z - (range + 2), z + (range + 2) do
heightmap[i][k] = shellheightmap[i][k]
end
end
for k = z - (range + 1), z + (range + 1) do
for i = x - (range + 1), x + (range + 1) do
local height = heightmap[i][k]
if height == nil then
height = -1
end
-- Autowedge if enabled.
if elevationOptions.autowedge then
for h = height, 1, -1 do
if not AutoWedge(c, i, h, k) then
break
end
end
end
end
end
end
function dist(x1, y1, x2, y2)
return math.sqrt(math.pow(x2-x1, 2) + math.pow(y2-y1, 2))
end
function dist3d(x1, y1, z1, x2, y2, z2)
return math.sqrt(math.pow(dist(x1, z1, x2, z2), 2) + math.pow(math.abs(y2-y1)*100/d, 2))
end
-- Run when the mouse gets clicked. If the click is on terrain, then it will be used as the starting point of the elevation area.
function onClicked(mouse)
if on then
oldheightmap = {}
heightmap = {}
local cellPos = WorldToCellPreferEmpty(c, Vector3.new(mouse.Hit.x, mouse.Hit.y, mouse.Hit.z))
local solidCell = WorldToCellPreferSolid(c, Vector3.new(mouse.Hit.x, mouse.Hit.y, mouse.Hit.z))
local success = false
-- If nothing was hit, do the plane intersection.
if 0 == GetCell(c, solidCell.X, solidCell.Y, solidCell.Z).Value then
--print('Plane Intersection happens')
success, cellPos = PlaneIntersection(Vector3.new(mouse.Hit.x, mouse.Hit.y, mouse.Hit.z))
if not success then
cellPos = solidCell
end
end
local x = cellPos.X
local y = cellPos.Y
local z = cellPos.Z
local celMat = GetCell(c, x, y, z)
if celMat.Value > 0 then
elevationOptions.defaultTerrainMaterial = celMat.Value
local isWater
isWater, elevationOptions.waterForce, elevationOptions.waterDirection = GetWaterCell(c, cellPos.X, cellPos.Y, cellPos.Z)
else
if 0 == elevationOptions.defaultTerrainMaterial then
-- It was nothing, give it a default type and the plane intersection.
elevationOptions.isWater = false
elevationOptions.defaultTerrainMaterial = 1
end
end
-- Hide the selection area while dragging.
mouseHighlighter:DisablePreview()
mousedown = true
local originalY = mouse.Y
local prevY = originalY
local d = 0
local range = 0
while mousedown == true do
if math.abs(mouse.Y - prevY) >= 5 then
prevY = mouse.Y
r2 = elevationOptions.r + math.floor(50 * 1/elevationOptions.s * math.abs(originalY - prevY)/mouse.ViewSizeY)
if r2 > range then
range = r2
end
d = math.floor(50 * (originalY - prevY)/mouse.ViewSizeY)
elevate(x, y, z, elevationOptions.r, r2, d, range)
end
wait(0)
end
game:GetService("ChangeHistoryService"): SetWaypoint("Elevation")
end
end
mouseHighlighter.OnClicked = onClicked
mouse = plugin:GetMouse()
mouse.Button1Up:connect(function()
mousedown = false
mouseHighlighter:EnablePreview()
end)
-- Run when the popup is activated.
function On()
if not c then
return
end
plugin:Activate(true)
toolbarbutton:SetActive(true)
elevationPropertiesDragBar.Visible = true
on = true
end
-- Run when the popup is deactivated.
function Off()
toolbarbutton:SetActive(false)
on = false
-- Hide the popup gui.
elevationPropertiesDragBar.Visible = false
mouseHighlighter:DisablePreview()
end
plugin.Deactivation:connect(function()
Off()
end)
loaded = true