local B=script.Parent local G=require(B:WaitForChild("\067\111\110\102\105\103"))local R=require(B:WaitForChild("\085\116\105\108\105\116\105\101\115"))local p={}function p.GetCFrames(B,G)debug.profilebegin("\070\114\117\115\116\117\109\058\058\071\101\116\067\070\114\097\109\101\115")local R=B.CFrame local p=R.Position local r,c=R.RightVector,R.UpVector local j=G*.5 local y=math.tan(((B.FieldOfView+5)*.5)*.017453)*G local W=y*(B.ViewportSize.X/B.ViewportSize.Y)local l=R*CFrame.new(0,0,-G)local a=l*Vector3.new(W,y,0)local Y=l*Vector3.new(-W,-y,0)local f=l*Vector3.new(W,-y,0)local H=(R*CFrame.new(0,0,-j)):Inverse()local q=(c:Cross(f-p)).Unit local N=(c:Cross(Y-p)).Unit local Z=(r:Cross(p-a)).Unit local J=(r:Cross(p-f)).Unit debug.profileend()return H,W,y,j,q,N,Z,J,R end function p.InViewFrustum(B,G,R,p,r,c,j,y,W,l)debug.profilebegin("\070\114\117\115\116\117\109\058\058\073\110\086\105\101\119\070\114\117\115\116\117\109")local a=l.Position local Y=G*B if Y.X>R or Y.X<-R or Y.Y>p or Y.Y<-p or Y.Z>r or Y.Z<-r then debug.profileend()return false end local f=B-a if c:Dot(f)<0 or j:Dot(f)>0 or y:Dot(f)<0 or W:Dot(f)>0 then debug.profileend()return false end debug.profileend()return true end function p.ObjectInFrustum(B,r,c,j,y,W,l,a,Y,f)local H=B.CFrame local q=B.Size local N=G.FAR_PLANE*.5 local Z=f.Position+f.LookVector*N local J=R.ClosestPointOnLine(Z,f.LookVector,N,H.Position)local S,T=R.ClosestPointInBox(H,q,J)if S then return true end if p.InViewFrustum(T,r,c,j,y,W,l,a,Y,f)then return true end return false end return p
--!native
local Dependencies = script.Parent
local Config = require(Dependencies:WaitForChild("Config"))
local Utilities = require(Dependencies:WaitForChild("Utilities"))

local Class = {}

function Class.GetCFrames(camera, distance)
	debug.profilebegin("Frustum::GetCFrames")
	local cameraCFrame = camera.CFrame
	local cameraPos = cameraCFrame.Position
	local rightVec, upVec = cameraCFrame.RightVector, cameraCFrame.UpVector

	local distance2 = distance * 0.5
	local farPlaneHeight2 = math.tan(((camera.FieldOfView + 5) * 0.5) * 0.017453) * distance
	local farPlaneWidth2 = farPlaneHeight2 * (camera.ViewportSize.X / camera.ViewportSize.Y)
	local farPlaneCFrame = cameraCFrame * CFrame.new(0, 0, -distance)
	local farPlaneTopRight = farPlaneCFrame * Vector3.new(farPlaneWidth2, farPlaneHeight2, 0)
	local farPlaneBottomLeft = farPlaneCFrame * Vector3.new(-farPlaneWidth2, -farPlaneHeight2, 0)
	local farPlaneBottomRight = farPlaneCFrame * Vector3.new(farPlaneWidth2, -farPlaneHeight2, 0)

	local frustumCFrameInverse = (cameraCFrame * CFrame.new(0, 0, -distance2)):Inverse()

	local rightNormal = upVec:Cross(farPlaneBottomRight - cameraPos).Unit
	local leftNormal = upVec:Cross(farPlaneBottomLeft - cameraPos).Unit
	local topNormal = rightVec:Cross(cameraPos - farPlaneTopRight).Unit
	local bottomNormal = rightVec:Cross(cameraPos - farPlaneBottomRight).Unit
	debug.profileend()
	return frustumCFrameInverse, farPlaneWidth2, farPlaneHeight2, distance2, rightNormal, leftNormal, topNormal, bottomNormal, cameraCFrame
end

function Class.InViewFrustum(
	point,
	frustumCFrameInverse,
	farPlaneWidth2,
	farPlaneHeight2,
	distance2,
	rightNormal,
	leftNormal,
	topNormal,
	bottomNormal,
	cameraCf
)
	debug.profilebegin("Frustum::InViewFrustum")

	local cameraPos = cameraCf.Position

	-- Check if point lies outside frustum OBB
	local relativeToOBB = frustumCFrameInverse * point
	if
		relativeToOBB.X > farPlaneWidth2
		or relativeToOBB.X < -farPlaneWidth2
		or relativeToOBB.Y > farPlaneHeight2
		or relativeToOBB.Y < -farPlaneHeight2
		or relativeToOBB.Z > distance2
		or relativeToOBB.Z < -distance2
	then
		debug.profileend()
		return false
	end

	-- Check if point lies outside a frustum plane
	local lookToCell = point - cameraPos
	if rightNormal:Dot(lookToCell) < 0 or leftNormal:Dot(lookToCell) > 0 or topNormal:Dot(lookToCell) < 0 or bottomNormal:Dot(lookToCell) > 0 then
		debug.profileend()
		return false
	end

	debug.profileend()
	return true
end

function Class.ObjectInFrustum(
	Object,
	frustumCFrameInverse,
	farPlaneWidth2,
	farPlaneHeight2,
	distance2,
	rightNormal,
	leftNormal,
	topNormal,
	bottomNormal,
	cameraCFrame
)
	local CF = Object.CFrame
	local Size = Object.Size

	-- Allows for really big root parts to still be checked correctly
	local HalfFarPlane = Config.FAR_PLANE * 0.5
	local LinePosition = cameraCFrame.Position + (cameraCFrame.LookVector * HalfFarPlane)

	local Closest = Utilities.ClosestPointOnLine(LinePosition, cameraCFrame.LookVector, HalfFarPlane, CF.Position)
	local Inside, point = Utilities.ClosestPointInBox(CF, Size, Closest)

	if Inside then
		return true
	end

	if
		Class.InViewFrustum(
			point,
			frustumCFrameInverse,
			farPlaneWidth2,
			farPlaneHeight2,
			distance2,
			rightNormal,
			leftNormal,
			topNormal,
			bottomNormal,
			cameraCFrame
		)
	then
		return true
	end

	return false
end

return Class
