--!strict

local Players = game:GetService("Players") :: Players
local RunService = game:GetService("RunService") :: RunService
local TweenService = game:GetService("TweenService") :: TweenService

local TypedRemote = require(game.ReplicatedStorage.SharedPackages.TypedRemote)

local Replication = {}

type ReplicationFrame = {
	part: BasePart,
	offset: CFrame,
}

type ClientReplicationFrame = ReplicationFrame & {
	fromOffset: CFrame,
	lerpOffset: CFrame,
	receivedAt: number,
}

-- stylua: ignore start
local syncRemote = TypedRemote.func("SyncRemote", script) :: TypedRemote.Function<(), ({ [Player]: ReplicationFrame })>
local replicatorRemote = TypedRemote.event("ReplicatorRemote", script) :: TypedRemote.Event<BasePart?, CFrame?, Player?>
-- stylua: ignore end

Replication.ENABLED = true
Replication.REPLICATE_DEBOUNCE_TIME = 0.2

function Replication.send(part: BasePart, offset: CFrame)
	assert(RunService:IsClient(), "This API can only be called from the client")
	if Replication.ENABLED then
		replicatorRemote:FireServer(part, offset)
	end
end

function Replication.listenServer()
	assert(RunService:IsServer(), "This API can only be called from the server")

	if not Replication.ENABLED then
		return
	end

	local framesByPlayer: { [Player]: ReplicationFrame } = {}

	Players.PlayerRemoving:Connect(function(player)
		framesByPlayer[player] = nil
		replicatorRemote:FireAllClients(nil, nil, player)
	end)

	replicatorRemote.OnServerEvent:Connect(function(player, part, offset)
		if part and offset then
			framesByPlayer[player] = {
				part = part,
				offset = offset,
			}
		else
			framesByPlayer[player] = nil
		end

		replicatorRemote:FireAllClients(part, offset, player)
	end)

	syncRemote.OnServerInvoke = function(_player)
		return framesByPlayer
	end
end

function Replication.listenClient()
	assert(RunService:IsClient(), "This API can only be called from the client")

	if not Replication.ENABLED then
		return
	end

	task.spawn(function()
		local clientFrameByPlayer: { [Player]: ClientReplicationFrame } = {}
		for player, frame in syncRemote:InvokeServer() do
			clientFrameByPlayer[player] = {
				part = frame.part,
				offset = frame.offset,
				fromOffset = frame.offset,
				lerpOffset = frame.offset,
				receivedAt = os.clock(),
			}
		end

		replicatorRemote.OnClientEvent:Connect(function(part, offset, sentPlayer)
			local player = sentPlayer :: Player

			if part and offset then
				local now = os.clock()
				local frame: ClientReplicationFrame = clientFrameByPlayer[player]
					or {
						part = part,
						offset = offset,
						fromOffset = offset,
						lerpOffset = offset,
						receivedAt = now,
					}

				local worldCF = frame.part.CFrame * frame.lerpOffset
				local fromOffset = part.CFrame:ToObjectSpace(worldCF)

				clientFrameByPlayer[player] = {
					part = part,
					offset = offset,
					fromOffset = fromOffset,
					lerpOffset = fromOffset,
					receivedAt = now,
				}
			else
				clientFrameByPlayer[player] = nil
			end
		end)

		RunService.PreRender:Connect(function(_dt)
			local now = os.clock()
			for player, frame in clientFrameByPlayer do
				if player == Players.LocalPlayer then
					continue
				end

				local character = player.Character
				local rootPart = character and character:FindFirstChild("HumanoidRootPart") :: BasePart

				if rootPart and frame.part then
					local dt = now - frame.receivedAt
					local alpha = TweenService:GetValue(
						math.min(1, dt / Replication.REPLICATE_DEBOUNCE_TIME),
						Enum.EasingStyle.Linear,
						Enum.EasingDirection.Out
					)

					frame.lerpOffset = frame.fromOffset:Lerp(frame.offset, alpha)

					rootPart.CFrame = frame.part.CFrame * frame.lerpOffset
					rootPart.Anchored = true
				end
			end
		end)
	end)
end

return Replication


--!strict

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local SharedPackages = ReplicatedStorage.SharedPackages
local Trove = require(SharedPackages.Trove)

local Replication = require(script.Replication)
local GravityCamera = require(script.GravityCamera)
local RotationSpring = require(script.RotationSpring)
local CharacterHelper = require(script.CharacterHelper)

local globalRenderTicket = 0

local CLASS_NAMES_TO_CONVERT = {
	["Seat"] = { ClassName = "Part" },
	["VehicleSeat"] = { ClassName = "Part" },
	["SpawnLocation"] = { ClassName = "Part" },

	["Terrain"] = {
		ClassName = "Part",
		Size = Vector3.new(1, 1, 1),
		CanCollide = false,
	},
}

-- Class

local WallstickClass = {}
WallstickClass.__index = WallstickClass
WallstickClass.ClassName = "Wallstick"

-- Types

export type Options = {
	parent: Instance,
	origin: CFrame,

	retainWorldVelocity: boolean,

	camera: {
		tilt: boolean,
		spin: boolean,
	},
}

export type Wallstick = typeof(setmetatable(
	{} :: {
		trove: Trove.Trove,

		options: Options,
		fallStartHeight: number,
		replicateTick: number,

		part: BasePart,
		normal: Vector3,

		cameraUpSpring: RotationSpring.RotationSpring,

		geometry: Folder,
		cachedCollisionGeometry: { [BasePart]: BasePart },

		real: CharacterHelper.RealCharacter,
		fake: CharacterHelper.FakeCharacter,
		streamingFocus: CharacterHelper.StreamingFocus,
	},
	WallstickClass
))

-- Constructors

function WallstickClass.new(options: Options): Wallstick
	local self = setmetatable({}, WallstickClass) :: Wallstick

	self.trove = Trove.new()

	self.options = table.clone(options)
	self.fallStartHeight = -1
	self.replicateTick = -1

	self.part = workspace.Terrain
	self.normal = Vector3.yAxis

	self.cameraUpSpring = RotationSpring.new(1, 3, CFrame.identity, CFrame.identity)

	self.geometry = Instance.new("Folder")
	self.geometry.Name = "Geometry"
	self.geometry.Parent = self.options.parent
	self.trove:Add(self.geometry)

	self.cachedCollisionGeometry = {}

	self.real = CharacterHelper.real(Players.LocalPlayer)
	self.fake = CharacterHelper.fake(Players.LocalPlayer)
	self.streamingFocus = assert(CharacterHelper.findStreamingFocus(Players.LocalPlayer), "No streaming focus found.")

	self.trove:Add(CharacterHelper.applyCollisionGroup(self.real.character, "WallstickNoCollision"))

	self.real.humanoid.PlatformStand = true
	self.real.rootPart.Anchored = false

	self.fake.character.Parent = self.options.parent
	self.trove:Add(self.fake.character)

	CharacterHelper.setMyPerformer(self.real.character, self.fake.character)

	self:set(workspace.Terrain, Vector3.yAxis)

	self.trove:Add(self.fake.humanoid.StateChanged:Connect(function(_, newState)
		if newState == Enum.HumanoidStateType.Freefall then
			self.fallStartHeight = self.fake.rootPart.Position.Y
		end
	end))

	self.trove:Add(RunService.PostSimulation:Connect(function(dt)
		self:_stepPhysics(dt)
	end))

	globalRenderTicket = globalRenderTicket + 1

	local renderBeforeCameraBindKey = "WallstickBeforeCamera" .. tostring(globalRenderTicket)
	local renderCharacterBindKey = "WallstickCharacter" .. tostring(globalRenderTicket)

	RunService:BindToRenderStep(renderBeforeCameraBindKey, Enum.RenderPriority.Camera.Value - 1, function(dt)
		self:_stepRenderBeforeCamera(dt)
	end)

	RunService:BindToRenderStep(renderCharacterBindKey, Enum.RenderPriority.Character.Value, function(dt)
		self:_stepRenderCharacter(dt)
	end)

	self.trove:Add(function()
		RunService:UnbindFromRenderStep(renderBeforeCameraBindKey)
		RunService:UnbindFromRenderStep(renderCharacterBindKey)

		GravityCamera.setSpinPart(workspace.Terrain)
		GravityCamera.setUpVector(Vector3.yAxis)

		CharacterHelper.setMyPerformer(self.real.character, nil)

		self.real.humanoid.EvaluateStateMachine = true
		self.real.rootPart.Anchored = false

		self.streamingFocus.part.CFrame = self.options.origin
		self.streamingFocus.alignPosition.Position = self.options.origin.Position
		self.streamingFocus.alignOrientation.CFrame = self.options.origin
	end)

	return self
end

-- Private

local function fromToRotation(from: Vector3, to: Vector3, backupUnitAxis: Vector3?)
	local dot = from:Dot(to)
	if dot < -0.99999 then
		return if backupUnitAxis
			then CFrame.fromAxisAngle(backupUnitAxis, math.pi)
			else CFrame.fromRotationBetweenVectors(from, to)
	end
	local qv = from:Cross(to)
	local qw = math.sqrt(from:Dot(from) * to:Dot(to)) + dot
	return CFrame.new(0, 0, 0, qv.X, qv.Y, qv.Z, qw)
end

function WallstickClass._getOriginCFrame(self: Wallstick)
	return self.options.origin * fromToRotation(self.normal, Vector3.yAxis, Vector3.xAxis)
end

function WallstickClass._getCalculatedRealRootCFrame(self: Wallstick)
	local originCF = self:_getOriginCFrame()
	local offset = originCF:ToObjectSpace(self.fake.rootPart.CFrame)
	return self.part.CFrame * offset
end

function WallstickClass._updateCollisionGeometry(self: Wallstick)
	local newCachedCollisionGeometry = {}

	local originCF = self:_getOriginCFrame()
	local realRootCF = self.real.rootPart.CFrame
	local stickCFInv = self.part.CFrame:Inverse()

	local colliderBoxSizeHalf = Vector3.new(10, 10, 10)

	-- stylua: ignore
	-- selene: allow (deprecated)
	local parts = workspace:FindPartsInRegion3WithIgnoreList(Region3.new(
		realRootCF.Position - colliderBoxSizeHalf,
		realRootCF.Position + colliderBoxSizeHalf
	), {self.real.character, self.options.parent} :: {Instance}, 1000) :: {BasePart}

	for _, realPart in parts do
		local collisionPart: BasePart
		local foundPart = self.cachedCollisionGeometry[realPart]

		if not foundPart then
			local properties = CLASS_NAMES_TO_CONVERT[realPart.ClassName] :: { [string]: any }

			if properties then
				local convertedPart = Instance.new(properties.ClassName) :: any
				for key, value in properties do
					if key ~= "ClassName" then
						(convertedPart :: any)[key] = value
					end
				end

				collisionPart = convertedPart :: BasePart
			else
				collisionPart = realPart:Clone()
				collisionPart.Name = "Part"
				collisionPart:ClearAllChildren()
			end

			collisionPart.CollisionGroup = "WallstickCollision"
			collisionPart.Parent = self.geometry
		else
			collisionPart = foundPart
		end

		collisionPart.Anchored = true
		collisionPart.CastShadow = false
		collisionPart.AssemblyLinearVelocity = Vector3.zero
		collisionPart.AssemblyAngularVelocity = Vector3.zero
		collisionPart.CFrame = originCF * (stickCFInv * realPart.CFrame)
		collisionPart.Size = realPart.Size
		collisionPart.CanCollide = realPart.CanCollide
		collisionPart.Transparency = 1

		self.cachedCollisionGeometry[realPart] = collisionPart
		newCachedCollisionGeometry[realPart] = collisionPart
	end

	for realPart, collisionPart in self.cachedCollisionGeometry do
		if not newCachedCollisionGeometry[realPart] then
			self.cachedCollisionGeometry[realPart] = nil
			collisionPart:Destroy()
		end
	end
end

function WallstickClass._trySendReplication(self: Wallstick, force: boolean)
	local t = os.clock()
	if force or t - self.replicateTick >= Replication.REPLICATE_DEBOUNCE_TIME then
		self.replicateTick = t

		local realRootCFrame = self:_getCalculatedRealRootCFrame()
		local offset = self.part.CFrame:ToObjectSpace(realRootCFrame)

		Replication.send(self.part, offset)
	end
end

function WallstickClass._stepRenderBeforeCamera(self: Wallstick, dt: number)
	if self.options.camera.tilt then
		self.cameraUpSpring:step(dt)

		local upVector = self.cameraUpSpring:getPosition().YVector
		local worldUpVector = self.part.CFrame:VectorToWorldSpace(upVector)

		GravityCamera.setUpVector(worldUpVector)
	end
end

function WallstickClass._stepRenderCharacter(self: Wallstick, _dt: number)
	local realRootCF = self:_getCalculatedRealRootCFrame()
	local fakeRootCF = self.fake.rootPart.CFrame
	local rootCameraOffset = realRootCF:ToObjectSpace(workspace.CurrentCamera.CFrame)
	local geometryCameraCF = fakeRootCF * rootCameraOffset

	self.fake.humanoid.Jump = self.real.humanoid.Jump
	self.fake.humanoid:Move(GravityCamera.getMoveVector(geometryCameraCF), false)

	self.streamingFocus.alignPosition.Position = fakeRootCF.Position
	self.streamingFocus.alignOrientation.CFrame = fakeRootCF

	if GravityCamera.getRotationType() == Enum.RotationType.CameraRelative then
		local right = GravityCamera.getMoveVector(geometryCameraCF, Vector3.xAxis)
		local rotation = CFrame.fromMatrix(Vector3.zero, right, Vector3.yAxis)
		self.fake.alignOrientation.CFrame = rotation
		self.fake.alignOrientation.Enabled = true
	else
		self.fake.alignOrientation.Enabled = false
	end
end

function WallstickClass._stepPhysics(self: Wallstick, _dt: number)
	if self.fake.rootPart.Position.Y <= workspace.FallenPartsDestroyHeight then
		self:Destroy()
		return
	end

	local realRootCFrame = self:_getCalculatedRealRootCFrame()

	self.real.rootPart.CFrame = realRootCFrame
	self.real.alignPosition.Position = realRootCFrame.Position
	self.real.alignOrientation.CFrame = realRootCFrame

	self:_updateCollisionGeometry()
	self:_trySendReplication(false)
end

-- Public

function WallstickClass.getPart(self: Wallstick)
	return self.part
end

function WallstickClass.getNormal(self: Wallstick, worldSpace: boolean)
	if worldSpace then
		return self.part.CFrame:VectorToWorldSpace(self.normal)
	end
	return self.normal
end

