local A=require(script.Parent:WaitForChild("\066\097\115\101\067\097\109\101\114\097"))local d=setmetatable({},A)d.__index=d function d.new()local n=setmetatable(A.new(),d)n.lastUpdate=tick()n.changedSignalConnections={}n.refAzimuthRad=nil n.curAzimuthRad=nil n.minAzimuthAbsoluteRad=nil n.maxAzimuthAbsoluteRad=nil n.useAzimuthLimits=nil n.curElevationRad=nil n.minElevationRad=nil n.maxElevationRad=nil n.curDistance=nil n.minDistance=nil n.maxDistance=nil n.r3ButtonDown=false n.l3ButtonDown=false n.gamepadDollySpeedMultiplier=1 n.lastUserPanCamera=tick()n.externalProperties={}n.externalProperties.InitialDistance=25 n.externalProperties.MinDistance=10 n.externalProperties.MaxDistance=100 n.externalProperties.InitialElevation=35 n.externalProperties.MinElevation=35 n.externalProperties.MaxElevation=35 n.externalProperties.ReferenceAzimuth=-45 n.externalProperties.CWAzimuthTravel=90 n.externalProperties.CCWAzimuthTravel=90 n.externalProperties.UseAzimuthLimits=false n:LoadNumberValueParameters()return n end function d.LoadOrCreateNumberValueParameter(C,A,d,n)local a=script:FindFirstChild(A)if a and a:isA(d)then C.externalProperties[A]=a.Value elseif C.externalProperties[A]~=nil then a=Instance.new(d)a.Name=A a.Parent=script a.Value=C.externalProperties[A]else print("\101\120\116\101\114\110\097\108\080\114\111\112\101\114\116\105\101\115 \116\097\098\108\101 \104\097\115 \110\111 \101\110\116\114\121 \102\111\114 ",A)return end if n then if C.changedSignalConnections[A]then C.changedSignalConnections[A]:Disconnect()end C.changedSignalConnections[A]=a.Changed:Connect(function(d)C.externalProperties[A]=d n(C)end)end end function d.SetAndBoundsCheckAzimuthValues(A)A.minAzimuthAbsoluteRad=math.rad(A.externalProperties.ReferenceAzimuth)-math.abs(math.rad(A.externalProperties.CWAzimuthTravel))A.maxAzimuthAbsoluteRad=math.rad(A.externalProperties.ReferenceAzimuth)+math.abs(math.rad(A.externalProperties.CCWAzimuthTravel))A.useAzimuthLimits=A.externalProperties.UseAzimuthLimits if A.useAzimuthLimits then A.curAzimuthRad=math.max(A.curAzimuthRad,A.minAzimuthAbsoluteRad)A.curAzimuthRad=math.min(A.curAzimuthRad,A.maxAzimuthAbsoluteRad)end end function d.SetAndBoundsCheckElevationValues(A)local d=math.max(A.externalProperties.MinElevation,MIN_ALLOWED_ELEVATION_DEG)local n=math.min(A.externalProperties.MaxElevation,MAX_ALLOWED_ELEVATION_DEG)A.minElevationRad=math.rad(math.min(d,n))A.maxElevationRad=math.rad(math.max(d,n))A.curElevationRad=math.max(A.curElevationRad,A.minElevationRad)A.curElevationRad=math.min(A.curElevationRad,A.maxElevationRad)end function d.SetAndBoundsCheckDistanceValues(A)A.minDistance=A.externalProperties.MinDistance A.maxDistance=A.externalProperties.MaxDistance A.curDistance=math.max(A.curDistance,A.minDistance)A.curDistance=math.min(A.curDistance,A.maxDistance)end
--[[ The Module ]]
--
local BaseCamera = require(script.Parent:WaitForChild("BaseCamera"))
local OrbitalCamera = setmetatable({}, BaseCamera)
OrbitalCamera.__index = OrbitalCamera

