-- May return NaN or inf or -inf
-- This is a way of finding the angle between the two vectors:

local function findAngleBetweenXZVectors(vec2, vec1)
	return math_atan2(vec1.X*vec2.Z-vec1.Z*vec2.X, vec1.X*vec2.X + vec1.Z*vec2.Z)
end

local function GetValueObject(name, defaultValue)
	local valueObj = script:FindFirstChild(name)
	if valueObj then
		return valueObj.Value
	end
	return defaultValue
end

local function 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
		externalProperties[name] = valueObj.Value
	elseif 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 = externalProperties[name]
	else
		print("externalProperties table has no entry for ",name)
		return
	end
	
	if updateFunction then
		if changedSignalConnections[name] then
			changedSignalConnections[name]:disconnect()
		end
		changedSignalConnections[name] = valueObj.Changed:connect(function(newValue)
			externalProperties[name] = newValue
			updateFunction()
		end)
	end
end

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

local function 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(externalProperties["MinElevation"], MIN_ALLOWED_ELEVATION_DEG)
	local maxElevationDeg = math.min(externalProperties["MaxElevation"], MAX_ALLOWED_ELEVATION_DEG)
	
	-- Set internal values in radians
	minElevationRad = math.rad(math.min(minElevationDeg, maxElevationDeg))
	maxElevationRad = math.rad(math.max(minElevationDeg, maxElevationDeg))
	curElevationRad = math.max(curElevationRad, minElevationRad)
	curElevationRad = math.min(curElevationRad, maxElevationRad)
end

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