function WallstickClass.getFallDistance(self: Wallstick)
	if self.fake.humanoid:GetState() == Enum.HumanoidStateType.Freefall then
		return self.fake.rootPart.Position.Y - self.fallStartHeight
	end
	return 0
end

function WallstickClass.set(self: Wallstick, part: BasePart, normal: Vector3, teleportCF: CFrame?)
	local prevPart = self.part
	local prevPartCF = prevPart.CFrame
	local worldUpCFrame = prevPartCF:ToWorldSpace(self.cameraUpSpring:getPosition())
	local worldUpVelocity = prevPartCF:VectorToWorldSpace(self.cameraUpSpring:getVelocity())

	local partCF = part.CFrame
	local newWorldNormal = partCF:VectorToWorldSpace(normal)
	local worldGoalUpCFrame = fromToRotation(worldUpCFrame.YVector, newWorldNormal, worldUpCFrame.XVector)
		* worldUpCFrame

	self.cameraUpSpring:setGoal(partCF:ToObjectSpace(worldGoalUpCFrame))
	self.cameraUpSpring:setPosition(partCF:ToObjectSpace(worldUpCFrame))
	self.cameraUpSpring:setVelocity(partCF:VectorToObjectSpace(worldUpVelocity))

	self.part = part
	self.normal = normal

	if self.options.camera.spin then
		GravityCamera.setSpinPart(self.part)
	end

	local originCF = self:_getOriginCFrame()
	local targetCF = originCF * self.part.CFrame:ToObjectSpace(teleportCF or self.real.rootPart.CFrame)
	local sphericalArc = fromToRotation(targetCF.YVector, Vector3.yAxis, targetCF.XVector)
	local resultCF = (sphericalArc * targetCF.Rotation) + targetCF.Position

	local fakeRoot = self.fake.rootPart
	local localRootVelocity = fakeRoot.CFrame:VectorToObjectSpace(fakeRoot.AssemblyLinearVelocity)
	local localRootAngularVelocity = fakeRoot.CFrame:VectorToObjectSpace(fakeRoot.AssemblyAngularVelocity)

	fakeRoot.CFrame = resultCF

	if self.options.retainWorldVelocity then
		fakeRoot.AssemblyLinearVelocity = targetCF:VectorToWorldSpace(localRootVelocity)
		fakeRoot.AssemblyAngularVelocity = targetCF:VectorToWorldSpace(localRootAngularVelocity)
	else
		fakeRoot.AssemblyLinearVelocity = resultCF:VectorToWorldSpace(localRootVelocity)
		fakeRoot.AssemblyAngularVelocity = resultCF:VectorToWorldSpace(localRootAngularVelocity)
	end

	self.fallStartHeight = fakeRoot.Position.Y

	self:_updateCollisionGeometry()

	if self.part ~= prevPart then
		self:_trySendReplication(true)
	end
end

function WallstickClass.setAndPivot(self: Wallstick, part: BasePart, normal: Vector3, position: Vector3)
	local worldNormal = part.CFrame:VectorToWorldSpace(normal)
	local realRootCF = self:_getCalculatedRealRootCFrame()
	local heightAdjust = (realRootCF.Position - position):Dot(worldNormal)

	local floorRootCF = realRootCF * CFrame.new(0, -heightAdjust, 0)
	local newRotation = fromToRotation(floorRootCF.YVector, worldNormal, floorRootCF.XVector) * floorRootCF.Rotation
	local teleportCF = CFrame.new(position) * newRotation * CFrame.new(0, heightAdjust, 0)

	return self:set(part, normal, teleportCF)
end

function WallstickClass.setAndTeleport(self: Wallstick, part: BasePart, normal: Vector3, position: Vector3)
	local worldNormal = part.CFrame:VectorToWorldSpace(normal)
	local realRootCF = self:_getCalculatedRealRootCFrame()
	local heightAdjust = self.real.rootPart.Size.Y / 2 + self.real.humanoid.HipHeight

	local floorRootCF = realRootCF * CFrame.new(0, -heightAdjust, 0)
	local newRotation = fromToRotation(floorRootCF.YVector, worldNormal, floorRootCF.XVector) * floorRootCF.Rotation
	local teleportCF = CFrame.new(position) * newRotation * CFrame.new(0, heightAdjust, 0)

	return self:set(part, normal, teleportCF)
end

function WallstickClass.Destroy(self: Wallstick)
	self.trove:Destroy()
end

--

return WallstickClass


--!strict

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local SharedPackages = ReplicatedStorage.SharedPackages
local RaycastHelper = require(SharedPackages.RaycastHelper)

local WallstickClass = require(ReplicatedStorage.Wallstick)
local Replication = require(ReplicatedStorage.Wallstick.Replication)

local function ignoreCharacterParts(result: RaycastResult)
	local hit = result.Instance :: BasePart
	local accessory = hit:FindFirstAncestorWhichIsA("Accessory")

	local character = (accessory or hit).Parent
	local humanoid = character and character:FindFirstChildWhichIsA("Humanoid")
	if character and humanoid then
		return not (accessory or humanoid:GetLimb(hit) ~= Enum.Limb.Unknown)
	end

	return true
end

local function onCharacterAdded(character: Model)
	local wallstickModel = workspace:WaitForChild("Wallstick") :: Model
	local wallstickOrigin = wallstickModel:WaitForChild("Origin") :: BasePart

	local wallstick = WallstickClass.new({
		parent = wallstickModel,
		origin = wallstickOrigin.CFrame,
		retainWorldVelocity = true,
		camera = {
			tilt = true,
			spin = true,
		},
	})

	local humanoid = character and character:WaitForChild("Humanoid") :: Humanoid
	local hrp = humanoid and humanoid.RootPart :: BasePart

	local rayParams = RaycastHelper.params({
		filterType = Enum.RaycastFilterType.Exclude,
		instances = { character :: Instance },
	})

	local simulationConnection = RunService.PreSimulation:Connect(function(_dt)
		if wallstick:getFallDistance() < -50 then
			wallstick:set(workspace.Terrain, Vector3.yAxis)
			return
		end

		local hipHeight = humanoid.HipHeight
		if humanoid.RigType == Enum.HumanoidRigType.R6 then
			hipHeight = 2
		end

		local hrpCF = hrp.CFrame
		local result = RaycastHelper.raycast({
			origin = hrpCF.Position,
			direction = -(hipHeight + hrp.Size.Y / 2 + 0.1) * hrpCF.YVector,
			filter = ignoreCharacterParts,
			rayParams = rayParams,
		})

		if result then
			local stickPart = (result.Instance :: BasePart).AssemblyRootPart
			local stickNormal = stickPart.CFrame:VectorToObjectSpace(result.Normal)

			wallstick:setAndPivot(stickPart, stickNormal, result.Position)
		end
	end)

	humanoid.Died:Wait()
	simulationConnection:Disconnect()
	wallstick:Destroy()
end

if Players.LocalPlayer.Character then
	onCharacterAdded(Players.LocalPlayer.Character)
end

Players.LocalPlayer.CharacterAdded:Connect(onCharacterAdded)

Replication.listenClient()


--!strict

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local SharedPackages = ReplicatedStorage.SharedPackages
local CharacterAnimate = require(SharedPackages.CharacterAnimate)

local animate = script.Parent
local character = animate.Parent
local performer = character:WaitForChild("Humanoid")

local module = {}
local controller

function module.matchAnimate(director: Humanoid)
	if controller then
		controller.cleanup()
	end

	controller = CharacterAnimate.animate(animate, director, performer)
end

animate:WaitForChild("PlayEmote").OnInvoke = function(emote)
	return controller.playEmote(emote)
end

module.matchAnimate(performer)

return module


--!strict

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local SharedPackages = ReplicatedStorage.SharedPackages
local CharacterSounds = require(SharedPackages.CharacterSounds)

local module = {}

-- Private

type PlayerState = {
	performer: Model?,
	terminateSound: (() -> ())?,
	connections: { RBXScriptConnection },
}

local playerStates: { [Player]: PlayerState } = {}

local function terminateSound(player: Player)
	local state = playerStates[player]
	if state and state.terminateSound then
		state.terminateSound()
		state.terminateSound = nil
	end
end

local function characterRemoving(player: Player, _character: Model)
	terminateSound(player)
end

local function characterAdded(player: Player, character: Model)
	characterRemoving(player, character)

	local state = playerStates[player]
	if state then
		local performer = state.performer or character
		local controller = CharacterSounds.listen(performer, character)

		state.terminateSound = controller.cleanup
	end
end

local function playerRemoving(player: Player)
	terminateSound(player)

	local state = playerStates[player]
	if state then
		for _, connection in state.connections do
			connection:Disconnect()
		end
	end

	playerStates[player] = nil
end

local function playerAdded(player: Player)
	playerRemoving(player)

	local state = {
		performer = nil,
		connections = {},
		terminateSound = nil,
	}

	if player.Character then
		characterAdded(player, player.Character)
	end

	table.insert(
		state.connections,
		player.CharacterAdded:Connect(function(character)
			characterAdded(player, character)
		end)
	)

	table.insert(
		state.connections,
		player.CharacterRemoving:Connect(function(character)
			characterRemoving(player, character)
		end)
	)

	playerStates[player] = state
end

for _, player in Players:GetPlayers() do
	task.spawn(playerAdded, player)
end

Players.PlayerAdded:Connect(playerAdded)
Players.PlayerRemoving:Connect(playerRemoving)

-- Public

function module.setPerformer(player: Player, performer: Model?)
	local state = playerStates[player]
	if state then
		state.performer = performer

		local character = player.Character
		if character then
			characterAdded(player, character)
		end
	end
end

--

return module


--!strict

local StarterPlayer = game:GetService("StarterPlayer")
local ServerScriptService = game:GetService("ServerScriptService")
local StarterPlayerScripts = StarterPlayer:WaitForChild("StarterPlayerScripts")
local StarterCharacterScripts = StarterPlayer:WaitForChild("StarterCharacterScripts")

local ServerPackages = ServerScriptService.ServerPackages
local PlayerModulePackage = require(ServerPackages.PlayerModule)

local module = {}

local function replaceIfExists(parent: Instance, instance: Instance)
	local found = parent:FindFirstChild(instance.Name)
	if found then
		found:Destroy()
	end

	instance.Parent = parent
end

function module.setup()
	local patched = PlayerModulePackage.getCopy(true)
	local modifiers = require(patched:WaitForChild("Modifiers")) :: any
	modifiers.add(script.GravityCameraModifier)
	PlayerModulePackage.replace(patched)

	local renamedCharacterSounds = script.CharacterSounds:Clone()
	renamedCharacterSounds.Name = "RbxCharacterSounds"

	replaceIfExists(StarterCharacterScripts, script.Animate:Clone())
	replaceIfExists(StarterPlayerScripts, renamedCharacterSounds)
end

return module


--[=[
	Host
	A sample script on how you would
	run a shader.
]=]

--!native

-- // Dependencies
local Client = game.Players.LocalPlayer
local RbxShader = require(game.ReplicatedStorage.RbxShader)

-- // Configurations
local CONFIGURATIONS = {
	InterlaceFactor = 2 ,
	DualAxisInterlacing = true ,
	ScreenDivision = 4 ,
	IsBlurred = false
}

local CANVAS_SIZE = Vector2.new(180, 120) -- try (192, 108) too!
local SHADER = script.Shaders.VoxelShader

local SCREEN = Instance.new('ScreenGui')
SCREEN.Parent = Client.PlayerGui
SCREEN.IgnoreGuiInset = true
SCREEN.ResetOnSpawn = false

RbxShader.new( SHADER.Name, SHADER, CONFIGURATIONS, script, SCREEN, CANVAS_SIZE)
RbxShader.run( SHADER.Name )

task.wait(5)

RbxShader.pause( SHADER.Name )

task.wait(5)

RbxShader.resume( SHADER.Name )


--[=[
	BasicVoxelTunnel
	Shader by @Miojo on https://www.shadertoy.com/view/mt2cWm
	Ported to Luau — ran by my engine — `RbxShader`.

	( Original comments are preserved. )
]=]

--!native

local g = require(game.ReplicatedStorage.RbxShader.GraphicsMathLib)

-- // Variables
local res = .6			-- @ voxel size
local max_iter = 80		-- @ raycast depth
local distFade = 6		-- @ distance fade
local speed = 2			-- @ travel speed

local lightClamp = distFade / 2

-- // The white part of the tunnel
local function path1 ( z : number )
	return Vector3.new(
		g.sin(z * .2) * 3 ,
		g.cos(z * .3) * 2 ,
		z
	)
end

-- // The yellow part of the tunnel
local function path2 ( z : number )
	return Vector3.new(
		g.sin(z * .2) * 1 ,
		g.cos(z * .3) * 4 - 1 ,
		z
	)
end

-- // The blue part of the tunnel
local function path3 ( z : number )
	return Vector3.new(
		g.sin(z * .3) * 3 ,
		g.cos(z * .2) - 1 ,
		z
	)
end

local cor : Vector3

local function map ( p : Vector3 )
	local d1 = (p - path1(p.Z)).Magnitude
	local d2 = (p - path2(p.Z)).Magnitude
	local d3 = (p - path3(p.Z)).Magnitude

	local s = 1.5
	local ret = s - g.min(d1, g.min(d2, d3))

	cor = Vector3.xAxis
	if ret == s - d1 then
		cor = Vector3.one
	elseif ret == s - d2 then
		cor = Vector3.new(1,1,0)
	elseif ret == s - d3 then
		cor = Vector3.new(0,1,1)
	end

	return ret
end

-- // Fragment shader
return {
	CONFIGURATION = {
		InterlaceFactor = 1 ,
		ScreenDivision = 4
	};
	-- // Image buffer
	mainImage = function (
		fragColor : Vector3 ,
		fragCoords : Vector2 ,
		iTime : number ,
		iTimeDelta : number ,
		iResolution : Vector2
	)
		local uv = (fragCoords - 0.5 * iResolution) / iResolution.Y

		-- // path
		local t = speed * iTime
		local ro = path1(t)
		local rd = path1(t + 1)

		-- // camera
		local f = (rd - ro).Unit
		local u = Vector3.yAxis
		local s = u:Cross(f).Unit
		u = f:Cross(s).Unit

		rd = (uv.X * s + uv.Y * u + f).Unit

		-- /*
		-- *    "Digital Differential Analysis" (DDA)
		-- */

		local mask, i

		local grid = (ro / res):Floor() * res
		local dGrid = rd:Sign() * res

		local dCubo = 1 / rd:Abs()
		local cubo = ( rd:Sign() * (grid - ro)
			+ (rd:Sign() * .5 + Vector3.one * .5) * res
		) * dCubo

		for __ = 1, max_iter do
			i = __
			if map(grid) < 0 then
				break
			end

			mask = g.step_v3(cubo, g.yzx(cubo)) * g.step_v3(cubo, g.zxy(cubo))

			grid += dGrid * mask
			cubo += dCubo * mask * res
		end

		local col = cor
		if i < max_iter then
			local d = (cubo * dCubo * res):Dot(mask)
			local p = ro + d * rd
			local q = g.fract_v3(p/res) - Vector3.one * 0.5

			-- // border
			q = q:Abs()
			local bd = 1 - g.smoothstep(.02, 0, .4 - .8 * g.yzx(q):Max(g.zxy(q)):Dot(mask))

			col += cor:Lerp(cor/2, bd) --mix(cor, cor/2, bd)
			--[[
			-- // sweeping line
			col *= g.cos(t/2) > uv.X and 2 or 1

			-- // dots
			col += Vector3.one * (g.cos(t/2) > uv.X and -1 or 1 - q.Magnitude * 4)
			]]
			-- // distance fade
			col *= distFade / (d * d) - .05

			-- // lights? shadows? oclusion?
		end

		-- // gamma correction
		col = g.sqrt_v3(col/lightClamp)
		return g.v3_rgb(col)
	end,
}


