2013/terrain plugins/03 - elevation.luau

651 lines
16 KiB
Plaintext

while not game do
wait()
end
local ChangeHistoryService = game:GetService "ChangeHistoryService"
local CoreGui = game:GetService "CoreGui"
local CreateStandardLabel = require "../Modules/Terrain/CreateStandardLabel"
local New = require("../Modules/New").New
---------------
--PLUGIN SETUP-
---------------
local loaded = false
-- True if the plugin is on, false if not.
local on = false
local On, Off
local plugin = PluginManager():CreatePlugin() :: Plugin
local mouse = plugin:GetMouse()
local toolbar = plugin:CreateToolbar "Terrain" :: Toolbar
local toolbarbutton = toolbar:CreateButton(
"Elevation Adjuster",
"Elevation Adjuster",
"elevation.png"
) :: Button
toolbarbutton.Click:connect(function()
if on then
Off()
elseif loaded then
On()
end
end)
game:WaitForChild "Workspace"
workspace:WaitForChild "Terrain"
local c = 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.
local waterMaterialID = 17
-- Elevation related options. Contains everything needed for making the elevation.
local 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.
local mouseHighlightColor = BrickColor.new "Lime green"
-- 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.
local function PlaneIntersection(vectorPos)
local currCamera = 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
-- 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.
local MouseHighlighter = {}
MouseHighlighter.__index = MouseHighlighter
-- Create a mouse movement highlighter.
-- plugin - Plugin to get the mouse from.
function MouseHighlighter.Create(mouseUse)
local highlighter = {}
local mouseH = 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
-- Update where the highlighter is displayed.
-- position - Where to display the highlighter, in world space.
local function UpdatePosition(position: Vector3)
if not position then
return
end
-- NOTE:
-- Change this gui to be the one you want to use.
highlighter.selectionBox.Parent = 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
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(mouseH.Hit.x, mouseH.Hit.y, mouseH.Hit.z)
)
else
cellPos = WorldToCellPreferEmpty(
c,
Vector3.new(mouseH.Hit.x, mouseH.Hit.y, mouseH.Hit.z)
)
end
end
end
-- Function to call when the mouse has moved. Updates where to display the highlighter.
local function MouseMoved()
if on then
UpdatePosition(mouseH.Hit)
end
end
-- Hook the mouse up to check for movement.
mouseH.Move:connect(function()
MouseMoved()
end)
mouseH.Button1Down:connect(function()
highlighter.mouseDown = true
end)
mouseH.Button1Up:connect(function()
highlighter.mouseDown = false
end)
-- Create the part that the highlighter will be attached to.
highlighter.selectionPart = New "Part" {
Name = "SelectionPart",
Archivable = false,
Transparency = 1,
Anchored = true,
Locked = true,
CanCollide = false,
FormFactor = Enum.FormFactor.Custom,
}
highlighter.selectionBox = New "SelectionBox" {
Archivable = false,
Color = mouseHighlightColor,
Adornee = highlighter.selectionPart,
}
mouseH.TargetFilter = highlighter.selectionPart
setmetatable(highlighter, MouseHighlighter)
return highlighter
end
-- Hide the highlighter.
function MouseHighlighter:DisablePreview()
self.selectionBox.Parent = nil
end
-- Show the highlighter.
function MouseHighlighter:EnablePreview()
self.selectionBox.Parent = CoreGui -- This will make it not show up in workspace.
end
-- Create the mouse movement highlighter.
local mouseHighlighter = MouseHighlighter.Create(mouse)
------
--GUI-
------
--screengui
local g = Instance.new "ScreenGui"
g.Name = "ElevationGui"
g.Parent = CoreGui
-- UI gui load. Required for sliders.
local RbxGui = LoadLibrary "RbxGui"
-- 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.
local 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 helpText = [[
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.]]
New "TextLabel" {
Name = "HelpText",
Font = Enum.Font.ArialBold,
FontSize = Enum.FontSize.Size12,
TextColor3 = Color3.new(227 / 255, 227 / 255, 227 / 255),
TextXAlignment = Enum.TextXAlignment.Left,
TextYAlignment = Enum.TextYAlignment.Top,
Position = UDim2.new(0, 4, 0, 4),
Size = UDim2.new(1, -8, 0, 177),
BackgroundTransparency = 1,
TextWrapped = true,
Text = helpText,
Parent = elevationHelpFrame,
}
-- Slider for controlling radius.
local radiusLabel = CreateStandardLabel(
"RadiusLabel",
UDim2.new(0, 8, 0, 10),
UDim2.new(0, 67, 0, 14),
"",
elevationFrame
)
local _, radiusSliderPosition = CreateStandardSlider(
"radiusSliderGui",
UDim2.new(0, 1, 0, 26),
UDim2.new(0, 10, 0.5, -2),
11,
function(radiusSliderPosition2)
elevationOptions.r = radiusSliderPosition2.Value -- 1
radiusLabel.Text = "Radius: " .. elevationOptions.r
end,
nil,
elevationFrame
)
radiusSliderPosition.Value = 1
-- Slider for controlling the z offset to generate terrain at.
local slopeLabel = CreateStandardLabel(
"SlopeLabel",
UDim2.new(0, 8, 0, 51),
UDim2.new(0, 67, 0, 14),
"",
elevationFrame
)
local _, slopeSliderPosition
_, 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)
local h = 0
local material, _, _ = GetCell(c, x, h + 1, z)
while material.Value > 0 do
h = h + 1
material, _, _ = 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
local height
local oldheightmap = {}
local heightmap = {}
local function dist(x1, y1, x2, y2)
return math.sqrt(math.pow(x2 - x1, 2) + math.pow(y2 - y1, 2))
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
local 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
local 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 height2 = heightmap[i][k]
if height2 == nil then
height2 = -1
end
-- Autowedge if enabled.
if elevationOptions.autowedge then
for h = height2, 1, -1 do
if not AutoWedge(c, i, h, k) then
break
end
end
end
end
end
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
local mousedown = false
-- 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.
local function onClicked()
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
_, 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
local 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()
end
ChangeHistoryService:SetWaypoint "Elevation"
end
end
mouse.Button1Down:connect(onClicked)
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