------------------------------------------------------------------------------------------------
-- Initialization
------------------------------------------------------------------------------------------------

local Debris = game:GetService("Debris")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local ServerStorage = game:GetService("ServerStorage")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local CollectionService = game:GetService("CollectionService")

local function getFlag(name)
	local flag = ServerStorage:FindFirstChild(name)
	return (flag and flag:IsA("BoolValue") and flag.Value)
end

local enableBevels = getFlag("EnableBevels")
local debugMode = getFlag("DevTestMode")

local bevelData = Instance.new("StringValue")
bevelData.Name = "BevelData"
bevelData.Archivable = false

local bevelCache = ServerStorage:FindFirstChild("BevelCache")
local bevelsReady = bevelCache and bevelCache:FindFirstChild("BevelsReady")

if not bevelCache then
	bevelCache = Instance.new("Folder")
	bevelCache.Name = "BevelCache"
	bevelCache.Parent = ServerStorage
end

if not bevelsReady then
	bevelsReady = Instance.new("BoolValue")
	bevelsReady.Name = "BevelsReady"
	bevelsReady.Parent = bevelCache
	bevelsReady.Archivable = false
end

if not enableBevels then
	bevelData.Parent = ReplicatedStorage
	bevelsReady.Value = true
	return
else
	local ids = {}
	local idMap = {}

	for _,part in pairs(bevelCache:GetChildren()) do
		if part:IsA("MeshPart") then
			local id = part.MeshId

			if not idMap[id] then
				idMap[id] = true
				table.insert(ids, id)
			end
		end
	end
	
	bevelData.Value = table.concat(ids, ";")
	bevelData.Parent = ReplicatedStorage
end

local regen = ServerStorage:FindFirstChild("Regeneration")

if regen then
	local ready = regen:WaitForChild("Ready")
	
	while not ready.Value do
		ready.Changed:Wait()
	end
end

local loadBuildTools = ServerStorage:FindFirstChild("LoadBuildTools")
local hasBuildTools = (loadBuildTools ~= nil)

------------------------------------------------------------------------------------------------

local edgeDepth = 1 / 15
local cornerDepth = edgeDepth * math.sqrt(8 / 3)

local mirrorProps = 
{
	"Anchored",
	"CanCollide",
	"CastShadow",
	"CFrame",
	"CollisionGroupId",
	"CustomPhysicalProperties",
	"Color",
	"Locked",
	"Material",
	"Name",
	"Reflectance",
	"RotVelocity",
	"Transparency",
	"Velocity",
}

local surfaceProps =
{
	"ParamA",
	"ParamB",
	"Surface",
	"SurfaceInput"
}

local bevelHash = "%.2f ~ %.2f ~ %.2f"
local isStudio = RunService:IsStudio()

local negateBase = Instance.new("Part")
negateBase.Name = "__negateplane"
negateBase.CanCollide = false
negateBase.BottomSurface = 0
negateBase.Transparency = 1
negateBase.Anchored = true
negateBase.TopSurface = 0
negateBase.Locked = true

CollectionService:AddTag(negateBase, "NoBevels")

for _,normalId in pairs(Enum.NormalId:GetEnumItems()) do
	local name = normalId.Name
	for _,surfaceProp in pairs(surfaceProps) do
		table.insert(mirrorProps, name .. surfaceProp)
	end
end

------------------------------------------------------------------------------------------------

local overload = 0
local threshold = Vector3.new(30, 30, 30)

if ServerStorage:FindFirstChild("BevelThreshold") then
	threshold = ServerStorage.BevelThreshold.Value
end

local function debugPrint(...)
	if debugMode then
		warn("[BEVELS DEBUG]:", ...)
	end
end

local function isPartOfHumanoid(object)
	local model = object:FindFirstAncestorOfClass("Model")
	
	if model then
		if model:FindFirstChildOfClass("Humanoid") then
			return true
		else
			return isPartOfHumanoid(model)
		end
	end
	
	return false
end