--[=[
	Cosmic
	Shader by @Xor on https://www.shadertoy.com/view/msjXRK
	Ported to Luau — ran by my engine — `RbxShader`.

	( Original comments are preserved. )
]=]

--!native

local g = require(game.ReplicatedStorage.RbxShader.GraphicsMathLib)
local vec3 = Vector3.new
local vec2 = Vector2.new

--[[
/*
    "Cosmic" by @XorDev

    I love making these glowy shaders. This time I thought I'd try using discs instead.

    Tweet: twitter.com/XorDev/status/1601060422819680256
    Twigl: t.co/IhRk3HX4Kt

    <300 chars playlist: shadertoy.com/playlist/fXlGDN
*/
]]

-- // Pixel shader
return {
	CONFIGURATION = {
		InterlaceFactor = 2 ,
		ScreenDivision = 4
	};
	-- // Image buffer
	mainImage = function (
		fragColor : Vector3 ,
		fragCoords : Vector2 ,
		iTime : number ,
		iTimeDelta : number ,
		iResolution : Vector2 ,
		iMouse : Vector2
	)
		-- // Clear fragcolor (hacky)
		fragColor *= 0;
		-- // Initialize resolution for scaling
		local r : Vector2 = iResolution;
		-- // Save centered pixel coordinates
		local p = (fragCoords-r*.6);
		p = vec2(
			(p.X) + (2*p.Y),
			(-p.X) + (2*p.Y)
		);

		-- // Initialize loop iterator and arc angle
		local a : number = 0;
		for i = 1, 30 do
			-- // Add with ring attenuation
			fragCoords = p/(r+r-p).Y;
			a = g.atan2(fragCoords.Y, fragCoords.X)*g.ceil(i*.1)+iTime*g.sin(i*i)+i*i;
			fragColor += Vector3.one * .2 / (g.abs(fragCoords.Magnitude*8e1-i)+4e1/r.Y) *
			-- // Limit to arcs
				g.clamp(g.cos(a),.0,.6) *
			-- // Give them color
				(g.cos_v3(Vector3.one * (a-i) + vec3(0,1,2))+Vector3.one);
		end

		return g.v3_rgb(fragColor);
	end
}


--[=[
	Currents
	Shader by @s23b on https://www.shadertoy.com/view/MsG3DK
	Ported to Luau — ran by my engine — `RbxShader`.

	( Original comments are preserved. )
]=]

--!native

local g = require(game.ReplicatedStorage.RbxShader.GraphicsMathLib)
local vec3 = Vector3.new
local vec2 = Vector2.new
local uvec3 = Vector3.one
local uvec2 = Vector2.one
local smoothstep = g.smoothstep

type float = number
type vec2 = Vector2
type vec3 = Vector3

local SMOOTH = true
local TAU = 6.28318530718

local function hash(uv: vec2): float
	return g.fract(math.cos(math.sin(uv:Dot(vec2(.009123898, .00231233))) * 48.512353) * 11111.5452313);
end

local function noise(uv: vec2): float
	local fuv = uv:Floor();
	local c_x = hash(fuv + vec2(0, 0))
	local c_y = hash(fuv + vec2(0, 1))
	local c_z = hash(fuv + vec2(1, 0))
	local c_w = hash(fuv + vec2(1, 1))
	local a1 = if SMOOTH then smoothstep(0, 1, g.fract(uv.Y)) else g.fract(uv.Y);
	local a2 = if SMOOTH then smoothstep(0, 1, g.fract(uv.X)) else g.fract(uv.X);
	local axis = g.mix_v2(vec2(c_x, c_z), vec2(c_y, c_w), a1);
	return g.mix(axis.X, axis.Y, a2);
end

local function fbm(uv: vec2, iTime: float): float
	local f = 0.;
	local r = 1.;
	local lim = if SMOOTH then 3 else 8
	for i = 0, lim-1, 1 do
		uv += vec2(-1, 1) * iTime / 16.
		local _r = r
		r *= 2.
		f += noise((uv)*_r) / r;
	end
	return f / (1. - 1. / r);
end

local function createBall(uv: vec2): vec3
	local f = g.smoothstep(0.5, 1.4, (uv-vec2(-.1, .1)).Magnitude) * .5;
    f += smoothstep(.0, .9, 1.3- (uv-vec2(-.3, .3)).Magnitude) * .5;
    f += smoothstep(.1, .5, .5- (uv-vec2(-.4, .4)).Magnitude);
    f += smoothstep(.1, .5, .4- (uv-vec2(.2, .6)).Magnitude);
    f *= 1. - smoothstep(.95, 1., (uv-vec2(.0, .0)).Magnitude);
	return uvec3*f;
end

-- // Pixel shader
return {
	CONFIGURATION = {
		InterlaceFactor = 2 ,
		ScreenDivision = 4
	};
	-- // Image buffer
	mainImage = function (
		fragColor : Vector3 ,
		fragCoords : Vector2 ,
		iTime : number ,
		iTimeDelta : number ,
		iResolution : Vector2 ,
		iMouse : Vector2
	)
		local uv = fragCoords / iResolution * 2. - uvec2;
		uv = vec2(uv.X * iResolution.X / iResolution.Y, uv.Y);

		local ball = vec2(.2, -.4 + (math.sin(iTime * 4.) / 40.));
		local r = .2;

		-- // create distorted version of the space
		local distuv = uv * vec2(150, 130) + vec2(0, 20);
		distuv *= (uv-vec2(1.5, -2)).Magnitude / 3.;

		-- vvv @temp; not from source code
		local dxa = distuv.X + smoothstep(1. - r * 1.5, 1., 1. - (uv-(ball - vec2(.1, 0))).Magnitude) * 15.;
		-- // add distortion for the ball
		distuv = vec2(dxa, distuv.Y)

		-- // calculate distortion level from distance to lower right corner
		local t = smoothstep(0., 1., 1. - ((uv * .5)-vec2(.4, -.85)).Magnitude);

		-- // add noise to distortion weighted by distortion level
		distuv += uvec2*(fbm(uv * 2., iTime) - .5) * t * 100.;

		-- // calculate stripes
		local f = math.sin(distuv.X + distuv.Y);

		-- // calculate distance from distorted diagonal
		local d = (distuv.X + distuv.Y) / TAU;

		if math.abs(uv.X) > 1. or math.abs(uv.Y) > 1. then -- // outside boundaries
			fragColor = Vector3.zero;
		elseif d < .5 and d > - 1. then -- // inside red line
			local grad = math.min(1., (.75 - math.abs(d + .25)) * 5.);
			fragColor = g.mix_v3(vec3(.92,.16,.20), vec3(.93, .64, .17), -uv.Y) * grad;
		else -- // lines
			local spot = math.clamp(3. - ((uv * vec2(1, 2))-vec2(-1, -1)).Magnitude, 0., 1.);
			fragColor = vec3(.8, .68, .82) * f * spot;
		end

		-- // create ball color
		local b = createBall((uv - ball) / r);

		-- // create ball mask
		local mask = 1. - smoothstep(r - .002, r + .01, (uv-ball).Magnitude);
		mask *= smoothstep(-1.2, -.9, d);

		-- // add ball
		fragColor = g.mix_v3(fragColor, b, mask);

		-- // add a noise
		fragColor -= uvec3*noise(uv * 300. + uvec2*(g.fract(iTime) * 10000.)) / 5.;

		return g.v3_rgb(fragColor);
	end
}


--[=[
	GravitySucks
	Shader by @mrange on https://www.shadertoy.com/view/4cyXWw
	Ported to Luau — ran by my engine — `RbxShader`.

	( Original comments are preserved. )
]=]

--!native

local g = require(game.ReplicatedStorage.RbxShader.GraphicsMathLib)

-- // CC0: Gravity sucks
-- //	Tinkering away....

local LAYERS		= 5.
local SCALE			= 1.

local PI			= 3.141592654
local TAU			= (2.0*PI)

-- // License: Unknown, author: Unknown, found: don't remember
local function hash( co : number ) : number
	return g.fract(g.sin(co*12.9898) * 13758.5453);
end

-- // License: MIT OR CC-BY-NC-4.0, author: mercury, found: https://mercury.sexy/hg_sdf/
-- /* `p` is `inout`, meaning that change to it is global(?)
local function mod1( p : number, size : number ) : (number, number)
	local halfsize = size/2
	local c = g.floor((p + halfsize)/size)
	p = ((p + halfsize) % size) - halfsize
	return c, p
end

-- // License: Unknown, author: Unknown, found: don't remember
local function bounce( t : number, dy : number , dropOff : number ) : number
	local gr = 5
	local p0 = 2*dy/gr

	t += p0/2

	local ldo = g.log(dropOff);

	local yy = 1. - (1. - dropOff) * t / p0;

	if yy > 1e-4 then
		local n	= g.floor(g.log(yy) / ldo);
		local dn = g.pow(dropOff, n);

		local yyy = dy * dn;
		t -= p0 * (1 - dn) / (1 - dropOff);

		return - 0.5*gr*t*t + yyy*t;

	else
		return 0
	end
end

local function ball(
	iResolution : Vector2 , col : Vector3 ,
	pp : Vector2 , p : Vector2 ,
	r : number , pal : number
) : Vector3
	local ro = Vector3.new(0,0,10)
	local difDir = Vector3.new(1, 1.5, 2).Unit
	local speDir = Vector3.new(1, 2, 1).Unit
	local p3 = Vector3.new(pp, 0)
	local rd = (p3-ro).Unit

	local bcol = Vector3.one*0.5+0.5*g.sin_v3(0.5*Vector3.new(0, 1, 2)+Vector3.one*TAU*pal)
	local aa = g.sqrt(8)/iResolution.Y
	local z2 = r*r-p:Dot(p)

	if z2 > 0 then
		local z = g.sqrt(z2)
		local cp = Vector3.new(p.X, p.Y, z)
		local cn = cp.Unit
		local cr = g.reflect(rd, cn)
		local cd = g.max(difDir:Dot(cn), 0.0)
		local cs = 1.008-cr:Dot(speDir)

		local ccol = g.mix(0.1, 1,cd*cd)*bcol+g.sqrt_v3(bcol)*(1e-2/cs)
		local d = p.Magnitude-r
		col = g.mix_v3(col, ccol, g.smoothstep(0, -aa, d))
	end

	return col
end

local function effect( iResolution : Vector2 , iTime : number , p : Vector2 ) : Vector3
	p += Vector2.new(0,.5);
	local sy = g.sign(p.Y);
	p = Vector2.new(p.X, g.abs(p.Y));
	if sy < 0 then
		p *= Vector2.new(1,1.5);
	end

	local col = Vector3.zero;
	local aa = g.sqrt(4)/iResolution.Y;
	for i = 0, LAYERS do
		local h0 = hash(i+123.4);
		local h1 = g.fract(8667.0*h0);
		local h2 = g.fract(8707.0*h0);
		local h3 = g.fract(8887.0*h0);
		local tf = g.mix(.5, 1.5, h3);
		local it = tf*iTime;
		local cw = g.mix(0.25, 0.75, h0*h0)*SCALE;
		local per = g.mix(0.75, 1.5, h1*h1)*cw;
		local p0 = p;
		local nt = g.floor(it/per);
		p0 -= Vector2.new(cw*(it-nt*per)/per, 0);
		local n0, p0x = mod1(p0.X, cw)
		n0 -= nt;
		p0 = Vector2.new(p0x, p0.Y)
		if n0 > -7-i*3 then continue end
		local ct = it+n0*per;

		local ch0 = hash(h0+n0);
		local ch1 = g.fract(8667.0*ch0);
		local ch2 = g.fract(8707.0*ch0);
		local ch3 = g.fract(8887.0*ch0);
		local ch4 = g.fract(9011.0*ch0);

		local radii = cw*g.mix(.25, .5, ch0*ch0);
		local dy = g.mix(3., 2., ch3);
		local bf = g.mix(.6, .9, ch2);
		local b = bounce(ct/tf+ch4, dy, bf);
		p0 -= Vector2.new(0, b+radii);
		col = ball(iResolution, col, p, p0, radii, ch1);
	end

	if sy < 0 then
		col *= g.mix_v3(g.sqrt_v3(Vector3.new(.05, .1, .2)), Vector3.new(.05, .1, .2), p.Y);
		col += .1*Vector3.zAxis*g.max(p.Y*p.Y, 0);
	end

	col = g.sqrt_v3(col);
	return col;
end

-- // Fragment shader
return {
	CONFIGURATION = {
		InterlaceFactor = 2 ,
		ScreenDivision = 4
	};
	-- // Image buffer
	mainImage = function (
		fragColor : Vector3 ,
		fragCoords : Vector2 ,
		iTime : number ,
		iTimeDelta : number ,
		iResolution : Vector2
	)
		local p : Vector2 = (-iResolution+2*fragCoords)/iResolution.Y
		local col : Vector3 = effect(iResolution, iTime, p);

		return g.v3_rgb(col)
	end,
}


--[=[
	GregsSecret
	Shader by @GregRostami found on his literal user description
	Ported to Luau — ran by my engine — `RbxShader`.
]=]

--!native

local g = require(game.ReplicatedStorage.RbxShader.GraphicsMathLib)
local vec2 = Vector2.new
local vec3 = Vector3.new

-- // Pixel shader
return {
	CONFIGURATION = {
		InterlaceFactor = 2 ,
		ScreenDivision = 4
	};
	-- // Image buffer
	mainImage = function (
		fragColor : Vector3 ,
		fragCoords : Vector2 ,
		iTime : number ,
		iTimeDelta : number ,
		iResolution : Vector2 ,
		iMouse : Vector2,
		iDate : {number}
	)
		fragColor *= 0;
		local fa : number	= 0;			-- // frag alpha
		local b : Vector3	= Vector3.zero;

		while (b.X^b.Y^b.Z) % 99 > b.Z - 5 do
			-- // Update b and fragColor
			fa += 0.1;
			fragColor += Vector3.one * 0.1;

			b = vec3(
				iTime * vec2(1, 4) + 5 * (fragCoords / iResolution.Y - 0.7) * fa,
				fragColor.X,
				fragColor.Y
			);
		end

		return g.c3_rgba(fragColor/74, fa/74);
	end
}


--[=[
	MicroRayMarcher
	Shader by @iq on https://www.shadertoy.com/view/DlBcz1
	Ported to Luau — ran by my engine — `RbxShader`.

	( Original comments are preserved. )
]=]

--!native

local g = require(game.ReplicatedStorage.RbxShader.GraphicsMathLib)
local vec3 = Vector3.new
local vec2 = Vector2.new

-- // The MIT License
-- // Copyright © 2023 Inigo Quilez
-- // https://www.youtube.com/c/InigoQuilez
-- // https://iquilezles.org/
-- // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

-- // A raymarching shader using no more than the two input variables
-- // Original was 161 chars long. -1 by SnoopethDuckDuck. -4 chars by Xor.

local FOV = .7;
local NOISINESS = .01;
local CONECTEDNESS = 4;
local BRIGHTNESS = .1;

-- // Pixel shader
return {
	CONFIGURATION = {
		InterlaceFactor = 2 ,
		ScreenDivision = 4
	};
	-- // Image buffer
	mainImage = function (
		fragColor : Vector3 ,
		fragCoords : Vector2 ,
		iTime : number ,
		iTimeDelta : number ,
		iResolution : Vector2 ,
		iMouse : Vector2
	)
		-- Set up camera
		local f = fragCoords / iResolution.Y;
        fragColor = vec3(f.X, f.Y, 1) - Vector3.one * FOV;

        while fragCoords.X > 0 do
			fragCoords = vec2(fragCoords.X - 1, fragCoords.Y);
            -- March forward
			fragColor *= .9 + .1 * g.cos_v3(.7 * fragColor.X * Vector3.one + vec3(fragColor.Z+iTime, fragColor.X, fragColor.Y)).Magnitude + NOISINESS * g.cos(CONECTEDNESS*fragColor.Y);
        end
        -- Final color calculation
        fragColor = (fragColor + Vector3.one * fragColor.Z) * BRIGHTNESS;
		return g.v3_rgb(fragColor);
	end
}


--[=[
	NaiveRaycast
	Pixelizes the game environment by naively raycasting.

	NOTE:
	- there is something very wrong with how I calculate
	  material reflectance
]=]

--!native

local g = require(game.ReplicatedStorage.RbxShader.GraphicsMathLib)
local Camera = workspace.CurrentCamera

-- // #defines
local SKY_COLOR = Color3.fromRGB(117, 202, 255)
local ZOOM = 6
local CANVAS_SIZE = Vector2.new(180, 120) * ZOOM

local Offset = (Camera.ViewportSize - CANVAS_SIZE) / 2

-- // Make screen size dynamically change
Camera:GetPropertyChangedSignal('ViewportSize'):Connect( function()
	Offset = (Camera.ViewportSize - CANVAS_SIZE) / 2
end)

-- // Fragment shader
return {
	CONFIGURATION = {
		InterlaceFactor = 2 ,
		ScreenDivision = 4 ,
		OriginOffset = Vector2.zero ,
		CountDirection = Vector2.one
	};
	-- // Image buffer
	mainImage = function (
		fragColor : Vector3 ,
		fragCoords : Vector2 ,
		iTime : number ,
		iTimeDelta : number ,
		iResolution : Vector2
	)
		local Raycast = Camera:ScreenPointToRay(
			Offset.X + fragCoords.X * ZOOM ,
			Offset.Y + fragCoords.Y * ZOOM
		)

		local RaycastResult = workspace:Raycast( Raycast.Origin, Raycast.Direction * 500 )

		if RaycastResult then
			if RaycastResult.Instance:IsA('Terrain') then
				fragColor = workspace.Terrain:GetMaterialColor( RaycastResult.Material )
			else
				fragColor = RaycastResult.Instance.Color

				if RaycastResult.Material == Enum.Material.Glass or RaycastResult.Material == Enum.Material.Metal then
					local refColor = nil
					local Reflectance = RaycastResult.Instance.Reflectance
					local Direction = g.reflect(RaycastResult.Position, RaycastResult.Normal)
					local ReflectanceResult = workspace:Raycast(RaycastResult.Position, Direction * 500)

					if ReflectanceResult then
						if ReflectanceResult.Instance:IsA('Terrain') then
							refColor = workspace.Terrain:GetMaterialColor( ReflectanceResult.Material )
						else
							refColor = ReflectanceResult.Instance.Color
						end
					else
						refColor = SKY_COLOR
					end

					-- /* Match reflectance property. */
					fragColor = Color3.new(g.c3(fragColor)):Lerp( refColor, Reflectance )
				end
			end

			-- /* Shadowing */
			fragColor = fragColor:Lerp( Color3.fromRGB(33, 33, 33), 1 - ((RaycastResult.Normal.Y + 1) / 2) )

			-- /* Motion blur */
		else
			fragColor = SKY_COLOR
		end

		return g.c3_rgb(fragColor)
	end,
}


