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