function Invisicam.LimbBehavior(R,D)for R,j in pairs(R.trackedLimbs)do D[#D+1]=R.Position end end function Invisicam.MoveBehavior(R,D)for j=1,MOVE_CASTS,1 do local Y,f=R.humanoidRootPart.Position,R.humanoidRootPart.Velocity local A=(Vector3.new(f.X,0,f.Z)).Magnitude/2 local P=((j-1)*R.humanoidRootPart.CFrame.lookVector)*A D[#D+1]=Y+P end end function Invisicam.CornerBehavior(R,D)local j=R.humanoidRootPart.CFrame local Y=j.p local f=j-Y local A=R.char:GetExtentsSize()/2 D[#D+1]=Y for R=1,#CORNER_FACTORS,1 do D[#D+1]=Y+f*(A*CORNER_FACTORS[R])end end function Invisicam.CircleBehavior(R,D)local j if R.mode==MODE.CIRCLE1 then j=R.humanoidRootPart.CFrame else local D=R.camera.CoordinateFrame j=(D-D.p)+R.humanoidRootPart.Position end D[#D+1]=j.p for R=0,CIRCLE_CASTS-1,1 do local Y=((2*math.pi)/CIRCLE_CASTS)*R local f=3*Vector3.new(math.cos(Y),math.sin(Y),0)D[#D+1]=j*f end end function Invisicam.LimbMoveBehavior(R,D)R:LimbBehavior(D)R:MoveBehavior(D)end function Invisicam.CharacterOutlineBehavior(R,D)local j=R.torsoPart.CFrame.upVector.unit local Y=R.torsoPart.CFrame.rightVector.unit D[#D+1]=R.torsoPart.CFrame.p D[#D+1]=R.torsoPart.CFrame.p+j D[#D+1]=R.torsoPart.CFrame.p-j D[#D+1]=R.torsoPart.CFrame.p+Y D[#D+1]=R.torsoPart.CFrame.p-Y if R.headPart then D[#D+1]=R.headPart.CFrame.p end local f=CFrame.new(ZERO_VECTOR3,Vector3.new(R.camera.CoordinateFrame.lookVector.X,0,R.camera.CoordinateFrame.lookVector.Z))local A=R.torsoPart and R.torsoPart.Position or R.humanoidRootPart.Position local P={R.torsoPart}if R.headPart then P[#P+1]=R.headPart end for R=1,CHAR_OUTLINE_CASTS,1 do local j=((2*math.pi)*R)/CHAR_OUTLINE_CASTS local Y=f*(3*Vector3.new(math.cos(j),math.sin(j),0))Y=Vector3.new(Y.X,math.max(Y.Y,-2.25),Y.Z)local T=Ray.new(A+Y,-3*Y)local e,k=game.Workspace:FindPartOnRayWithWhitelist(T,P,false,false)if e then D[#D+1]=k+.2*(A-k).unit end end end function Invisicam.SmartCircleBehavior(R,D)local j=R.torsoPart.CFrame.upVector.unit local Y=R.torsoPart.CFrame.rightVector.unit D[#D+1]=R.torsoPart.CFrame.p D[#D+1]=R.torsoPart.CFrame.p+j D[#D+1]=R.torsoPart.CFrame.p-j D[#D+1]=R.torsoPart.CFrame.p+Y D[#D+1]=R.torsoPart.CFrame.p-Y if R.headPart then D[#D+1]=R.headPart.CFrame.p end local f=R.camera.CFrame-R.camera.CFrame.p local A=Vector3.new(0,.5,0)+(R.torsoPart and R.torsoPart.Position or R.humanoidRootPart.Position)local P=2.5 for j=1,SMART_CIRCLE_CASTS,1 do local Y=SMART_CIRCLE_INCREMENT*j-.5*math.pi local T=P*Vector3.new(math.cos(Y),math.sin(Y),0)local e=A+f*T local k=e-R.camera.CFrame.p local N=Ray.new(A,e-A)local W,B,Z=game.Workspace:FindPartOnRayWithIgnoreList(N,{R.char},false,false)local s=e if W then local D=B+.1*Z.unit local j=D-A local Y=(j:Cross(k)).unit local f=(Y:Cross(Z)).unit local P=(D-R.camera.CFrame.p).unit if j.unit:Dot(-f)<j.unit:Dot(P)then s=RayIntersection(D,f,e,k)if s.Magnitude>0 then local j=Ray.new(D,s-D)local Y,f,A=game.Workspace:FindPartOnRayWithIgnoreList(j,{R.char},false,false)if Y then local D=f+.1*A.unit s=D end else s=D end else s=D end local T=Ray.new(A,s-A)local N,W,h=game.Workspace:FindPartOnRayWithIgnoreList(T,{R.char},false,false)if N then local D=W-.1*(s-A).unit s=D end end D[#D+1]=s end end function Invisicam.CheckTorsoReference(D)if D.char then D.torsoPart=D.char:FindFirstChild("\084\111\114\115\111")if not D.torsoPart then D.torsoPart=D.char:FindFirstChild("\085\112\112\101\114\084\111\114\115\111")if not D.torsoPart then D.torsoPart=D.char:FindFirstChild("\072\117\109\097\110\111\105\100\082\111\111\116\080\097\114\116")end end D.headPart=D.char:FindFirstChild("\072\101\097\100")end end function Invisicam.CharacterAdded(j,D,R)if R~=PlayersService.LocalPlayer then return end if j.childAddedConn then j.childAddedConn:Disconnect()j.childAddedConn=nil end if j.childRemovedConn then j.childRemovedConn:Disconnect()j.childRemovedConn=nil end j.char=D j.trackedLimbs={}local function Y(D)if D:IsA("\066\097\115\101\080\097\114\116")then if LIMB_TRACKING_SET[D.Name]then j.trackedLimbs[D]=true end if D.Name=="\084\111\114\115\111"or D.Name=="\085\112\112\101\114\084\111\114\115\111"then j.torsoPart=D end if D.Name=="\072\101\097\100"then j.headPart=D end end end local function f(D)j.trackedLimbs[D]=nil j:CheckTorsoReference()end j.childAddedConn=D.ChildAdded:Connect(Y)j.childRemovedConn=D.ChildRemoved:Connect(f)for D,R in pairs(j.char:GetChildren())do Y(R)end end function Invisicam.SetMode(R,D)AssertTypes(D,"\110\117\109\098\101\114")for j,Y in pairs(MODE)do if Y==D then R.mode=D R.behaviorFunction=R.behaviors[R.mode]return end end error("\073\110\118\097\108\105\100 \109\111\100\101 \110\117\109\098\101\114")end function Invisicam.GetObscuredParts(D)return D.savedHits end
--
function Invisicam:LimbBehavior(castPoints)
	for limb, _ in pairs(self.trackedLimbs) do
		castPoints[#castPoints + 1] = limb.Position
	end
end

function Invisicam:MoveBehavior(castPoints)
	for i = 1, MOVE_CASTS do
		local position, velocity = self.humanoidRootPart.Position, self.humanoidRootPart.Velocity
		local horizontalSpeed = Vector3.new(velocity.X, 0, velocity.Z).Magnitude / 2
		local offsetVector = (i - 1) * self.humanoidRootPart.CFrame.lookVector * horizontalSpeed
		castPoints[#castPoints + 1] = position + offsetVector
	end
end

function Invisicam:CornerBehavior(castPoints)
	local cframe = self.humanoidRootPart.CFrame
	local centerPoint = cframe.p
	local rotation = cframe - centerPoint
	local halfSize = self.char:GetExtentsSize() / 2 --NOTE: Doesn't update w/ limb animations
	castPoints[#castPoints + 1] = centerPoint
	for i = 1, #CORNER_FACTORS do
		castPoints[#castPoints + 1] = centerPoint + (rotation * (halfSize * CORNER_FACTORS[i]))
	end
end

function Invisicam:CircleBehavior(castPoints)
	local cframe
	if self.mode == MODE.CIRCLE1 then
		cframe = self.humanoidRootPart.CFrame
	else
		local camCFrame = self.camera.CoordinateFrame
		cframe = camCFrame - camCFrame.p + self.humanoidRootPart.Position
	end
	castPoints[#castPoints + 1] = cframe.p
	for i = 0, CIRCLE_CASTS - 1 do
		local angle = (2 * math.pi / CIRCLE_CASTS) * i
		local offset = 3 * Vector3.new(math.cos(angle), math.sin(angle), 0)
		castPoints[#castPoints + 1] = cframe * offset
	end
end

function Invisicam:LimbMoveBehavior(castPoints)
	self:LimbBehavior(castPoints)
	self:MoveBehavior(castPoints)
end

function Invisicam:CharacterOutlineBehavior(castPoints)
	local torsoUp = self.torsoPart.CFrame.upVector.unit
	local torsoRight = self.torsoPart.CFrame.rightVector.unit

	-- Torso cross of points for interior coverage
	castPoints[#castPoints + 1] = self.torsoPart.CFrame.p
	castPoints[#castPoints + 1] = self.torsoPart.CFrame.p + torsoUp
	castPoints[#castPoints + 1] = self.torsoPart.CFrame.p - torsoUp
	castPoints[#castPoints + 1] = self.torsoPart.CFrame.p + torsoRight
	castPoints[#castPoints + 1] = self.torsoPart.CFrame.p - torsoRight
	if self.headPart then
		castPoints[#castPoints + 1] = self.headPart.CFrame.p
	end

	local cframe = CFrame.new(ZERO_VECTOR3,Vector3.new(self.camera.CoordinateFrame.lookVector.X,0,self.camera.CoordinateFrame.lookVector.Z))
	local centerPoint = (self.torsoPart and self.torsoPart.Position or self.humanoidRootPart.Position)

	local partsWhitelist = {self.torsoPart}
	if self.headPart then
		partsWhitelist[#partsWhitelist + 1] = self.headPart
	end

	for i = 1, CHAR_OUTLINE_CASTS do
		local angle = (2 * math.pi * i / CHAR_OUTLINE_CASTS)
		local offset = cframe * (3 * Vector3.new(math.cos(angle), math.sin(angle), 0))

		offset = Vector3.new(offset.X, math.max(offset.Y, -2.25), offset.Z)	

		local ray = Ray.new(centerPoint + offset, -3 * offset)
		local hit, hitPoint = game.Workspace:FindPartOnRayWithWhitelist(ray, partsWhitelist, false, false)

		if hit then
			-- Use hit point as the cast point, but nudge it slightly inside the character so that bumping up against
			-- walls is less likely to cause a transparency glitch
			castPoints[#castPoints + 1] = hitPoint + 0.2 * (centerPoint - hitPoint).unit
		end
	end
end

function Invisicam:SmartCircleBehavior(castPoints)
	local torsoUp = self.torsoPart.CFrame.upVector.unit
	local torsoRight = self.torsoPart.CFrame.rightVector.unit

	-- SMART_CIRCLE mode includes rays to head and 5 to the torso.
	-- Hands, arms, legs and feet are not included since they
	-- are not canCollide and can therefore go inside of parts
	castPoints[#castPoints + 1] = self.torsoPart.CFrame.p
	castPoints[#castPoints + 1] = self.torsoPart.CFrame.p + torsoUp
	castPoints[#castPoints + 1] = self.torsoPart.CFrame.p - torsoUp
	castPoints[#castPoints + 1] = self.torsoPart.CFrame.p + torsoRight
	castPoints[#castPoints + 1] = self.torsoPart.CFrame.p - torsoRight
	if self.headPart then
		castPoints[#castPoints + 1] = self.headPart.CFrame.p
	end

	local cameraOrientation = self.camera.CFrame - self.camera.CFrame.p
	local torsoPoint = Vector3.new(0,0.5,0) + (self.torsoPart and self.torsoPart.Position or self.humanoidRootPart.Position)
	local radius = 2.5

	-- This loop first calculates points in a circle of radius 2.5 around the torso of the character, in the
	-- plane orthogonal to the camera's lookVector. Each point is then raycast to, to determine if it is within
	-- the free space surrounding the player (not inside anything). Two iterations are done to adjust points that
	-- are inside parts, to try to move them to valid locations that are still on their camera ray, so that the
	-- circle remains circular from the camera's perspective, but does not cast rays into walls or parts that are
	-- behind, below or beside the character and not really obstructing view of the character. This minimizes
	-- the undesirable situation where the character walks up to an exterior wall and it is made invisible even
	-- though it is behind the character.
	for i = 1, SMART_CIRCLE_CASTS do
		local angle = SMART_CIRCLE_INCREMENT * i - 0.5 * math.pi
		local offset = radius * Vector3.new(math.cos(angle), math.sin(angle), 0)
		local circlePoint = torsoPoint + cameraOrientation * offset

		-- Vector from camera to point on the circle being tested
		local vp = circlePoint - self.camera.CFrame.p

		local ray = Ray.new(torsoPoint, circlePoint - torsoPoint)
		local hit, hp, hitNormal = game.Workspace:FindPartOnRayWithIgnoreList(ray, {self.char}, false, false )
		local castPoint = circlePoint

		if hit then
			local hprime = hp + 0.1 * hitNormal.unit -- Slightly offset hit point from the hit surface
			local v0 = hprime - torsoPoint -- Vector from torso to offset hit point

			local perp = (v0:Cross(vp)).unit

			-- Vector from the offset hit point, along the hit surface
			local v1 = (perp:Cross(hitNormal)).unit

			-- Vector from camera to offset hit
			local vprime = (hprime - self.camera.CFrame.p).unit

			-- This dot product checks to see if the vector along the hit surface would hit the correct
			-- side of the invisicam cone, or if it would cross the camera look vector and hit the wrong side
			if ( v0.unit:Dot(-v1) < v0.unit:Dot(vprime)) then
				castPoint = RayIntersection(hprime, v1, circlePoint, vp)

				if castPoint.Magnitude > 0 then
					local ray = Ray.new(hprime, castPoint - hprime)
					local hit, hitPoint, hitNormal = game.Workspace:FindPartOnRayWithIgnoreList(ray, {self.char}, false, false )

					if hit then
						local hprime2 = hitPoint + 0.1 * hitNormal.unit
						castPoint = hprime2
					end
				else
					castPoint = hprime
				end
			else
				castPoint = hprime
			end

			local ray = Ray.new(torsoPoint, (castPoint - torsoPoint))
			local hit, hitPoint, hitNormal = game.Workspace:FindPartOnRayWithIgnoreList(ray, {self.char}, false, false )

			if hit then
				local castPoint2 = hitPoint - 0.1 * (castPoint - torsoPoint).unit
				castPoint = castPoint2
			end
		end

		castPoints[#castPoints + 1] = castPoint
	end
end

function Invisicam:CheckTorsoReference()
	if self.char then
		self.torsoPart = self.char:FindFirstChild("Torso")
		if not self.torsoPart then
			self.torsoPart = self.char:FindFirstChild("UpperTorso")
			if not self.torsoPart then
				self.torsoPart = self.char:FindFirstChild("HumanoidRootPart")
			end
		end

		self.headPart = self.char:FindFirstChild("Head")
	end
end

function Invisicam:CharacterAdded(char, player)
	-- We only want the LocalPlayer's character
	if player~=PlayersService.LocalPlayer then return end

	if self.childAddedConn then
		self.childAddedConn:Disconnect()
		self.childAddedConn = nil
	end
	if self.childRemovedConn then
		self.childRemovedConn:Disconnect()
		self.childRemovedConn = nil
	end

	self.char = char

	self.trackedLimbs = {}
	local function childAdded(child)
		if child:IsA("BasePart") then
			if LIMB_TRACKING_SET[child.Name] then
				self.trackedLimbs[child] = true
			end

			if child.Name == "Torso" or child.Name == "UpperTorso" then
				self.torsoPart = child
			end

			if child.Name == "Head" then
				self.headPart = child
			end
		end
	end

	local function childRemoved(child)
		self.trackedLimbs[child] = nil

		-- If removed/replaced part is 'Torso' or 'UpperTorso' double check that we still have a TorsoPart to use
		self:CheckTorsoReference()
	end

	self.childAddedConn = char.ChildAdded:Connect(childAdded)
	self.childRemovedConn = char.ChildRemoved:Connect(childRemoved)
	for _, child in pairs(self.char:GetChildren()) do
		childAdded(child)
	end
end

function Invisicam:SetMode(newMode)
	AssertTypes(newMode, 'number')
	for _, modeNum in pairs(MODE) do
		if modeNum == newMode then
			self.mode = newMode
			self.behaviorFunction = self.behaviors[self.mode]
			return
		end
	end
	error("Invalid mode number")
end

function Invisicam:GetObscuredParts()
	return self.savedHits
end