--[=[
	Plasma
	Shader by @Xor on https://www.shadertoy.com/view/WfS3Dd
	Ported to Luau — ran by my engine — `RbxShader`.

	( Original comments are preserved. )
]=]

--!native

local g = require(game.ReplicatedStorage.RbxShader.GraphicsMathLib)
local vec3 = Vector3.new
local vec2 = Vector2.new
local uvec3 = Vector3.one
local uvec2 = Vector2.one
local abs = math.abs

local function tanh_v3(v: Vector3): Vector3
	return vec3(math.tanh(v.X), math.tanh(v.Y), math.tanh(v.Z))
end

--[[
/*
    "Plasma" by @XorDev

    X Post:
    x.com/XorDev/status/1894123951401378051

*/
]]

-- // Pixel shader
return {
	CONFIGURATION = {
		InterlaceFactor = 2 ,
		ScreenDivision = 4
	};
	-- // Image buffer
	mainImage = function (
		fragColor : Vector3 ,
		fragCoords : Vector2 ,
		iTime : number ,
		iTimeDelta : number ,
		iResolution : Vector2 ,
		iMouse : Vector2
	)
		local O = Vector3.zero
		local I = fragCoords
		-- //Resolution for scaling
		local r = iResolution
		-- //Centered, ratio corrected, coordinates
		local p = (I+I-r) / r.Y
		-- //Z depth
		local z = vec2(0)
		-- //Iterator (x=0)
		local i = vec2(0)
		-- //Fluid coordinates
		z += uvec2*4
		local f = p*(z-uvec2*4*abs(.7-p:Dot(p)));

		-- //Clear frag color and loop 8 times
		while i.Y < 8 do
			i += Vector2.yAxis
			local s = (g.sin_v2(f)+uvec2) --@temp; not in source code
			-- //Set color waves and line brightness
			O += vec3(s.X, s.Y, s.Y) * abs(f.X-f.Y)
			-- //Add fluid waves
			f += g.cos_v2(g.yx(f)*i.Y+i+(uvec2*iTime))/i.Y+uvec2*.7;
		end

		-- //Tonemap, fade edges and color gradient
		O = tanh_v3(7.*g.exp_v3(uvec3*(z.X-4.)-p.Y*vec3(-1,1,2))/O);
		return g.v3_rgb(O);
	end
}


--[=[
	ShaderArt
	Shader by @kishimisu on https://www.shadertoy.com/view/mtyGWy
	Ported to Luau — ran by my engine — `RbxShader`.

	( Original comments are preserved. )
]=]

--!native

local g = require(game.ReplicatedStorage.RbxShader.GraphicsMathLib)

--[=[
	This animation is the material of my first youtube tutorial about creative
   coding, which is a video in which I try to introduce programmers to GLSL
   and to the wonderful world of shaders, while also trying to share my recent
   passion for this community.			Video URL: https://youtu.be/f4s1h2YETNY
]=]

-- // https://iquilezles.org/articles/palettes/
local function palette ( t : number )
	local a = Vector3.one * 0.5
	local b = Vector3.one * 0.5
	local c = Vector3.one
	local d = Vector3.new(0.263, 0.416, 0.557)

	return a + b*g.cos_v3(6.28318*(c*t+d))
end

-- // Fragment shader
return {
	CONFIGURATION = {
		InterlaceFactor = 2 ,
		DualAxisInterlacing = true ,
		ScreenDivision = 16
	};
	-- // Image buffer
	mainImage = function (
		fragColor : Vector3 ,
		fragCoords : Vector2 ,
		iTime : number ,
		iTimeDelta : number ,
		iResolution : Vector2
	)
		local uv = (fragCoords * 2 - iResolution) / iResolution.Y
		local uv0 = uv
		local finalColor = Vector3.zero

		for i = 0, 3 do
			uv = g.fract_v2(uv * 1.5) - Vector2.one * 0.5

			local d = uv.Magnitude * g.exp(-uv0.Magnitude)
			local col = palette(uv0.Magnitude + i * 0.4 + iTime * 0.4)

			d = g.sin(d * 8 + iTime) / 8
			d = g.abs(d)

			d = g.pow(0.01 / d, 1.2)

			finalColor += col * d
		end

		return g.v3_rgb(finalColor)
	end,
}


--[=[
	Plasma
	Shader by @Xor on https://www.shadertoy.com/view/ctXGRn
	Ported to Luau — ran by my engine — `RbxShader`.

	( Original comments are preserved. )
]=]

--!native

local g = require(game.ReplicatedStorage.RbxShader.GraphicsMathLib)
local vec3 = Vector3.new
local vec2 = Vector2.new
local uvec3 = Vector3.one
local uvec2 = Vector2.one
local cos = math.cos

--[[
/*
    "Shooting Stars" by @XorDev

    I got hit with inspiration for the concept of shooting stars.
    This is what I came up with.

    Tweet: twitter.com/XorDev/status/1604218610737680385
    Twigl: t.co/i7nkUWIpD8
    <300 chars playlist: shadertoy.com/playlist/fXlGDN
*/
]]

-- // Pixel shader
return {
	CONFIGURATION = {
		InterlaceFactor = 2 ,
		ScreenDivision = 4
	};
	-- // Image buffer
	mainImage = function (
		fragColor : Vector3 ,
		fragCoords : Vector2 ,
		iTime : number ,
		iTimeDelta : number ,
		iResolution : Vector2 ,
		iMouse : Vector2
	)
		local I = fragCoords
		-- //Clear fragcolor
		local O = Vector3.zero
		-- //Line dimensions (box) and position relative to line
		local b, p = vec2(0,.2), Vector2.zero;
		-- //Rotation matrix
		local R_b, R_c, R_ad = 0, 0, 0;
		-- //Iterate 20 times
		for i = 0.9, 20, 1 do
			-- //Rotate for each iteration
			R_b = cos(i+33); R_c = cos(i+11); R_ad = cos(i);
			-- //Using rotated boxes
			local v1 = (I/iResolution.Y*i*.1+uvec2*iTime*b) -- @temp;
			local v2 = g.fract_v2(vec2(
				(R_c*v1.X)+(R_ad*v1.Y),
				(R_ad*v1.X)+(R_b*v1.Y)
			)) - uvec2*.5 -- @temp;
			p = vec2(
				(R_c*v2.X)+(R_ad*v2.Y),
				(R_ad*v2.X)+(R_b*v2.Y)
			)
			-- //Add attenuation
			local glow = 1e-3/(b:Min((-b):Max(p))-p).Magnitude
			-- //My favorite color palette
			O += glow * (g.cos_v3((uvec3*p.Y/.1)+vec3(0,1,2))+uvec3)
		end

		return g.v3_rgba(O);
	end
}


--[=[
	SmallestClock
	Shader by @GregRostami on https://www.shadertoy.com/view/MsdXzH
	Ported to Luau — ran by my engine — `RbxShader`.

	( Original comments are preserved. )
]=]

--!native

local g = require(game.ReplicatedStorage.RbxShader.GraphicsMathLib)
local vec3 = Vector3.new

-- // Pixel shader
return {
	CONFIGURATION = {
		InterlaceFactor = 2 ,
		ScreenDivision = 4
	};
	-- // Image buffer
	mainImage = function (
		fragColor : Vector3 ,
		fragCoords : Vector2 ,
		iTime : number ,
		iTimeDelta : number ,
		iResolution : Vector2 ,
		iMouse : Vector2,
		iDate : {number}
	)
		-- Define N function as per the GLSL macro
		local function N( t : number ) : Vector3
			fragCoords /= 0.8
			return fragCoords.Magnitude < iResolution.Y
				and g.cos(iDate[4]/t-g.atan2(fragCoords.X, fragCoords.Y)) > 0.998
				and vec3(1, 1, 1) or vec3(0, 0, 0);
		end

		-- Initialization of R and adjustment of u (fragCoords)
		fragCoords += fragCoords - iResolution;

		-- Accumulate the result into fragColor (interpreted as o)
		fragColor = N(1e9 - 12.0) +
					N(573.0) +
					N(9.55) +
					N(6875.0);

		return g.v3_rgb(fragColor);
	end
}


--[=[
	Sun
	Shader by @invivel on https://www.shadertoy.com/view/NlfcW2
	Ported to Luau — ran by my engine — `RbxShader`.

	( Original comments are preserved. )
]=]

--!native

local g = require(game.ReplicatedStorage.RbxShader.GraphicsMathLib)

-- // Pixel shader
return {
	CONFIGURATION = {
		InterlaceFactor = 2 ,
		ScreenDivision = 4
	};
	-- // Image buffer
	mainImage = function (
		fragColor : Vector3 ,
		fragCoords : Vector2 ,
		iTime : number ,
		iTimeDelta : number ,
		iResolution : Vector2 ,
		iMouse : Vector2
	)
		iMouse = Vector2.new(iMouse.X, iMouse.Y);
		local uv : Vector2 = (fragCoords - iResolution) / iResolution.Y;
		local ms : Vector2 = (iMouse - iResolution) / iResolution.Y;

		local sun : number = 0.05 / (ms - uv).Magnitude + 0.02;

		return sun / 0.477, sun + 0.5, sun + 0.8
	end
}


--[=[
	UnknownPleasures
	Shader by @smiarx on https://www.shadertoy.com/view/4sVyWR
	Ported to Luau — ran by my engine — `RbxShader`.

	( Original comments are preserved. )
]=]

--!native

local g = require(game.ReplicatedStorage.RbxShader.GraphicsMathLib)
local vec2 = Vector2.new

