local V={}local A=math.sin local T=math.cos local F=math.sqrt local n=math.tan local j=math.floor local s=math.rad local y=math.max local x=math.min local q=math.huge local Q=math.abs local e=math.sign local C=math.pi local function H(V,A)A=A or Vector3.zero local T,F,n=q,q,q local j,s,Q=-q,-q,-q for V,q in pairs(V)do local e,C,H=unpack(q)e=e+A.X C=C+A.Y H=H+A.Z T=x(T,e)F=x(F,C)n=x(n,H)j=y(j,e)s=y(s,C)Q=y(Q,H)end return{Min=Vector3.new(T,F,n);Max=Vector3.new(j,s,Q)}end local function J(V,A)local T=H(V.points)return A.X>=T.Min.X and(A.X<=T.Max.X and(A.Y>=T.Min.Y and(A.Y<=T.Max.Y and(A.Z>=T.Min.Z and A.Z<=T.Max.Z))))end local function B(V,A)local T=V.Origin local F=V.Direction local n=nil if J(A,T)then n=T return true,n end local j=A.points for V,j in pairs(j)do local s=Vector3.new(j[1],j[2],j[3])local y=(s-T):Dot(F)if y>=0 and J(A,T+y*F)then n=T+y*F return true,n end end return false,nil end local function N(V,A,T,n,j)local s=T:GetCFrame()local y=s:Inverse()local x=Vector3.new(V,A,0)local q=((x-Vector3.new(n/2,j/2,0))/F(n^2+j^2))*2 local Q=(y*q).unit return Ray.new(T.Position,Q)end local function h(V,A)local T=V.Origin local F=V.Direction local n=q local j=nil local s=A.points for V,s in pairs(s)do local y=Vector3.new(s[1],s[2],s[3])local x=(y-T):Dot(F)if x>=0 and J(A,T+x*F)then if x<n then n=x j=T+x*F end end end return j end local function p(V,A)local T=V.points local F=Vector3.zero local n=Vector3.zero local j=q for V,s in pairs(V.triangles)do local y=Vector3.new(T[s[1]][1],T[s[1]][2],T[s[1]][3])local x=Vector3.new(T[s[2]][1],T[s[2]][2],T[s[2]][3])local q=Vector3.new(T[s[3]][1],T[s[3]][2],T[s[3]][3])local e=x-y local C=q-y local H=(e:Cross(C)).unit local J=H:Dot(y-A)if Q(J)<.001 then F=F+H end if J<j then n=H j=J end end if F.Magnitude==0 then return n.unit else return F.unit end end function V.Raycast(V,A,T)local F=Ray.new(V,A)for V,A in pairs(T)do local T,n=B(F,A)if T then return A.Name,n end end return nil,nil end function V.RaycastWithNormals(V,A,T,F)local n=Ray.new(V,A)local j,s,y=nil,nil,nil local x=q for A,F in pairs(F)do if table.find(T,F.Name)==nil then local A,T=B(n,F)if A then local A=(T-V).Magnitude if A<x then j=F s=T y=p(F,s)x=A end end end end return j,s,y end function V.AABBCollision(V,A,T)local F=H(V.points,T)local n=H(A.points)local j=F.Min.X<=n.Max.X and F.Max.X>=n.Min.X local s=F.Min.Y<=n.Max.Y and F.Max.Y>=n.Min.Y local y=F.Min.Z<=n.Max.Z and F.Max.Z>=n.Min.Z if j and(s and y)then local V=Vector3.new(0,0,0)local A=x(F.Max.X-n.Min.X,n.Max.X-F.Min.X)local T=x(F.Max.Y-n.Min.Y,n.Max.Y-F.Min.Y)local j=x(F.Max.Z-n.Min.Z,n.Max.Z-F.Min.Z)local s=x(A,T,j)if s==A then V=Vector3.new(e(F.Max.X-n.Min.X),0,0)elseif s==T then V=Vector3.new(0,e(F.Max.Y-n.Min.Y),0)else V=Vector3.new(0,0,e(F.Max.Z-n.Min.Z))end return true,V end return false,nil,0 end function V.IsModelInside(V,A)local T=H(V.points)local F=H(A.points)return T.Min.X>=F.Min.X and(T.Min.Y>=F.Min.Y and(T.Min.Z>=F.Min.Z and(T.Max.X<=F.Max.X and(T.Max.Y<=F.Max.Y and T.Max.Z<=F.Max.Z))))end return V

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
