local S={}local z=math.sin local m=math.cos local r=math.sqrt local Y=math.tan local O=math.floor local G=math.rad local I=math.max local R=math.min local B=math.huge local k=math.abs local v=math.sign local V=math.pi local function a(S,z)z=z or Vector3.zero local m,r,Y=B,B,B local O,G,k=-B,-B,-B for S,B in pairs(S)do local v,V,a=unpack(B)v=v+z.X V=V+z.Y a=a+z.Z m=R(m,v)r=R(r,V)Y=R(Y,a)O=I(O,v)G=I(G,V)k=I(k,a)end return{Min=Vector3.new(m,r,Y),Max=Vector3.new(O,G,k)}end local function x(S,z)local m=a(S.points)return z.X>=m.Min.X and(z.X<=m.Max.X and(z.Y>=m.Min.Y and(z.Y<=m.Max.Y and(z.Z>=m.Min.Z and z.Z<=m.Max.Z))))end local function H(S,z)local m=S.Origin local r=S.Direction local Y=nil if x(z,m)then Y=m return true,Y end local O=z.points for S,O in pairs(O)do local G=Vector3.new(O[1],O[2],O[3])local I=(G-m):Dot(r)if I>=0 and x(z,m+I*r)then Y=m+I*r return true,Y end end return false,nil end local function d(S,z,m,Y,O)local G=m:GetCFrame()local I=G:Inverse()local R=Vector3.new(S,z,0)local B=((R-Vector3.new(Y/2,O/2,0))/r(Y^2+O^2))*2 local k=(I*B).unit return Ray.new(m.Position,k)end local function y(S,z)local m=S.Origin local r=S.Direction local Y=B local O=nil local G=z.points for S,G in pairs(G)do local I=Vector3.new(G[1],G[2],G[3])local R=(I-m):Dot(r)if R>=0 and x(z,m+R*r)then if R<Y then Y=R O=m+R*r end end end return O end local function Q(S,z)local m=S.points local r=Vector3.zero local Y=Vector3.zero local O=B for S,G in pairs(S.triangles)do local I=Vector3.new(m[G[1]][1],m[G[1]][2],m[G[1]][3])local R=Vector3.new(m[G[2]][1],m[G[2]][2],m[G[2]][3])local B=Vector3.new(m[G[3]][1],m[G[3]][2],m[G[3]][3])local v=R-I local V=B-I local a=(v:Cross(V)).unit local x=a:Dot(I-z)if k(x)<.001 then r=r+a end if x<O then Y=a O=x end end if r.Magnitude==0 then return Y.unit else return r.unit end end function S.Raycast(S,z,m)local r=Ray.new(S,z)for S,z in pairs(m)do local m,Y=H(r,z)if m then return z.Name,Y end end return nil,nil end function S.RaycastWithNormals(S,z,m,r)local Y=Ray.new(S,z)local O,G,I=nil,nil,nil local R=B for z,r in pairs(r)do if table.find(m,r.Name)==nil then local z,m=H(Y,r)if z then local z=(m-S).Magnitude if z<R then O=r G=m I=Q(r,G)R=z end end end end return O,G,I end function S.AABBCollision(S,z,m)local r=a(S.points,m)local Y=a(z.points)local O=r.Min.X<=Y.Max.X and r.Max.X>=Y.Min.X local G=r.Min.Y<=Y.Max.Y and r.Max.Y>=Y.Min.Y local I=r.Min.Z<=Y.Max.Z and r.Max.Z>=Y.Min.Z if O and(G and I)then local S=Vector3.new(0,0,0)local z=R(r.Max.X-Y.Min.X,Y.Max.X-r.Min.X)local m=R(r.Max.Y-Y.Min.Y,Y.Max.Y-r.Min.Y)local O=R(r.Max.Z-Y.Min.Z,Y.Max.Z-r.Min.Z)local G=R(z,m,O)if G==z then S=Vector3.new(v(r.Max.X-Y.Min.X),0,0)elseif G==m then S=Vector3.new(0,v(r.Max.Y-Y.Min.Y),0)else S=Vector3.new(0,0,v(r.Max.Z-Y.Min.Z))end return true,S end return false,nil,0 end function S.IsModelInside(S,z)local m=a(S.points)local r=a(z.points)return m.Min.X>=r.Min.X and(m.Min.Y>=r.Min.Y and(m.Min.Z>=r.Min.Z and(m.Max.X<=r.Max.X and(m.Max.Y<=r.Max.Y and m.Max.Z<=r.Max.Z))))end return S
--!native
local CollisionModule = {}