-- // #defines
local LINEWIDTH = 3.2;
local H = 35;
local WAVEHEIGHT = 6.6;
local WIDTH = 0.6;
local CLOSENESS = 45;
local OCTAVES = 3;

-- // aux functions

local function random(x : number) : number
	return g.fract(g.sin(x)*43758.5453123);
end

local function noise(x : number) : number
	local i : number = g.floor(x);
	local f : number = g.fract(x);

	local a : number = random(i);
	local b : number = random(i+1);

	local u : number = f * f * (3 - 2 * f);

	return g.mix(a, b, u);
end

local function fbm(x : number) : number
	local value : number	 = 0;
	local amplitude : number = .5;
	local frequency : number = 0;

	for i = 1, OCTAVES, 1 do
		value += amplitude * noise(x);
		x *= 2;
		amplitude *= .7;
	end

	return value;
end

-- // Pixel shader
return {
	CONFIGURATION = {
		InterlaceFactor = 2 ,
		ScreenDivision = 4
	};
	-- // Image buffer
	mainImage = function (
		fragColor : Vector3 ,
		fragCoords : Vector2 ,
		iTime : number ,
		iTimeDelta : number ,
		iResolution : Vector2 ,
		iMouse : Vector2,
		iDate : {number}
	)
		local st : Vector2		 = (2*(iResolution - fragCoords) - iResolution) / iResolution.Y;
		st = vec2(st.X, st.Y * CLOSENESS);

		local linewidth : number = CLOSENESS * LINEWIDTH / iResolution.Y;
		local val : number		 = 0;

		if g.abs(st.X) < WIDTH then
			local env : number	= g.pow(g.cos(st.X/WIDTH*3.14159/2), 4.9);
			local i : number	= g.floor(st.Y);

			for n = g.max(-H, i-6), g.min(H, i), 1 do
				local f : number	= st.Y - n;
				local y : number	= f - 0.5;
				y -= WAVEHEIGHT
					* g.pow(fbm(st.X*10.504 +n*432.1 + 0.5*iTime), 3)
					* env
					+ (fbm(st.X*25.+n*1292.21)-0.32)*2 * 0.15;
				local grid : number	= g.abs(y);
				val += (1-g.smoothstep(0, linewidth, grid));
				-- // val = grid;
				if y < 0 then break end;
			end
		end

		return val, val, val, val;
	end
}


--[=[
	VoxelShader
	Shader by @Xor on https://www.shadertoy.com/view/fstSRH
	Ported to Luau — ran by my engine — `RbxShader`.

	( Original comments are preserved. )
]=]

--!native

local g = require(game.ReplicatedStorage.RbxShader.GraphicsMathLib)

local function map ( v : Vector3 , iTime : number )
	return math.sqrt(
		(v.X % 18 - 9)^2 +
			(v.Y % 18 - 9)^2 +
			(v.Z % 18 - 9)^2
	) - 9.5 + g.sin(
		v.Z * 0.3 - iTime * 0.1
	)
end

-- // Fragment shader
return {
	CONFIGURATION = {
		InterlaceFactor = 2 ,
		ScreenDivision = 16
	};
	-- // Image buffer
	mainImage = function (
		fragColor : Vector3 ,
		fragCoords : Vector2 ,
		iTime : number ,
		iTimeDelta : number ,
		iResolution : Vector2
	)
		local cam = Vector3.new(
			g.sin(iTime*0.2+iResolution.X),
			g.sin(iTime*0.2+iResolution.Y),
			iTime
		)

		local pos = cam
		local ray = Vector3.new(
			fragCoords.X*2-iResolution.X,
			fragCoords.Y*2-iResolution.Y,
			iResolution.Y
		).Unit
		local cell = Vector3.zero

		-- // Step up to 100 voxels.
		for i = 1, 100 do
			-- // Axis distance to nearest cell (with a small bias).
			local dist = g.fract_v3(-pos * ray:Sign()) + Vector3.one * 1e-4
			-- // Alternative version (produces artifacts after a while)
			-- // vec3 dist = 1-g.fract_v3(pos * sign(ray)),
			-- // Raytraced distance to each axis.
			local leng = dist / ray:Abs()
			-- // Nearest axis' raytrace distance (as a vec3).
			local near = g.min( leng.X, g.min(leng.Y, leng.Z))

			-- // Step to the nearest voxel cell.
			pos += ray * near
			-- // Get the cell position (center of the voxel).
			cell = pos:Ceil() - Vector3.one * 0.5;
			-- // Stop if we hit a voxel.
			if map(cell, iTime) < 0 then
				break
			end
		end

		-- // Rainbow color based off the voxel cell position.
		local color = g.sin_v3(Vector3.new(0,2,4) + Vector3.one * cell.Z) * 0.5 + (Vector3.one * 0.5)
		-- // Square for gamma encoding.
		color *= color;

		-- // Compute cheap ambient occlusion from the SDF.
		local ao = g.smoothstep(-1, 1, map(pos, iTime))
		-- // Fade out to black ug.sing the distance.
		local fog = g.min(1, g.exp(1 - (pos-cam).Magnitude/8))

		-- // Output final color with ao and fog (sqrt for gamma correction).
		fragColor = g.sqrt_v3(color * ao * fog)
		return g.v3_rgb(fragColor)
	end,
}


--[=[
	ZippyZaps
	Shader by @SnoopethDuckDuck on https://www.shadertoy.com/view/XXyGzh
	Ported to Luau — ran by my engine — `RbxShader`.

	( Original comments are preserved. )
]=]

--!native

local g = require(game.ReplicatedStorage.RbxShader.GraphicsMathLib)
local vec3 = Vector3.new
local vec2 = Vector2.new
local uvec3 = Vector3.one
local uvec2 = Vector2.one
local pow = math.pow
local cos = math.cos

local function tanh_v2(v: Vector2): Vector2
	return vec2(math.tanh(v.X), math.tanh(v.Y))
end

-- // Pixel shader
return {
	CONFIGURATION = {
		InterlaceFactor = 3 ,
		ScreenDivision = 4 ,
		IsBlurred = true
	};
	-- // Image buffer
	-- // -13 thanks to Nguyen2007 ⚡
	mainImage = function (
		fragColor : Vector3 ,
		fragCoords : Vector2 ,
		iTime : number ,
		iTimeDelta : number ,
		iResolution : Vector2 ,
		iMouse : Vector2,
		iDate : {number}
	)
		local v = iResolution;
		local u = fragCoords;
		u = .2*(u+u-v)/v.Y;

		local o = vec3(1, 2, 3);
		local z = o;

		local a = .5;
		local t = iTime;
		local i = 0.0;

		while true do
			i += 1
			if i >= 19 then break end
			o += (uvec3 + g.cos_v3(z+uvec3*t)) / (
				(1+i*v:Dot(v)) *
				g.sin_v2(1.5*u/(.5-u:Dot(u)) - 9*g.yx(u) + uvec2*t)
			).Magnitude

			t += 1
			a += .03
			v = g.cos_v2(uvec2*t - 7*u*pow(a, i)) - 5*u

			local r1 = i + 0.02 * t
			local r2 = cos(r1)
			u = vec2(
				(r2 * u.X) + (cos(r1 - 11) * u.Y),
				(cos(r1 - 33) * u.X) + (r2 * u.Y)
			)
			-- use stanh here if shader has black artifacts
			--  vvvv
			u += tanh_v2(40 * u:Dot(u) * g.cos_v2(1e2*g.yx(u)+uvec2*t)) / 2e2
			+ .2 * a * u
			+ uvec2*(cos(4/math.exp(o:Dot(o)/1e2) + t) / 3e2)
		end

		o = 25.6 / (o:Min(uvec3*13) + uvec3*164 / o) - uvec3*u:Dot(u) / 250;

		fragColor = o
		return g.v3_rgb(fragColor);
	end
}


--[=[
	BismuthCrystals
	Shader by @jarble on https://www.shadertoy.com/view/NdsXzl
	Ported to Luau — ran by my engine — `RbxShader`.

	( Original comments are preserved. )
]=]

--!native

local g = require(game.ReplicatedStorage.RbxShader.GraphicsMathLib)
local vec2 = Vector2.new
local vec3 = Vector3.new

-- // #defines
local ITERS = 12

-- // Pixel shader
return {
	CONFIGURATION = {
		InterlaceFactor = 2 ,
		ScreenDivision = 4
	};
	-- // Image buffer
	mainImage = function (
		fragColor : Vector3 ,
		fragCoords : Vector2 ,
		iTime : number ,
		iTimeDelta : number ,
		iResolution : Vector2 ,
		iMouse : Vector2
	)
		fragColor = Vector3.zero;
		local col : Vector3		= Vector3.zero;
		local colPrev : Vector3	= Vector3.zero;
		local t : number		= 0;
		local uv : Vector2		= (fragCoords*10 - iResolution) / iResolution.Y / 15;
		uv = vec2(uv.X, uv.Y + iTime / 25);

		for c = 1, ITERS do
			local scale : number	= 2.1;
			local scale1 : number	= 1.13;
			local s1 : number		= scale1*scale;
			colPrev = col;

			for i = 1, ITERS do
				uv = g.fract_v2((-uv) - vec2(
					uv.X/scale - uv.Y/scale1,
					uv.Y/scale - uv.X/scale1
				) / scale) / scale1;
				uv = vec2(uv.X * scale1, uv.Y);
				uv = g.fract_v2(g.yx(-uv)/s1)*s1;

				uv = vec2(uv.X, uv.Y / -scale1);
				-- // scale1 += (uv.X*(.0005*g.fract(uv.X+iTime/4)));
			end
			col = vec3(col.X, g.abs(g.fract(uv.Y)-g.fract(uv.X)), col.Z);
			--//			  g.fract((uv.Y)-(uv.X));
			col = (col+g.yzx(colPrev)) / 2.125;
		end

		return g.v3_rgb(col*3);
	end
}


--[=[
	Clouds
	Shader by @zxxuan1001 on https://www.shadertoy.com/view/tlB3zK
	Ported to Luau — ran by my engine — `RbxShader`.

	( Original comments are preserved. )
]=]

--!native

local g = require(game.ReplicatedStorage.RbxShader.GraphicsMathLib)
local vec2 = Vector2.new
local vec3 = Vector3.new

-- // noise function from iq: https://www.shadertoy.com/view/Msf3WH
function hash( p : Vector2 ) : Vector2
	p = vec2( p:Dot(vec2(127.1,311.7)), p:Dot(vec2(269.5,183.3)) );
	return Vector2.one * -1.0 + 2.0*g.fract_v2(g.sin_v2(p)*43758.5453123);
end

function noise( inv : { p : Vector2 } ) : number
	local K1 = 0.366025404;	-- // (sqrt(3)-1)/2;
    local K2 = 0.211324865;	-- // (3-sqrt(3))/6;

	local i : Vector2	= ( inv.p + Vector2.one * (inv.p.X+inv.p.Y)*K1 ):Floor();
    local a : Vector2	= inv.p - i + Vector2.one * (i.X+i.Y)*K2;
    local m : number	= g.step(a.Y,a.X);
    local o : Vector2	= vec2(m,1.0-m);
    local b : Vector2	= a - o + Vector2.one * K2;
	local c : Vector2	= a - Vector2.one + Vector2.one * 2.0*K2;
    local h : Vector3	= (Vector3.one * 0.5 - vec3(a:Dot(a), b:Dot(b), c:Dot(c))):Max(Vector3.zero);	-- might cause problems
	local n : Vector3	= h*h*h*h*vec3( a:Dot(hash(i)), b:Dot(hash(i+o)), c:Dot(hash(i+Vector2.one)));
    return n:Dot(vec3(70.0));
end

local function applyMatrix( p : Vector2 ) : Vector2
	return vec2(
		1.6 * p.X + -1.2 * p.X,
		1.2 * p.Y + 1.6 * p.Y
	);
end

function fbm4( p : Vector2 ) : number
	local amp : number	= 0.5;
	local h : number	= 0;

	local inv = {p = p};
	for i = 1, 4 do
		local n : number = noise(inv);
		h += amp * n;
		amp *= 0.5;
		inv.p = applyMatrix(inv.p);
	end

	return 0.5 + 0.5*h;
end

-- // Fragment shader
return {
	CONFIGURATION = {
		InterlaceFactor = 2 ,
		ScreenDivision = 4
	};
	-- // Image buffer
	mainImage = function (
		fragColor : Vector3 ,
		fragCoords : Vector2 ,
		iTime : number ,
		iTimeDelta : number ,
		iResolution : Vector2 ,
		iMouse : Vector2
	)
		iMouse = vec2(iMouse.X, iMouse.Y);

		-- // Normalized pixel coordinates (from 0 to 1)
		local uv : Vector2	= fragCoords/iResolution;
		uv = vec2( (uv.X - 0.5) * iResolution.X/iResolution.Y, uv.Y - 0.5 );
		local mo : Vector2	= iMouse/iResolution;
		local sky : Vector3	= vec3(0.5, 0.7, 0.8);
		local col : Vector3	= vec3(0.0);

		-- // speed
    	local v : number = 0.001;

		-- // layer1
		local cloudCol : Vector3	= Vector3.one;
		uv += mo * 10;

		local uv10 : Vector2		= uv * 10;
		local scale : Vector2		= uv * 2;
		local turbulence : Vector2	= 0.008 * vec2(noise({p=uv10}), noise({p=uv10}));
		scale += turbulence;
		local n1 : number	= fbm4(vec2(scale.X - 20 * g.sin(iTime * v * 2), scale.Y - 50 * g.sin(iTime * v)));
		col = g.mix_v3( sky, cloudCol, g.smoothstep(0.5, 0.8, n1));

		-- // layer2
		scale = uv * 0.5;
		turbulence = 0.05 * vec2(noise({p=vec2(uv.X * 2.0, uv.Y * 2.1)}), noise({p=vec2(uv.X * 1.5, uv.Y * 1.2)}));
		scale += turbulence;
		local n2 : number	= fbm4(scale + Vector2.one * 20.0 * g.sin(iTime * v));
		col =  g.mix_v3( col, cloudCol, g.smoothstep(0.2, 0.9, n2));
		col = col:Min(Vector3.one);

		-- // Output to screen
		return g.v3_rgb(col)
	end
}


--[=[
	Procedural3DOcean
	Shader by @jarble and @afl_ext on https://www.shadertoy.com/view/4cyXWw
	Ported to Luau — ran by my engine — `RbxShader`.

	( Original comments are preserved. )
	[ ⚠ NOT WORKING AS INTENDED ]
]=]

--!native

local g = require(game.ReplicatedStorage.RbxShader.GraphicsMathLib)

-- // #defines
-- // Use your mouse to move the camera around! Press the Left Mouse Button on the image to look around!