local function canGiveBevels(part)
	if part.Parent and part:IsA("Part") and not CollectionService:HasTag(part, "NoBevels") then
		if not isPartOfHumanoid(part) and not part:FindFirstChildWhichIsA("DataModelMesh") then
			local inThreshold = false
			local diff = threshold - part.Size
			
			if diff.X >= 0 and diff.Y >= 0 and diff.Z >= 0 then
				inThreshold = true
			end
			
			if inThreshold then
				if CollectionService:HasTag(part, "ForceBevels") then
					return true
				else
					return part.Shape.Name == "Block" and part.Transparency < 1
				end
			end
		end
	end
	
	return false
end

local function createProxyPart(part, name, tag, sizeChange)
	local proxyPart = Instance.new("Part")
	proxyPart.Name = name
	proxyPart.Locked = true
	proxyPart.TopSurface = 0
	proxyPart.Massless = true
	proxyPart.Transparency = 1
	proxyPart.BottomSurface = 0
	proxyPart.CanCollide = false
	proxyPart.CFrame = part.CFrame
	
	local size = part.Size
	if sizeChange then
		size = size + sizeChange
	end
	
	local proxyWeld = Instance.new("Weld")
	proxyWeld.Name = "ProxyWeld"
	proxyWeld.Part1 = proxyPart
	proxyWeld.Part0 = part
	
	if hasBuildTools then
		local mesh = Instance.new("SpecialMesh")
		mesh.Scale = size * 20
		mesh.MeshType = "Brick"
		mesh.Offset = part.Size
		mesh.Parent = proxyPart
		
		proxyPart.Size = Vector3.new(.05, .05, .05)
		proxyWeld.C0 = CFrame.new(-mesh.Offset)
	else
		proxyPart.Size = part.Size
	end
	
	CollectionService:AddTag(proxyPart, tag)
	CollectionService:AddTag(proxyPart, "NoBevels")
	CollectionService:AddTag(proxyWeld, "GorillaGlue")
	
	proxyWeld.Parent = proxyPart
	proxyPart.Parent = part
	
	return proxyPart
end

local function createBevels(part, initializing)
	if not canGiveBevels(part) or isPartOfHumanoid(part) then
		return
	end
	
	local size = part.Size
	local sx, sy, sz = size.X, size.Y, size.Z
	local bevelKey = bevelHash:format(sx, sy, sz)
	
	local debugBox
	
	if debugMode then
		debugBox = Instance.new("BoxHandleAdornment")
		
		debugBox.Color3 = Color3.new(0, 2, 2)
		debugBox.AlwaysOnTop = true
		debugBox.Name = "DebugBox"
		debugBox.Size = size
		debugBox.ZIndex = 0
		
		debugBox.Adornee = part
		debugBox.Parent = part
	end
	
	if not bevelCache:FindFirstChild(bevelKey) then
		local halfSize = size / 2
		
		local planeScale = math.max(sx, sy, sz)
		local planes = {}
		
		local solverPart = part:Clone()
		solverPart.CFrame = CFrame.new()
		solverPart.BrickColor = BrickColor.new(-1)
		
		debugPrint("Solving:", bevelKey)
		
		for x = -1, 1 do
			local x0 = (x == 0)
			
			for y = -1, 1 do
				local y0 = (y == 0)
				
				for z = -1, 1 do
					local z0 = (z == 0)
					
					local isCenter = (x0 and y0 and z0)
					local isFace = ((x0 and y0) or (y0 and z0) or (z0 and x0))
					
					if not (isCenter or isFace) then
						local isCorner = (not x0 and not y0 and not z0)
						local depth = isCorner and cornerDepth or edgeDepth
						
						local offset = Vector3.new(x, y, z)
						local cornerPos = (halfSize * offset)
						
						local plane = negateBase:Clone()
						plane.CFrame = CFrame.new(cornerPos, cornerPos + offset)
						plane.Size = Vector3.new(planeScale, planeScale, depth)
						plane.Parent = part
						
						table.insert(planes, plane)
					end
				end
			end
		end
		
		local success, union = pcall(function ()
			return solverPart:SubtractAsync(planes, "Box")
		end)
		
		if success then
			union.Name = bevelKey
			union.UsePartColor = true
			union.Parent = bevelCache
			
			CollectionService:AddTag(union, "HasBevels")
			
			if debugBox then
				debugBox.Color3 = Color3.new(0, 2, 0)
			end
		elseif debugBox then
			debugBox.Color3 = Color3.new(2, 0, 0)
		end
		
		for _,plane in pairs(planes) do
			plane:Destroy()
		end
		
		overload = 0
	else
		if debugBox then
			debugBox.Color3 = Color3.new(2, 0, 2)
		end
		
		overload = overload + 1
		
		if overload % 10 == 0 then
			RunService.Heartbeat:Wait()
		end
	end
	
	local baseUnion = bevelCache:FindFirstChild(bevelKey)
	
	if baseUnion then
		local archivable = baseUnion.Archivable
		baseUnion.Archivable = true
		
		local union = baseUnion:Clone()
		baseUnion.Archivable = archivable
		
		for _,prop in ipairs(mirrorProps) do
			union[prop] = part[prop]
		end
		
		for _,joint in pairs(part:GetJoints()) do
			if joint:IsA("JointInstance") or joint:IsA("WeldConstraint") then
				if joint.Part0 == part then
					joint.Part0 = union
				elseif joint.Part1 == part then
					joint.Part1 = union
				end
			end
		end
		
		for _,child in pairs(part:GetChildren()) do
			if not child:IsA("TouchTransmitter") and not child:IsA("Texture") then
				if child:IsA("BaseScript") then
					child.Disabled = true
				end
				
				child.Parent = union
				
				if child:IsA("BaseScript") then
					child.Disabled = false
				end
			end
		end
		
		if not initializing then
			wait()
		end
		
		if CollectionService:HasTag(part, "DoUnlock") then
			union.Locked = false
		end
		
		if part.ClassName ~= "Part" then
			local holder = Instance.new("Weld")
			holder.Part0 = part
			holder.Part1 = union
			holder.Parent = part
			
			union.Anchored = false
			union.Massless = true
			union.Parent = part
			
			part.Transparency = 1
			CollectionService:AddTag(holder, "GorillaGlue")
		else
			local parent = part.Parent
			part:Destroy()
			
			union.Parent = parent
		end
	elseif debugBox then
		debugBox.Color3 = Color3.new(2, 0, 0)
	end
	
	if debugBox then
		debugBox.Transparency = 0.5
		Debris:AddItem(debugBox, 2)
	end
