local d=require(script.Parent:WaitForChild("\066\097\115\101\067\097\109\101\114\097"))local y=setmetatable({},d)y.__index=y function y.new()local I=setmetatable(d.new(),y)I.lastUpdate=tick()I.changedSignalConnections={}I.refAzimuthRad=nil I.curAzimuthRad=nil I.minAzimuthAbsoluteRad=nil I.maxAzimuthAbsoluteRad=nil I.useAzimuthLimits=nil I.curElevationRad=nil I.minElevationRad=nil I.maxElevationRad=nil I.curDistance=nil I.minDistance=nil I.maxDistance=nil I.r3ButtonDown=false I.l3ButtonDown=false I.gamepadDollySpeedMultiplier=1 I.lastUserPanCamera=tick()I.externalProperties={}I.externalProperties.InitialDistance=25 I.externalProperties.MinDistance=10 I.externalProperties.MaxDistance=100 I.externalProperties.InitialElevation=35 I.externalProperties.MinElevation=35 I.externalProperties.MaxElevation=35 I.externalProperties.ReferenceAzimuth=-45 I.externalProperties.CWAzimuthTravel=90 I.externalProperties.CCWAzimuthTravel=90 I.externalProperties.UseAzimuthLimits=false I:LoadNumberValueParameters()return I end function y.LoadOrCreateNumberValueParameter(t,d,y,I)local Z=script:FindFirstChild(d)if Z and Z:isA(y)then t.externalProperties[d]=Z.Value elseif t.externalProperties[d]~=nil then Z=Instance.new(y)Z.Name=d Z.Parent=script Z.Value=t.externalProperties[d]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 ",d)return end if I then if t.changedSignalConnections[d]then t.changedSignalConnections[d]:Disconnect()end t.changedSignalConnections[d]=Z.Changed:Connect(function(y)t.externalProperties[d]=y I(t)end)end end function y.SetAndBoundsCheckAzimuthValues(d)d.minAzimuthAbsoluteRad=math.rad(d.externalProperties.ReferenceAzimuth)-math.abs(math.rad(d.externalProperties.CWAzimuthTravel))d.maxAzimuthAbsoluteRad=math.rad(d.externalProperties.ReferenceAzimuth)+math.abs(math.rad(d.externalProperties.CCWAzimuthTravel))d.useAzimuthLimits=d.externalProperties.UseAzimuthLimits if d.useAzimuthLimits then d.curAzimuthRad=math.max(d.curAzimuthRad,d.minAzimuthAbsoluteRad)d.curAzimuthRad=math.min(d.curAzimuthRad,d.maxAzimuthAbsoluteRad)end end function y.SetAndBoundsCheckElevationValues(d)local y=math.max(d.externalProperties.MinElevation,MIN_ALLOWED_ELEVATION_DEG)local I=math.min(d.externalProperties.MaxElevation,MAX_ALLOWED_ELEVATION_DEG)d.minElevationRad=math.rad(math.min(y,I))d.maxElevationRad=math.rad(math.max(y,I))d.curElevationRad=math.max(d.curElevationRad,d.minElevationRad)d.curElevationRad=math.min(d.curElevationRad,d.maxElevationRad)end function y.SetAndBoundsCheckDistanceValues(d)d.minDistance=d.externalProperties.MinDistance d.maxDistance=d.externalProperties.MaxDistance d.curDistance=math.max(d.curDistance,d.minDistance)d.curDistance=math.min(d.curDistance,d.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