local DRAG_MULT = 0.38 -- // changes how much waves pull on the water
local WATER_DEPTH = 1.0 -- // how deep is the water
local CAMERA_HEIGHT = 1.5 -- // how high the camera should be
local ITERATIONS_RAYMARCH = 12 -- // waves iterations of raymarching
local ITERATIONS_NORMAL = 37 -- // waves iterations when calculating normals

local function xz( v : Vector3 )
	return Vector2.new(v.X, v.Z)
end

local function clamp_v3( v : Vector3 , min : number , max : number ) : Vector3
	return Vector3.new(
		g.clamp(v.X, min, max),
		g.clamp(v.Y, min, max),
		g.clamp(v.Z, min, max)
	)
end

-- // Helper function for multiplying two matrices
local function matmul( A : {{number}} , B : {{number}} ) : {{number}}
	local result = {}

	for i = 1, 3 do
		result[i] = {}
		for j = 1, 3 do
			result[i][j] = 0
			for k = 1, 3 do
				result[i][j] = result[i][j] + A[i][k] * B[k][j]
			end
		end
	end

	table.clear(A)
	table.clear(B)
	return result
end

-- // Helper function for multiplying a matrix and a vector
local function mat_v3_mul( m : {{number}} , v : Vector3 ) : Vector3
	local result = Vector3.new(
		m[1][1] * v.X + m[2][1] * v.X + m[3][1] * v.X,
		m[1][2] * v.Y + m[2][2] * v.Y + m[3][2] * v.Y,
		m[1][3] * v.Z + m[2][3] * v.Z + m[3][3] * v.Z
	)

	table.clear(m)
	return result
end

-- // Calculates wave value and its derivative,
-- // for the wave direction, position in space, wave frequency and time
local function wavedx ( position : Vector2, direction : Vector2, speed : number , frequency : number, timeshift : number) : Vector2
	local x = direction:Dot(position) * frequency + timeshift * speed;
	local wave = g.exp(g.sin(x) - 1);
	local dx = wave * g.cos(x);
	return Vector2.new(wave, -dx);
end

-- // Calculates waves by summing octaves of various waves with various parameters
local function getWaves( iTime : number , position : Vector2 , iterations : number ) : number
	local iter = 0.0; 				-- // this will help generating well distributed wave directions
	local phase = 6.0;				-- // frequency of the wave, this will change every iteration
	local speed = 2.0;				-- // time multiplier for the wave, this will change every iteration
	local weight = 1.0;				-- // weight in final sum for the wave, this will change every iteration
	local sumOfValues = 0.0;		-- // will store final sum of values
	local sumOfWeights = 0.0;		-- // will store final sum of weights
	for i = 0, iterations-1 do
		-- // generate some wave direction that looks kind of random
		local p : Vector2 = Vector2.new(g.sin(iter), g.cos(iter));
		-- // calculate wave data
		local res : Vector2 = wavedx(position, p, speed, phase, iTime);

		-- // shift position around according to wave drag and derivative of the wave
		position += p * res.Y * weight * DRAG_MULT;

		-- // add the results to sums
		sumOfValues += res.X * weight;
		sumOfWeights += weight;

		-- // modify next octave ;
		weight = g.mix(weight, 0, 0.2);
		phase *= 1.18;
		speed *= 1.07;

		-- // add some kind of random value to make next wave look random too
		iter += 12.0;
	end
	-- // calculate and return
	return sumOfValues / sumOfWeights;
end

--// Raymarches the ray from top water layer boundary to low water layer boundary
function rayMarchWater ( iTime : number , camera : Vector3, start : Vector3, ending : Vector3, depth : number ) : number
	local pos : Vector3 = start;
	local h = 0;
	local hupper = depth;
	local hlower = 0;
	local zer = Vector2.zero;
	local dir = (ending - start).Unit;
	local eps = 0.01;
	for i = 0, 317 do
		h = getWaves(iTime, xz(pos) * 0.1, ITERATIONS_RAYMARCH) * depth - depth;
		local dist_pos = (pos - camera).Magnitude;
		if h + eps*dist_pos > pos.Y then
			return dist_pos;
		end
		pos += dir * (pos.Y - h);
		-- // eps *= 1.01;
	end
	return -1;
end

-- // Calculate normal at point by calculating the height at the pos and 2 additional points very close to pos
function getNormal( iTime : number , pos : Vector2 , e : number , depth : number ) : Vector3
	local ex = Vector2.new(e, 0);
	local H = getWaves(iTime, pos, ITERATIONS_NORMAL) * depth;
	local a = Vector3.new(pos.X, H, pos.Y);
	return (a - Vector3.new(pos.X - e, getWaves(iTime, pos - ex, ITERATIONS_NORMAL) * depth, pos.Y)):Cross(
		a - Vector3.new(pos.X, getWaves(iTime, pos + g.yx(ex), ITERATIONS_NORMAL) * depth, pos.Y + e)
	).Unit;
end

-- // Helper function generating a rotation matrix around the axis by the angle
function rotmat( axis : Vector3, angle : number ) : {{number}}
	local s = g.sin(angle);
	local c = g.cos(angle);
	local oc = 1.0 - c;
	return {
		{oc * axis.X * axis.X + c, oc * axis.X * axis.Y - axis.Z * s,  oc * axis.Z * axis.X + axis.Y * s},
		{oc * axis.X * axis.Y + axis.Z * s,  oc * axis.Y * axis.Y + c,           oc * axis.Y * axis.Z - axis.X * s},
		{oc * axis.Z * axis.X - axis.Y * s,  oc * axis.Y * axis.Z + axis.X * s,  oc * axis.Z * axis.Z + c}
	}
end

-- // Helper function that generates camera ray based on UV and mouse
function getRay ( iResolution : Vector2, iMouse : Vector2, uv : Vector2 ) : Vector3
	uv = (uv * 2 - Vector2.one) * Vector2.new(iResolution.X / iResolution.Y, 1);
	-- // for fisheye, uncomment following line and comment the next one
	-- // local proj : Vector3 = (Vector3.new(uv.X, uv.Y, 1) + (Vector3.new(uv.X, uv.Y, -1) * uv.Magnitude^2 * 0.05)).Unit;
	local proj : Vector3 = Vector3.new(uv.X, uv.Y, 1.5).Unit;
	if iResolution.X < 400 then return proj end

	return mat_v3_mul(
		matmul(
			rotmat(-1 * Vector3.yAxis, 3 * (iMouse.X * 2 - 1)),
			rotmat(Vector3.xAxis, 1.5 * (iMouse.Y * 2 - 1))
		),
		proj
	);
end

-- // Ray-Plane intersection checker
function intersectPlane ( origin : Vector3 , direction : Vector3 , point : Vector3 , normal : Vector3 ) : number
	return g.clamp((point - origin):Dot(normal) / direction:Dot(normal), -1, 9991999);
end

-- // Some very barebones but fast atmosphere approximation
function extra_cheap_amosphere( raydir : Vector3 , sundir : Vector3 ) : Vector3
	local special_trick = 1.0 / (raydir.Y * 1.0 + 0.1);
	local special_trick2 = 1.0 / (sundir.Y * 11.0 + 1.0);
	local raysundt = g.abs(sundir:Dot(raydir))^2;
	local sundt = g.max(0.0, sundir:Dot(raydir))^8;
	local mymie = sundt * special_trick * 0.2;
	local suncolor = g.mix_v3(Vector3.one, Vector3.new(0.7544642686843872, 0.4196428656578064, 0), special_trick2);
	local bluesky = Vector3.new(0.2455357164144516, 0.5803571343421936, 1) * suncolor;
	local bluesky2 = Vector3.zero:Max(bluesky - Vector3.new(5.5, 13, 22.4) * 0.002 * (special_trick + -6 * sundir.Y * sundir.Y));
	bluesky2 *= special_trick * (0.24 + raysundt * 0.24);
	return bluesky2 * (1.0 + 1.0 * 1.0 - raydir.Y^3) + mymie * suncolor;
end

-- // Get atmosphere color for given direction
function getAtmosphere( ray : Vector3 ) : Vector3
	return extra_cheap_amosphere(ray, Vector3.one.Unit) * 0.5;
end

-- // Get sun color for given direction
function getSun ( ray : Vector3 ) : number
	return g.max(0, ray:Dot(Vector3.one.Unit))^528 * 110;
end

-- // Great tonemapping function from my other shader: https://www.shadertoy.com/view/XsGfWV
function aces_tonemap( color : Vector3 ) : Vector3
	local m1 = {
		{0.59719, 0.07600, 0.02840},
		{0.35458, 0.90834, 0.13383},
		{0.04823, 0.01566, 0.83777}
	};
	local m2 = {
		{1.60475, -0.10208, -0.00327},
		{-0.53108,  1.10813, -0.07276},
		{-0.07367, -0.00605,  1.07602}
	};
	local v = mat_v3_mul(m1, color);
	local c = (v * (v + 0.0245786) - 0.000090537) / (v * (0.983729 * v + 0.4329510) + 0.238081);
	return g.pow_v3(clamp_v3(mat_v3_mul(m2, c), 0, 1), Vector3.one * 1 / 2.2);
end

-- // Pixel shader
return {
	CONFIGURATION = {
		InterlaceFactor = 2 ,
		ScreenDivision = 4
	};
	-- // Image buffer
	mainImage = function (
		fragColor : Vector3 ,
		fragCoords : Vector2 ,
		iTime : number ,
		iTimeDelta : number ,
		iResolution : Vector2 ,
		iMouse : Vector2
	)
		local uv = fragCoords / iResolution;

		local waterdepth = 2.1;
		local wfloor = Vector3.yAxis * -waterdepth;
		local wceil = Vector3.zero;
		local orig = Vector3.yAxis * 2;
		local ray = getRay(iResolution, iMouse, uv);
		local hihit = intersectPlane(orig, ray, wceil, Vector3.yAxis);

		if ray.Y >- -0.01 then
			local C = getAtmosphere(ray) * 2 + getSun(ray);
			-- // tonemapping
			C = aces_tonemap(C);
			return g.v3_rgb(C);
		end

		local lohit = intersectPlane(orig, ray, wfloor, Vector3.yAxis);
		local hipos = orig + ray * hihit;
		local lopos = orig + ray * lohit;
		local dist = rayMarchWater(iTime, orig, hipos, lopos, waterdepth);
		local pos = orig + ray * dist;

		local N = getNormal(iTime, xz(pos), 0.001, waterdepth);
		N = g.mix(Vector3.yAxis, N, 1 / (dist * dist * 0.01 + 1));
		local R = g.reflect(ray, N);
		local fresnel = 0.04 + 0.96 * (1 - g.max(0, (-N):Dot(ray)))^5;

		local C = fresnel * getAtmosphere(R) * (2 + fresnel * getSun(R));
		-- // tonemapping
		return g.v3_rgb(aces_tonemap(C));
	end,
}


--[=[
	SimpleOceanWaves
	Shader by @jarble on https://www.shadertoy.com/view/ftS3zh
	Ported to Luau — ran by my engine — `RbxShader`.

	( Original comments are preserved. )
	[ ⚠ NOT WORKING AS INTENDED ]
]=]

--!native

local g = require(game.ReplicatedStorage.RbxShader.GraphicsMathLib)

-- // `in` value system
--	* designed to consume less memory by reusing old `in`s (which are just tables)
local in_bookkeep : {[number] : boolean}	-- # maps a key to it's availability
local in_map : {[number] : {}} = {}			-- # maps a key to an `in` value

-- # looks for any previous `in`s that can be reused and returns them
local function alloc_in()
	local free_in : {} = nil
	-- # look for free unallocated `in`s
	for key : number, availability : boolean in in_bookkeep do
		if not availability then continue end
		free_in = in_map[key]
		in_bookkeep[key] = false
	end
	-- # make a new entry if everything before has been occupied
	if free_in == nil then
		free_in = {}
		local key = #in_map+1
		in_map[key] = free_in
		in_bookkeep[key] = false
	end

	return free_in
end

-- # frees up the data stored in the `in`s to be garbage collected
local function freeup_ins()
	for key : number, availability : boolean in in_bookkeep do
		in_bookkeep[key] = false
		table.clear(in_map[key])
	end
end

-- // #defines
local SC = 250
local OCTAVES = 4
local ITIME = 0

-- // aux functions

local function xyy( v : Vector3 ) : Vector3
	return Vector3.new( v.X, v.Y, v.Y )
end

local function yyx( v : Vector3 ) : Vector3
	return Vector3.new( v.Y, v.Y, v.X )
end

type Matrix3 = {Vector3}

local function mul_mat3vec3( m : Matrix3 , v : Vector3 ) : Vector3
	return Vector3.new(
		m[1].X * v.X + m[2].X * v.X + m[3].X * v.X,
		m[1].Y * v.Y + m[2].Y * v.Y + m[3].Y * v.Y,
		m[1].Z * v.Z + m[2].Z * v.Z + m[3].Z * v.Z
	)
end

-- // program functions

-- # uv is an in-value
function noise( uv : Vector2 ) : number
    return g.sin(uv.X - ITIME/2);
end

-- # uv is an in-value
function fbm( uv : Vector2 ) : number
	local value = 0;
	local amplitude = 1;
	local freq = 0.8;

	for i = 0, OCTAVES-1 do
		-- // value += noise(uv * freq) * amplitude;
		-- // uv = Vector2.new(uv.X - ITIME/2, uv.Y);

		-- // From Dave_Hoskins https://www.shadertoy.com/user/Dave_Hoskins
		value += (.25 - g.abs(noise(uv * freq)-.3) * amplitude);
        amplitude *= .37;
        freq *= 3.+1. / 3.;
        uv += g.yx(uv) / 10.0;
        -- // uv = g.yx(uv);
	end

	return value;
end

function f ( p : Vector3 ) : number
	return fbm(g.xz(p));
end

function getNormal ( p : Vector3 , t : number ) : Vector3
	local eps = Vector3.xAxis * .001 * t;
	return Vector3.new(
		f(p - xyy(eps)) - f(p + xyy(eps)),
		2 * eps.X,
		f(p - yyx(eps)) - f(p + yyx(eps))
	).Unit;
end

function rayMarching ( ro : Vector3 , rd : Vector3 , tMin : number , tMax : number ) : number
	local t = tMin;
	for _ = 1, 300 do
		local pos = ro + t * rd;
		local h = pos.Y - f(pos);
		if g.abs(h) < (0.0015 * t) or t > tMax then break end
		t += 0.4 * h;
	end
	return t;
end