local sin = math.sin
local cos = math.cos
local sqrt = math.sqrt
local tan = math.tan
local floor = math.floor
local rad = math.rad

local max = math.max
local min = math.min
local huge = math.huge
local abs = math.abs
local sign = math.sign

local pi = math.pi

local function GetBoundingBox(points, offset)
	offset = offset or Vector3.zero

	local minX, minY, minZ = huge, huge, huge
	local maxX, maxY, maxZ = -huge, -huge, -huge

	for _, point in pairs(points) do
		local x, y, z = unpack(point)

		x = x + offset.X
		y = y + offset.Y
		z = z + offset.Z

		minX = min(minX, x)
		minY = min(minY, y)
		minZ = min(minZ, z)

		maxX = max(maxX, x)
		maxY = max(maxY, y)
		maxZ = max(maxZ, z)
	end

	return {
		Min = Vector3.new(minX, minY, minZ),
		Max = Vector3.new(maxX, maxY, maxZ),
	}
end

local function IsPointInsideModel(model, point)
	local bounds = GetBoundingBox(model.points)
	return point.X >= bounds.Min.X
		and point.X <= bounds.Max.X
		and point.Y >= bounds.Min.Y
		and point.Y <= bounds.Max.Y
		and point.Z >= bounds.Min.Z
		and point.Z <= bounds.Max.Z
end

local function DoesRayIntersectModel(ray, model)
	local origin = ray.Origin
	local direction = ray.Direction
	local intersectionPoint = nil

	-- Check if the ray's origin is inside the model
	if IsPointInsideModel(model, origin) then
		intersectionPoint = origin
		return true, intersectionPoint
	end

	-- Check for intersections with each model point
	local points = model.points
	for _, vertex in pairs(points) do
		local point = Vector3.new(vertex[1], vertex[2], vertex[3])
		local hitDistance = (point - origin):Dot(direction)
		if hitDistance >= 0 and IsPointInsideModel(model, origin + hitDistance * direction) then
			intersectionPoint = origin + hitDistance * direction
			return true, intersectionPoint
		end
	end

	return false, nil
end

local function CalculateMouseRay(mouseX, mouseY, Camera, viewportWidth, viewportHeight)
	local cameraCFrame = Camera:GetCFrame()
	local cameraInverseCFrame = cameraCFrame:Inverse()
	local mousePosition = Vector3.new(mouseX, mouseY, 0)

	local mouseRayDirection = (mousePosition - Vector3.new(viewportWidth / 2, viewportHeight / 2, 0))
		/ sqrt(viewportWidth ^ 2 + viewportHeight ^ 2)
		* 2

	local mouseRayWorldDirection = (cameraInverseCFrame * mouseRayDirection).unit
	return Ray.new(Camera.Position, mouseRayWorldDirection)
end

local function GetIntersectionPoint(ray, model)
	local origin = ray.Origin
	local direction = ray.Direction

	local closestDistance = huge
	local intersectionPoint = nil

	-- Check for intersections with each model point
	local points = model.points
	for _, vertex in pairs(points) do
		local point = Vector3.new(vertex[1], vertex[2], vertex[3])
		local hitDistance = (point - origin):Dot(direction)
		if hitDistance >= 0 and IsPointInsideModel(model, origin + hitDistance * direction) then
			-- Update the closest intersection point if this point is closer to the ray's origin
			if hitDistance < closestDistance then
				closestDistance = hitDistance
				intersectionPoint = origin + hitDistance * direction
			end
		end
	end

	return intersectionPoint
end