end

------------------------------------------------------------------------------------------------

do
	warn("Solving bevels...")
	
	-- Collect all blocks currently in the workspace.
	local initialPass = {}
	local debugHint
	
	for _,desc in pairs(workspace:GetDescendants()) do
		if canGiveBevels(desc) then
			if not desc.Locked then
				CollectionService:AddTag(desc, "DoUnlock")
				desc.Locked = true
			end
			
			table.insert(initialPass, desc)
		end
	end
	
	if debugMode then
		debugHint = Instance.new("Hint")
		debugHint.Text = "Generating Bevels..."
		debugHint.Parent = workspace
	end
	
	-- Run through the initial bevel creation phase.
	for _,block in ipairs(initialPass) do
		createBevels(block, true)
	end
	
	if debugHint then
		debugHint:Destroy()
	end
end

-- Listen for new parts being added.
workspace.DescendantAdded:Connect(createBevels)

-- Allow regeneration to request bevel solving
local bevelSolver = bevelCache:FindFirstChild("RequestSolve")

if not bevelSolver then
	bevelSolver = Instance.new("BindableFunction")
	bevelSolver.Name = "RequestSolve"
	bevelSolver.Parent = bevelCache
	bevelSolver.Archivable = false
end

function bevelSolver.OnInvoke(inst)
	for _,desc in pairs(inst:GetDescendants()) do
		if desc:IsA("Part") then
			createBevels(desc)
		end
	end
end

if RunService:IsStudio() then
	local exportBin = Instance.new("Folder")
	exportBin.Name = "ExportBin"
	exportBin.Parent = ServerStorage
	
	for _,v in pairs(bevelCache:GetChildren()) do
		if v:IsA("TriangleMeshPart") and v.Archivable then
			v:Clone().Parent = exportBin
		end
	end
	
	wait(.1)
	
	for _,v in pairs(exportBin:GetChildren()) do
		if v:FindFirstChild("LOD") then
			v.LOD:Destroy()
		end
	end
end

-- Ready!
warn("Bevels ready!")
bevelsReady.Value = true

------------------------------------------------------------------------------------------------