function lighting ( p : Vector3 , normal : Vector3 , L : Vector3 , V : Vector3 ) : Vector3
	local sunColor = Vector3.new(1, .956, .839);
	local albedo = 1;
	local diff = Vector3.one * g.max(normal:Dot(L) * albedo, 0);

	local refl = g.reflect(L, normal).Unit;
	local spec = g.max(refl:Dot(-(V.Unit)), 0);
	spec ^= 18;
	spec = g.clamp(spec, 0, 1);
	local sky = g.max(0, Vector3.yAxis:Dot(normal));

	-- // local amb = 0.5 * g.smoothstep(0.0, 2.0, p.Y);

	local col = diff * sunColor;
	col += spec * sunColor;
	col += sky * Vector3.new(0, 0.6, 1) * .1;
	-- // col += amb * .2;

	return col;
end

function lookAt( origin : Vector3 , target : Vector3 , roll : number ) : Matrix3
	local rr = Vector3.new(g.sin(roll), g.cos(roll), 0);
	local ww = (target - origin).Unit;
	local uu = ww:Cross(rr);
	local vv = uu:Cross(ww);

	return {uu, vv, ww};
end

function camerapath ( t : number ) : Vector3
	return Vector3.new(
		-13.0 + 3.5 * g.cos(t),
		3.3,
		-1.1 + 2.4 * g.cos(2.4 * t + 2.0)
	)
end

-- // Pixel shader
return {
	CONFIGURATION = {
		InterlaceFactor = 2 ,
		ScreenDivision = 4
	};
	-- // Image buffer
	mainImage = function (
		fragColor : Vector3 ,
		fragCoords : Vector2 ,
		iTime : number ,
		iTimeDelta : number ,
		iResolution : Vector2 ,
		iMouse : Vector2
	)
		freeup_ins();
		ITIME = iTime;

		local uv = (fragCoords - iResolution * 0.5) / iResolution.Y;
		local lightDir = Vector3.new(-.8, .15, -.3).Unit;

		local camStep = Vector3.new(lightDir.X, 0, lightDir.Z);
		local camPos = Vector3.new(8, 2, 5) + camStep;
		local camTarget = Vector3.new(1, 1, 4) + camStep;
		local mat = lookAt(camPos, camTarget, 0);

		local ro = camPos;
		local rd = mul_mat3vec3(mat, Vector3.new(uv.X, uv.Y, 1)).Unit;

		local tMin = .1;
		local tMax = 20;
		local t = rayMarching(ro, rd, tMin, tMax);

		local col = Vector3.zero;

		if t > tMax then
			-- // from iq's shader, https://www.shadertoy.com/view/MdX3Rr
			local sundot = g.clamp(rd:Dot(lightDir), 0.0, 1.0);
			col = Vector3.new(.3, .5, .85) - Vector3.one * rd.Y*rd.Y*0.5;
			col = g.mix_v3(col, 0.85 * Vector3.new(.7, .75, .85), (1 - g.max(rd.Y, 0))^4);
			-- // sun
			col += 0.25 * Vector3.new(1, .7, .4) * sundot^5;
			col += 0.25 * Vector3.new(1, .8, .6) * sundot^64;
			col += 0.2 * Vector3.new(1, .8, .6) * sundot^512;
			-- // clouds
			local sc = g.xz(ro) + g.xz(rd) * (SC*1000-ro.Y) / rd.Y;
			col = g.mix_v3(col, Vector3.new(1, .95, 1), 0.5 * g.smoothstep(0.5, 0.8, fbm(0.0005*sc/SC)));
			-- // horizon
			col = g.mix_v3(col, 0.68 * Vector3.new(.4, .65, 1), (1 - g.max(rd.Y, 0))^16);
		else
			local p = ro + rd * t;
			local normal = getNormal(p, t);
			local viewDir = (ro - p).Unit;

			-- // lighting terrain
			col = lighting(p, normal, lightDir, viewDir);

			-- // fog
			local fo = 1 - g.exp((30 * t / SC)^1.5);
			local fco = 0.65 * Vector3.new(.4, .65, 1);
			col = g.mix_v3(col, fco, fo);
		end

		-- // Gamma correction
		col = g.pow_v3(g.clamp_v3(col, 0, 1), Vector3.one * .45);

		return g.v3_rgb(col)
	end
}


--[=[
	StarNest
	Shader by @Kali on https://www.shadertoy.com/view/XlfGRj
	Ported to Luau — ran by my engine — `RbxShader`.

	NOTE:
	- very resource intensive, our goal is to be able
	  to render this efficiently without changing this
	  programs code
]=]

--!native

local g = require(game.ReplicatedStorage.RbxShader.GraphicsMathLib)

-- // #define
local iterations = 17
local formuparam = 0.53

local volsteps = 20
local stepsize = 0.1

local zoom = 0.800
local tile = 0.850
local speed = 0.010

local brightness = 0.0015
local darkmatter = 0.300
local distfading = 0.730
local saturation = 0.850

local Matrix2 = {}

function Matrix2.new(
	a : number, b : number ,
	c : number , d : number
)
	return {{a, b}, {c, d}}
end

-- // Fragment shader
return {
	CONFIGURATION = {
		InterlaceFactor = 2 ,
		ScreenDivision = 4
	};
	-- // Image buffer
	mainImage = function (
		fragColor : Vector3 ,
		fragCoords : Vector2 ,
		iTime : number ,
		iTimeDelta : number ,
		iResolution : Vector2 ,
		iMouse : Vector2
	)
		-- // get coords and direction
		local uv = Vector2.new(
			fragCoords.X/iResolution.X - .5,
			fragCoords.Y/iResolution.Y - .5 * iResolution.Y/iResolution.X
		) * zoom
		local dir = Vector3.new(uv.X, uv.Y, 1)
		local time = iTime * speed + .25

		-- // mouse rotation
		local a1 = 0.5 + iMouse.X / iResolution.X * 2
		local a2 = 0.8 + iMouse.Y / iResolution.Y * 2

		local rot1 = Matrix2.new(g.cos(a1), g.sin(a1), -g.sin(a1), g.cos(a1))
		local rot2 = Matrix2.new(g.cos(a2), g.sin(a2), -g.sin(a2), g.cos(a2))

		dir = Vector3.new(dir.X * rot1[1][1] + dir.Z * rot1[1][2], dir.Y, dir.X * rot1[2][1] + dir.Z * rot1[2][2])
		dir = Vector3.new(dir.X, dir.Y * rot2[1][1] + dir.Z * rot2[1][2], dir.X * rot2[2][1] + dir.Y * rot2[2][2])

		local from = Vector3.new(1, 0.5, 0.5)
		from += Vector3.new(time * 2, time, -2)

		from = Vector3.new(from.X * rot1[1][1] + from.Z * rot1[1][2], from.Y, from.X * rot1[2][1] + from.Z * rot1[2][2])
		from = Vector3.new(from.X, from.Y * rot2[1][1] + from.Z * rot2[1][2], from.X * rot2[2][1] + from.Y * rot2[2][2])

		-- // volumetric rendering
		local s, fade = 0.1, 1.0
		local v = Vector3.zero

		for r = 1, volsteps do
			local p = from+s*dir*.5
			p = Vector3.one * tile - g.mod_v3(p, Vector3.one * tile * 2)	-- // tiling fold
			local pa, a = 0, 0
			for i = 1, iterations do
				p = p:Abs() / p:Dot(p) - Vector3.one * formuparam			-- // the magic formula
				a += g.abs((p.Magnitude-pa))								-- // absolute sum of average change
				pa = p.Magnitude
			end
			local dm = g.max(0, darkmatter-a*a*0.001)	-- // dark matter
			a *= a*a									-- // add contrast
			if r > 6 then fade *= 1 - dm end			-- // dark matter, don't render near
			-- // v += Vector3.new(dm,dm*.5,0.)
			v += Vector3.one * fade
			v += Vector3.new(s, s^2, s^4)*a*brightness*fade	-- // coloring based on distance
			fade *= distfading								-- // distance fading
			s += stepsize
		end

		v = g.mix_v3(Vector3.one * v.Magnitude, v, saturation)	-- // color adjust
		return g.v3_rgb(v*.01)
	end,
}


--[=[
	Canvas
	A simple EditableImage interface that mimics
	CanvasDraw's API design. Specifically built for RbxShader.

	NOTE:
	* RGBA values are normalized (between 0 and 1)
]=]

--!native

local Asset = game:GetService('AssetService')

local Resampler = Enum.ResamplerMode
local writeu8 = buffer.writeu8
local readu8 = buffer.readu8
local writeu16 = buffer.writeu16
local readu16 = buffer.readu16
-- * denormalizes a normalized number
local uint8 = function(n: number) : number
	return math.clamp(math.ceil(n * 255), 0, 255)
end

local Canvas = {}
Canvas.__index = Canvas

type Parent = GuiObject | Decal | Texture | SurfaceAppearance | MeshPart
export type Canvas = typeof(Canvas) & {
	Resolution: Vector2 ,
	__Easel: ImageLabel ,
	__Canvas: EditableImage ,
	__ImageBuffer: buffer ,
	__VPMBuffer: buffer
}

function Canvas.new(Parent: Parent, Resolution: Vector2, Blur: boolean?): (Canvas)
	Blur = Blur or false

	-- Build the necessary objects
	local Easel = Instance.new("ImageLabel")
	Easel.Name = "FastCanvas"
	Easel.BackgroundTransparency = 1
	Easel.ClipsDescendants = true
	Easel.Size = UDim2.fromScale(1, 1)
	Easel.Position = UDim2.fromScale(0.5, 0.5)
	Easel.AnchorPoint = Vector2.new(0.5, 0.5)
	Easel.ResampleMode = not Blur and Resampler.Pixelated or Resampler.Default
	Easel.Parent = Parent

	local AspectRatio = Instance.new("UIAspectRatioConstraint")
	AspectRatio.AspectRatio = Resolution.X / Resolution.Y
	AspectRatio.Parent = Easel

	local InternalCanvas = Asset:CreateEditableImage({ Size = Resolution })
	Easel.ImageContent = Content.fromObject(InternalCanvas)

	-- Build the 'Canvas' object
	local self = setmetatable({}, Canvas)
	self.Resolution = Resolution
	self.__Easel = Easel
	self.__Canvas = InternalCanvas
	self.__ImageBuffer = buffer.create(Resolution.X * Resolution.Y * 4)
	self.__VPMBuffer = buffer.create(Resolution.X * Resolution.Y * 4)
	-- ^^^ 'VPM' stands for 'virtual pixel mapping'
	-- * pure coincidence that the size of both buffers are the same

	return self
end

function Canvas:__GetIndex(X: number, Y: number): (number)
	return (Y - 1) * (self.Resolution.X * 4) + (X - 1) * 4
end

function Canvas:SetRGBA(X: number, Y: number, R: number, G: number, B: number, A: number): ()
	local Index = self:__GetIndex(X, Y)
	local ImgBuff = self.__ImageBuffer
	writeu8(ImgBuff, Index, uint8(R))
	writeu8(ImgBuff, Index + 1, uint8(G))
	writeu8(ImgBuff, Index + 2, uint8(B))
	writeu8(ImgBuff, Index + 3, uint8(A or 1))
	return nil
end

function Canvas:GetRGB(X: number, Y: number): (number, number, number)
	local Index = self:__GetIndex(X, Y)
	local ImgBuff = self.__ImageBuffer
	return readu8(ImgBuff, Index) / 255,
		readu8(ImgBuff, Index + 1) / 255,
		readu8(ImgBuff, Index + 2) / 255
end

function Canvas:Fill(Color: Color3?, Alpha: number?): ()
	Color = Color or Color3.new(1, 1, 1)
	Alpha = Alpha or 1

	for X = 1, self.Resolution.X do
		for Y = 1, self.Resolution.Y do
			self:SetRGBA(X, Y, Color.R, Color.G, Color.B, Alpha)
		end
	end
end

function Canvas:RecalculateVirtualPixelMapping(
	VirtualOrigin: Vector2 , --> in pixels
	RelativeOffset: Vector2 , --> in pixels
	CountDirection: Vector2 --> sign vector
): ()
	VirtualOrigin = VirtualOrigin:Floor()
	RelativeOffset = RelativeOffset:Floor()
	CountDirection = CountDirection:Sign()

	-- CountDirection sanitization
	if CountDirection.X == 0 then CountDirection += Vector2.xAxis end
	if CountDirection.Y == 0 then CountDirection += Vector2.yAxis end
	local VPMBuff = self.__VPMBuffer

	for X = 1, self.Resolution.X do
		for Y = 1, self.Resolution.Y do
			local Index = self:__GetIndex(X, Y)
			writeu16(VPMBuff, Index, VirtualOrigin.X + CountDirection.X * (X + RelativeOffset.X))
			writeu16(VPMBuff, Index + 2, VirtualOrigin.Y + CountDirection.Y * (Y + RelativeOffset.Y))
		end
	end
end

function Canvas:GetVirtualPosition(X: number, Y: number): (number, number)
	local Index = self:__GetIndex(X, Y)
	local VPMBuff = self.__VPMBuffer
	return readu16(VPMBuff, Index),
		readu16(VPMBuff, Index + 2)
end

function Canvas:Render(): ()
	local Canvas : EditableImage = self.__Canvas
	Canvas:WritePixelsBuffer(Vector2.zero, Canvas.Size, self.__ImageBuffer)
	return nil
end

function Canvas:Destroy(): ()
	self.__Canvas:Destroy()
	self.__Easel:Destroy()
	setmetatable(self, nil)
	table.clear(self)
	self = nil
	return nil
end

return Canvas


--[=[
	GraphicsMathLib
	Reimplementation of commonly used
	GLSL functions in Luau.

	NOTE:
	- Reimplementation is incomplete
	- Do not implement function swizzling
	  just like what GLSL did, as Luau
	  is not powerful enough to do that
]=]

--!native

local MathLib = {}

function MathLib.fract_v3 ( v : Vector3 )
	return Vector3.new(
		v.X - math.floor(v.X) ,
		v.Y - math.floor(v.Y) ,
		v.Z - math.floor(v.Z)
	)
end

function MathLib.fract_v2 ( v : Vector2 )
	return Vector2.new(
		v.X - math.floor(v.X) ,
		v.Y - math.floor(v.Y)
	)
end

function MathLib.fract ( x : number )
	return x - math.floor(x)
end

function MathLib.smoothstep ( edge0 : number , edge1 : number , x : number )
	local t = math.clamp((x - edge0) / (edge1 - edge0), 0, 1);
	return t * t * (3 - 2 * t);
end

function MathLib.clamp_v3 ( v : Vector3 , min : number , max : number )
	return Vector3.new(
		math.clamp(v.X, min, max),
		math.clamp(v.Y, min, max),
		math.clamp(v.Z, min, max)
	)
end

function MathLib.clamp_v2 ( v : Vector2 , min : number , max : number )
	return Vector2.new(
		math.clamp(v.X, min, max),
		math.clamp(v.Y, min, max)
	)