local function CalculateSurfaceNormal(model, hitPoint)
	local points = model.points
	local normalsSum = Vector3.zero

	local closestNormal = Vector3.zero
	local closestDistance = huge

	for _, triangle in pairs(model.triangles) do
		local vertex1 = Vector3.new(points[triangle[1]][1], points[triangle[1]][2], points[triangle[1]][3])
		local vertex2 = Vector3.new(points[triangle[2]][1], points[triangle[2]][2], points[triangle[2]][3])
		local vertex3 = Vector3.new(points[triangle[3]][1], points[triangle[3]][2], points[triangle[3]][3])

		local edge1 = vertex2 - vertex1
		local edge2 = vertex3 - vertex1
		local normal = edge1:Cross(edge2).unit

		local distanceToPlane = normal:Dot(vertex1 - hitPoint)
		if abs(distanceToPlane) < 0.001 then
			normalsSum = normalsSum + normal
		end

		if distanceToPlane < closestDistance then
			closestNormal = normal
			closestDistance = distanceToPlane
		end
	end

	if normalsSum.Magnitude == 0 then
		return closestNormal.unit
	else
		return normalsSum.unit
	end
end

function CollisionModule.Raycast(origin, direction, whitelist)
	local ray = Ray.new(origin, direction)

	-- Check for collisions with each model in the whitelist
	for _, model in pairs(whitelist) do
		local doesIntersect, intersectionPoint = DoesRayIntersectModel(ray, model)
		if doesIntersect then
			return model.Name, intersectionPoint
		end
	end

	return nil, nil
end

function CollisionModule.RaycastWithNormals(origin, direction, blacklist, registry)
	local ray = Ray.new(origin, direction)
	local hitModel, hitPoint, surfaceNormal = nil, nil, nil
	local closestDistance = huge

	for i, model in pairs(registry) do
		if table.find(blacklist, model.Name) == nil then
			local doesIntersect, intersectionPoint = DoesRayIntersectModel(ray, model)

			if doesIntersect then
				local hitDistance = (intersectionPoint - origin).Magnitude

				if hitDistance < closestDistance then
					hitModel = model
					hitPoint = intersectionPoint
					surfaceNormal = CalculateSurfaceNormal(model, hitPoint)
					closestDistance = hitDistance
				end
			end
		end
	end

	return hitModel, hitPoint, surfaceNormal
end

function CollisionModule.AABBCollision(model1, model2, offset)
	local bounds1 = GetBoundingBox(model1.points, offset)
	local bounds2 = GetBoundingBox(model2.points)

	-- Check for collision along each axis (X, Y, Z)
	local collisionX = bounds1.Min.X <= bounds2.Max.X and bounds1.Max.X >= bounds2.Min.X
	local collisionY = bounds1.Min.Y <= bounds2.Max.Y and bounds1.Max.Y >= bounds2.Min.Y
	local collisionZ = bounds1.Min.Z <= bounds2.Max.Z and bounds1.Max.Z >= bounds2.Min.Z

	-- If there's a collision along all three axes, there's a collision
	if collisionX and collisionY and collisionZ then
		-- Calculate the collision normal (direction of collision)
		local collisionNormal = Vector3.new(0, 0, 0)

		-- Determine which side was hit based on the minimum penetration depth along each axis
		local minPenetrationX = min(bounds1.Max.X - bounds2.Min.X, bounds2.Max.X - bounds1.Min.X)
		local minPenetrationY = min(bounds1.Max.Y - bounds2.Min.Y, bounds2.Max.Y - bounds1.Min.Y)
		local minPenetrationZ = min(bounds1.Max.Z - bounds2.Min.Z, bounds2.Max.Z - bounds1.Min.Z)

		-- Find the axis with the minimum penetration depth
		local minPenetration = min(minPenetrationX, minPenetrationY, minPenetrationZ)

		if minPenetration == minPenetrationX then
			collisionNormal = Vector3.new(sign(bounds1.Max.X - bounds2.Min.X), 0, 0)
		elseif minPenetration == minPenetrationY then
			collisionNormal = Vector3.new(0, sign(bounds1.Max.Y - bounds2.Min.Y), 0)
		else
			collisionNormal = Vector3.new(0, 0, sign(bounds1.Max.Z - bounds2.Min.Z))
		end

		return true, collisionNormal
	end

	return false, nil, 0
end

function CollisionModule.IsModelInside(model1, model2)
	local bounds1 = GetBoundingBox(model1.points)
	local bounds2 = GetBoundingBox(model2.points)

	return bounds1.Min.X >= bounds2.Min.X
		and bounds1.Min.Y >= bounds2.Min.Y
		and bounds1.Min.Z >= bounds2.Min.Z
		and bounds1.Max.X <= bounds2.Max.X
		and bounds1.Max.Y <= bounds2.Max.Y
		and bounds1.Max.Z <= bounds2.Max.Z
end

return CollisionModule