function OrbitalCamera.new()
	local self = setmetatable(BaseCamera.new(), OrbitalCamera)

	self.lastUpdate = tick()

	-- OrbitalCamera-specific members
	self.changedSignalConnections = {}
	self.refAzimuthRad = nil
	self.curAzimuthRad = nil
	self.minAzimuthAbsoluteRad = nil
	self.maxAzimuthAbsoluteRad = nil
	self.useAzimuthLimits = nil
	self.curElevationRad = nil
	self.minElevationRad = nil
	self.maxElevationRad = nil
	self.curDistance = nil
	self.minDistance = nil
	self.maxDistance = nil

	-- Gamepad
	self.r3ButtonDown = false
	self.l3ButtonDown = false
	self.gamepadDollySpeedMultiplier = 1

	self.lastUserPanCamera = tick()

	self.externalProperties = {}
	self.externalProperties["InitialDistance"] = 25
	self.externalProperties["MinDistance"] = 10
	self.externalProperties["MaxDistance"] = 100
	self.externalProperties["InitialElevation"] = 35
	self.externalProperties["MinElevation"] = 35
	self.externalProperties["MaxElevation"] = 35
	self.externalProperties["ReferenceAzimuth"] = -45 -- Angle around the Y axis where the camera starts. -45 offsets the camera in the -X and +Z directions equally
	self.externalProperties["CWAzimuthTravel"] = 90 -- How many degrees the camera is allowed to rotate from the reference position, CW as seen from above
	self.externalProperties["CCWAzimuthTravel"] = 90 -- How many degrees the camera is allowed to rotate from the reference position, CCW as seen from above
	self.externalProperties["UseAzimuthLimits"] = false -- Full rotation around Y axis available by default
	self:LoadNumberValueParameters()

	return self
end

function OrbitalCamera:LoadOrCreateNumberValueParameter(name, valueType, updateFunction)
	local valueObj = script:FindFirstChild(name)

	if valueObj and valueObj:isA(valueType) then
		-- Value object exists and is the correct type, use its value
		self.externalProperties[name] = valueObj.Value
	elseif self.externalProperties[name] ~= nil then
		-- Create missing (or replace incorrectly-typed) valueObject with default value
		valueObj = Instance.new(valueType)
		valueObj.Name = name
		valueObj.Parent = script
		valueObj.Value = self.externalProperties[name]
	else
		print("externalProperties table has no entry for ", name)
		return
	end

	if updateFunction then
		if self.changedSignalConnections[name] then
			self.changedSignalConnections[name]:Disconnect()
		end
		self.changedSignalConnections[name] = valueObj.Changed:Connect(function(newValue)
			self.externalProperties[name] = newValue
			updateFunction(self)
		end)
	end
end

function OrbitalCamera:SetAndBoundsCheckAzimuthValues()
	self.minAzimuthAbsoluteRad = math.rad(self.externalProperties["ReferenceAzimuth"])
		- math.abs(math.rad(self.externalProperties["CWAzimuthTravel"]))
	self.maxAzimuthAbsoluteRad = math.rad(self.externalProperties["ReferenceAzimuth"])
		+ math.abs(math.rad(self.externalProperties["CCWAzimuthTravel"]))
	self.useAzimuthLimits = self.externalProperties["UseAzimuthLimits"]
	if self.useAzimuthLimits then
		self.curAzimuthRad = math.max(self.curAzimuthRad, self.minAzimuthAbsoluteRad)
		self.curAzimuthRad = math.min(self.curAzimuthRad, self.maxAzimuthAbsoluteRad)
	end
end

function OrbitalCamera:SetAndBoundsCheckElevationValues()
	-- These degree values are the direct user input values. It is deliberate that they are
	-- ranged checked only against the extremes, and not against each other. Any time one
	-- is changed, both of the internal values in radians are recalculated. This allows for
	-- A developer to change the values in any order and for the end results to be that the
	-- internal values adjust to match intent as best as possible.
	local minElevationDeg = math.max(self.externalProperties["MinElevation"], MIN_ALLOWED_ELEVATION_DEG)
	local maxElevationDeg = math.min(self.externalProperties["MaxElevation"], MAX_ALLOWED_ELEVATION_DEG)

	-- Set internal values in radians
	self.minElevationRad = math.rad(math.min(minElevationDeg, maxElevationDeg))
	self.maxElevationRad = math.rad(math.max(minElevationDeg, maxElevationDeg))
	self.curElevationRad = math.max(self.curElevationRad, self.minElevationRad)
	self.curElevationRad = math.min(self.curElevationRad, self.maxElevationRad)
end

function OrbitalCamera:SetAndBoundsCheckDistanceValues()
	self.minDistance = self.externalProperties["MinDistance"]
	self.maxDistance = self.externalProperties["MaxDistance"]
	self.curDistance = math.max(self.curDistance, self.minDistance)
	self.curDistance = math.min(self.curDistance, self.maxDistance)
end