end

function MathLib.sqrt_v3 ( v : Vector3 )
	return Vector3.new(
		math.sqrt(v.X) ,
		math.sqrt(v.Y) ,
		math.sqrt(v.Z)
	)
end

function MathLib.sqrt_v2 ( v : Vector2 )
	return Vector2.new(
		math.sqrt(v.X) ,
		math.sqrt(v.Y)
	)
end

function MathLib.sin_v3 ( v : Vector3 )
	return Vector3.new(
		math.sin(v.X) ,
		math.sin(v.Y) ,
		math.sin(v.Z)
	)
end

function MathLib.sin_v2 ( v : Vector2 )
	return Vector2.new(
		math.sin(v.X) ,
		math.sin(v.Y)
	)
end

function MathLib.cos_v3 ( v : Vector3 )
	return Vector3.new(
		math.cos(v.X) ,
		math.cos(v.Y) ,
		math.cos(v.Z)
	)
end

function MathLib.cos_v2 ( v : Vector2 )
	return Vector2.new(
		math.cos(v.X) ,
		math.cos(v.Y)
	)
end

function MathLib.yzx ( v : Vector3 )
	return Vector3.new(v.Y, v.Z, v.X)
end

function MathLib.zxy ( v : Vector3 )
	return Vector3.new(v.Z, v.X, v.Y)
end

function MathLib.xz ( v : Vector3 )
	return Vector2.new(v.X, v.Z)
end

function MathLib.yx ( v : Vector2 | Vector3 )
	return Vector2.new(v.Y, v.X)
end

function MathLib.step ( edge : number , x : number )
	return x < edge and 0 or 1
end

function MathLib.step_v3 ( edge : Vector3 , x : Vector3 )
	return Vector3.new(
		MathLib.step( edge.X , x.X ) ,
		MathLib.step( edge.Y , x.Y ) ,
		MathLib.step( edge.Z , x.Z )
	)
end

function MathLib.step_v2 ( edge : Vector2 , x : Vector2 )
	return Vector2.new(
		MathLib.step( edge.X , x.X ) ,
		MathLib.step( edge.Y , x.Y )
	)
end

-- // Basically lerp
function MathLib.mix_v3 ( x : Vector3 , y : Vector3 , a : number )
	return x:Lerp(y,a)
end

function MathLib.mix_v2 ( x : Vector2 , y : Vector2 , a : number )
	return x:Lerp(y,a)
end
function MathLib.mix ( x : number , y : number , a : number )
	return x + (y - x) * a
end

function MathLib.mod_v3 ( x : Vector3 , y : Vector3 )
	return Vector3.new(
		x.X % y.X ,
		x.Y % y.Y ,
		x.Z % y.Z
	)
end

function MathLib.mod_v2 ( x : Vector2 , y : Vector2 )
	return Vector2.new(
		x.X % y.X ,
		x.Y % y.Y
	)
end

function MathLib.reflect ( i : Vector3 , n : Vector3 )
	return i - 2 * n:Dot(i) * n
end

function MathLib.exp_v3 ( v : Vector3 )
	return Vector3.new(
		math.exp(v.X) ,
		math.exp(v.Y) ,
		math.exp(v.Z)
	)
end

function MathLib.exp_v2 ( v : Vector2 )
	return Vector2.new(
		math.exp(v.X) ,
		math.exp(v.Y)
	)
end

function MathLib.pow_v3 ( v : Vector3 , vp : Vector3 )
	return Vector3.new(
		math.pow(v.X, vp.X) ,
		math.pow(v.Y, vp.Y) ,
		math.pow(v.Z, vp.Z)
	)
end

function MathLib.pow_v2 ( v : Vector2 , vp : Vector3 )
	return Vector2.new(
		math.pow(v.X, vp.X) ,
		math.pow(v.Y, vp.Y)
	)
end

--[=[=======================
	/* NON-GLSL FUNCTIONS */
--========================]=]
function MathLib.v3_rgb ( v : Vector3 )
	return v.X, v.Y, v.Z
end

function MathLib.c3_rgb ( c : Color3 )
	return c.R, c.G, c.B
end

function MathLib.v3_rgba ( v : Vector3 , alpha : number? )
	return v.X, v.Y, v.Z, alpha
end

function MathLib.c3_rgba ( c : Color3 , alpha : number? )
	return c.R, c.G, c.B, alpha
end

function MathLib.c3 ( a : Color3 | Vector3 )
	return typeof(a) == 'Color3' and a or Color3.new( a.X, a.Y, a.Z )
end

for operation : string, func : () -> any in math do
	MathLib[operation] = func
end

MathLib.Vector4 = require(script.Parent.Vector4)

return MathLib


--[=[
	Vector4
	Partial implementation of the
	`vec4` data type in GLSL.
]=]

--!native

local cos = math.cos
local sin = math.sin

local Vector4 = {}

function Vector4.new(
	x : number , y : number ,
	z : number , w : number
)
	local vec4 = { X = x , Y = y , Z = z , W = w }

	function vec4:Cos()
		return Vector4.new(
			cos(self.X) ,
			cos(self.Y) ,
			cos(self.Z) ,
			cos(self.W)
		)
	end

	function vec4:Sin()
		return Vector4.new(
			sin(self.X) ,
			sin(self.Y) ,
			sin(self.Z) ,
			sin(self.W)
		)
	end

	function vec4:Lerp( b : Vector4 , t : number )
		return Vector4.new(
			self.X + ( b.X - self.X ) * t ,
			self.Y + ( b.Y - self.Y ) * t ,
			self.Z + ( b.Z - self.Z ) * t ,
			self.W + ( b.W - self.W ) * t
		)
	end

	local function IsAVector4( p : {} )
		return type(p) == 'table' and p.X and p.Y and p.Z and p.W
	end

	setmetatable(vec4, {
		__add = function( a , b )
			return Vector4.new(
				a.X + b.X ,
				a.Y + b.Y ,
				a.Z + b.Z ,
				a.W + b.W
			)
		end,

		__sub = function( a , b )
			return Vector4.new(
				a.X - b.X ,
				a.Y - b.Y ,
				a.Z - b.Z ,
				a.W - b.W
			)
		end,

		__mul = function ( a , b )
			if not IsAVector4(a) then
				a, b = b, a
			end

			if IsAVector4(b) then
				return Vector4.new(
					a.X * b.X ,
					a.Y * b.Y ,
					a.Z * b.Z ,
					a.W * b.W
				)
			else
				return Vector4.new(
					a.X * b ,
					a.Y * b ,
					a.Z * b ,
					a.W * b
				)
			end
		end,

		__div = function ( a , b )
			if not IsAVector4(a) then
				a, b = b, a
			end

			if IsAVector4(b) then
				return Vector4.new(
					a.X / b.X ,
					a.Y / b.Y ,
					a.Z / b.Z ,
					a.W / b.W
				)
			else
				return Vector4.new(
					a.X / b ,
					a.Y / b ,
					a.Z / b ,
					a.W / b
				)
			end
		end,
	})

	return vec4
end

setmetatable(Vector4, {
	__index = function( self , index )
		if index == 'one' then
			return Vector4.new(1, 1, 1, 1)
		elseif index == 'zero' then
			return Vector4.new(0, 0, 0, 0)
		end
	end,
})

export type Vector4 = typeof(Vector4)

return Vector4


--[=[ Basically an assertion with format. ]=]

--!native
--!strict

local argue = setmetatable({}, {
	__call = function (
		t : {} ,
		cond : any ,
		msg : string
	)
		msg = msg or 'Assertion failed!'
		local location = t.at or debug.info(2, "n")
		if not cond then return error( `@RbxShader/{location}: {msg}` ) end
	end
})

function argue:at( at : string )
	if typeof(at) ~= "string" then return argue end
	return setmetatable({ at = at }, getmetatable(argue))
end

return argue


-- [=[ Common types found across the scripts. ]=]
--!native

export type ShaderBuffer = (
	fragColor : Vector3 ,
	fragCoords : Vector2 ,
	iTime : number ,
	iTimeDelta : number ,
	iResolution : Vector2 ,
	iMouse : SharedTable ,
	iDate : {number}
) -> (number, number, number, number)

-- a few buffer name examples
export type BufferName = 'bufferA' | 'bufferB' | 'bufferC' | 'bufferD' | 'bufferE'

export type Shader = {
	CONFIGURATION : EngineConfiguration ,
	[BufferName] : ShaderBuffer ,
	mainImage : ShaderBuffer
}

export type EngineConfiguration = {
	InterlaceFactor : number ,
	DualAxisInterlacing : boolean ,
	ScreenDivision : number ,
	InitialColor : Color3? ,
	InitialAlpha : number? ,
	IsBlurred : boolean? ,
	OriginOffset : Vector2 ,
	CountDirection : Vector2
}

return true


local fs = require("@lune/fs")
local roblox = require("@lune/roblox")
local process = require("@lune/process")
local run = require("@utils/run")

run("rojo", { "build", "test.project.json", "--output", "test-place.rbxl" })

local function trim(s: string)
	return s:match("^%s*(.-)%s*$")
end

local gameRoot = roblox.deserializePlace(fs.readFile("test-place.rbxl"))
local packages = assert(gameRoot:GetService("ReplicatedStorage"):FindFirstChild("Packages"))

local license = roblox.Instance.new("ModuleScript")
license.Name = "LICENSE"
license.Source = `return [[\n{trim(fs.readFile("LICENSE"))}\n]]`
license.Parent = assert(packages:FindFirstChild("Bufferize"))

local standalone = roblox.Instance.new("ModuleScript")
standalone.Name = "Bufferize"
standalone.Source = trim([[
local Bufferize = require(script.Packages.Bufferize)

export type Encoder = Bufferize.Encoder
export type BufferStream = Bufferize.BufferStream

export type Supported = Bufferize.Supported
export type Overridable = Bufferize.Overridable

return Bufferize
]])

packages.Parent = standalone

local outputPath = process.args[1]
fs.writeFile(outputPath or "standalone.rbxm", roblox.serializeModel({ standalone }))


local fs = require("@lune/fs")
local process = require("@lune/process")
local run = require("@utils/run")

local commandName = process.args[1]
local writePath = process.args[2]

local result
pcall(function()
	run("rojo", { "build", "test.project.json", "--output", "test-place.rbxl" })
	result = run(
		"run-in-roblox",
		{ "--place", "test-place.rbxl", "--script", `lune/generate/{commandName}.luau` },
		{ stdio = "default" }
	)
end)

fs.writeFile(writePath, result)


local pkgRoot = game.ReplicatedStorage.Packages.Bufferize
local dataTypes = require(pkgRoot.DataTypes)

-- https://tableconvert.com/csv-to-markdown

local SUPPORT_MAP = {
	["implemented"] = "✔",
	["unimplemented"] = "❌",
	["never"] = "⛔",
}

local lines = { "DataType, Supported, Overridable" }
local definitionsByTypeId = dataTypes.Encoder.definitionsByTypeId

local maxTypeId = -1
for _, definition in definitionsByTypeId do
	maxTypeId = math.max(definition.typeId, maxTypeId)
end

for i = 0, maxTypeId do
	local definition = definitionsByTypeId[i]
	if definition.typeOf:match("userdata:") then
		continue
	end

	table.insert(
		lines,
		table.concat({
			definition.typeOf,
			SUPPORT_MAP[definition.support],
			if definition.overridable
				then SUPPORT_MAP.implemented
				elseif definition.support == "never" then SUPPORT_MAP.never
				else SUPPORT_MAP.unimplemented,
		}, ",")
	)
end

print(table.concat(lines, "\n"))

return {}


local pkgRoot = game.ReplicatedStorage.Packages.Bufferize
local dataTypes = require(pkgRoot.DataTypes)

local definitionsByTypeId = dataTypes.Encoder.definitionsByTypeId

local maxTypeId = -1
for _, definition in definitionsByTypeId do
	maxTypeId = math.max(definition.typeId, maxTypeId)
end

local supportedTypings = {}
local overridableTypings = {}
for i = 0, maxTypeId do
	local definition = definitionsByTypeId[i]
	if definition.typeOf:match("userdata:") then
		continue
	end

	if definition.support == "implemented" then
		table.insert(supportedTypings, definition.typing)
		if definition.overridable then
			table.insert(overridableTypings, definition.typing)
		end
	end
end

print("export type Supported = " .. table.concat(supportedTypings, "\n\t| "))
print("")
print("export type Overridable = " .. table.concat(overridableTypings, "\n\t| "))
print("")
print("return {}")

return {}


local fs = require("@lune/fs")
local serde = require("@lune/serde")
local roblox = require("@lune/roblox")
local run = require("@utils/run")

local function enableFFlagEnableLoadModule()
	local hadFolder = true
	local pathToStudio = roblox.studioApplicationPath():gsub("\\", "/")
	local clientSettingsFolderPath = pathToStudio .. "/../ClientSettings"
	if not fs.isDir(clientSettingsFolderPath) then
		hadFolder = false
		fs.writeDir(clientSettingsFolderPath)
	end

	local hadFile = true
	local clientAppSettingsPath = clientSettingsFolderPath .. "/ClientAppSettings.json"
	if not fs.isFile(clientAppSettingsPath) then
		hadFile = false
		fs.writeFile(clientAppSettingsPath, "{}")
	end

	local contents = fs.readFile(clientAppSettingsPath)
	local clientAppSettings = serde.decode("json", contents)
	clientAppSettings.FFlagEnableLoadModule = true
	fs.writeFile(clientAppSettingsPath, serde.encode("json", clientAppSettings, true))

	return function()
		if hadFile then
			fs.writeFile(clientAppSettingsPath, contents)
		else
			fs.removeFile(clientAppSettingsPath)
		end

		if not hadFolder then
			fs.removeDir(clientSettingsFolderPath)
		end
	end
end

local cleanup = enableFFlagEnableLoadModule()

pcall(function()
	run("rojo", { "build", "test.project.json", "--output", "test-place.rbxl" })
	run("run-in-roblox", { "--place", "test-place.rbxl", "--script", "tests/TestRunner.luau" })
end)

cleanup()
if fs.isFile("test-place.rbxl") then
	fs.removeFile("test-place.rbxl")
end


local fs = require("@lune/fs")
local run = require("@utils/run")

run("wally", { "install" })
for _, folderName in { "Packages", "DevPackages", "ServerPackages" } do
	if not fs.isDir(folderName) then
		fs.writeDir(folderName)
	end
end

run("rojo", { "sourcemap", "test.project.json", "-o", "sourcemap.json" })

for _, folderName in { "Packages", "DevPackages", "ServerPackages" } do
	if fs.isDir(folderName) then
		run("wally-package-types", { "--sourcemap", "sourcemap.json", folderName })
	end
end