function Invisicam.LimbBehavior(W,b)for W,X in pairs(W.trackedLimbs)do b[#b+1]=W.Position end end function Invisicam.MoveBehavior(W,b)for X=1,MOVE_CASTS,1 do local A,y=W.humanoidRootPart.Position,W.humanoidRootPart.Velocity local T=(Vector3.new(y.X,0,y.Z)).Magnitude/2 local m=((X-1)*W.humanoidRootPart.CFrame.lookVector)*T b[#b+1]=A+m end end function Invisicam.CornerBehavior(W,b)local X=W.humanoidRootPart.CFrame local A=X.p local y=X-A local T=W.char:GetExtentsSize()/2 b[#b+1]=A for W=1,#CORNER_FACTORS,1 do b[#b+1]=A+y*(T*CORNER_FACTORS[W])end end function Invisicam.CircleBehavior(W,b)local X=nil if W.mode==MODE.CIRCLE1 then X=W.humanoidRootPart.CFrame else local b=W.camera.CoordinateFrame X=(b-b.p)+W.humanoidRootPart.Position end b[#b+1]=X.p for W=0,CIRCLE_CASTS-1,1 do local A=((2*math.pi)/CIRCLE_CASTS)*W local y=3*Vector3.new(math.cos(A),math.sin(A),0)b[#b+1]=X*y end end function Invisicam.LimbMoveBehavior(W,b)W:LimbBehavior(b)W:MoveBehavior(b)end function Invisicam.CharacterOutlineBehavior(W,b)local X=W.torsoPart.CFrame.upVector.unit local A=W.torsoPart.CFrame.rightVector.unit b[#b+1]=W.torsoPart.CFrame.p b[#b+1]=W.torsoPart.CFrame.p+X b[#b+1]=W.torsoPart.CFrame.p-X b[#b+1]=W.torsoPart.CFrame.p+A b[#b+1]=W.torsoPart.CFrame.p-A if W.headPart then b[#b+1]=W.headPart.CFrame.p end local y=CFrame.new(ZERO_VECTOR3,Vector3.new(W.camera.CoordinateFrame.lookVector.X,0,W.camera.CoordinateFrame.lookVector.Z))local T=W.torsoPart and W.torsoPart.Position or W.humanoidRootPart.Position local m={W.torsoPart}if W.headPart then m[#m+1]=W.headPart end for W=1,CHAR_OUTLINE_CASTS,1 do local X=((2*math.pi)*W)/CHAR_OUTLINE_CASTS local A=y*(3*Vector3.new(math.cos(X),math.sin(X),0))A=Vector3.new(A.X,math.max(A.Y,-2.25),A.Z)local u=Ray.new(T+A,-3*A)local d,l=game.Workspace:FindPartOnRayWithWhitelist(u,m,false,false)if d then b[#b+1]=l+.2*(T-l).unit end end end function Invisicam.SmartCircleBehavior(W,b)local X=W.torsoPart.CFrame.upVector.unit local A=W.torsoPart.CFrame.rightVector.unit b[#b+1]=W.torsoPart.CFrame.p b[#b+1]=W.torsoPart.CFrame.p+X b[#b+1]=W.torsoPart.CFrame.p-X b[#b+1]=W.torsoPart.CFrame.p+A b[#b+1]=W.torsoPart.CFrame.p-A if W.headPart then b[#b+1]=W.headPart.CFrame.p end local y=W.camera.CFrame-W.camera.CFrame.p local T=Vector3.new(0,.5,0)+(W.torsoPart and W.torsoPart.Position or W.humanoidRootPart.Position)local m=2.5 for X=1,SMART_CIRCLE_CASTS,1 do local A=SMART_CIRCLE_INCREMENT*X-.5*math.pi local u=m*Vector3.new(math.cos(A),math.sin(A),0)local d=T+y*u local l=d-W.camera.CFrame.p local Y=Ray.new(T,d-T)local F,K,o=game.Workspace:FindPartOnRayWithIgnoreList(Y,{W.char},false,false)local h=d if F then local b=K+.1*o.unit local X=b-T local A=X.magnitude local y=(X:Cross(l)).unit local m=(y:Cross(o)).unit local u=(b-W.camera.CFrame.p).unit if X.unit:Dot(-m)<X.unit:Dot(u)then h=RayIntersection(b,m,d,l)if h.Magnitude>0 then local X=Ray.new(b,h-b)local A,y,T=game.Workspace:FindPartOnRayWithIgnoreList(X,{W.char},false,false)if A then local b=y+.1*T.unit h=b end else h=b end else h=b end local Y=Ray.new(T,h-T)local F,D,N=game.Workspace:FindPartOnRayWithIgnoreList(Y,{W.char},false,false)if F then local b=D-.1*(h-T).unit h=b end end b[#b+1]=h end end function Invisicam.CheckTorsoReference(b)if b.char then b.torsoPart=b.char:FindFirstChild("\084\111\114\115\111")if not b.torsoPart then b.torsoPart=b.char:FindFirstChild("\085\112\112\101\114\084\111\114\115\111")if not b.torsoPart then b.torsoPart=b.char:FindFirstChild("\072\117\109\097\110\111\105\100\082\111\111\116\080\097\114\116")end end b.headPart=b.char:FindFirstChild("\072\101\097\100")end end function Invisicam.CharacterAdded(X,b,W)if W~=PlayersService.LocalPlayer then return end if X.childAddedConn then X.childAddedConn:Disconnect()X.childAddedConn=nil end if X.childRemovedConn then X.childRemovedConn:Disconnect()X.childRemovedConn=nil end X.char=b X.trackedLimbs={}local function A(b)if b:IsA("\066\097\115\101\080\097\114\116")then if LIMB_TRACKING_SET[b.Name]then X.trackedLimbs[b]=true end if b.Name=="\084\111\114\115\111"or b.Name=="\085\112\112\101\114\084\111\114\115\111"then X.torsoPart=b end if b.Name=="\072\101\097\100"then X.headPart=b end end end local function y(b)X.trackedLimbs[b]=nil X:CheckTorsoReference()end X.childAddedConn=b.ChildAdded:Connect(A)X.childRemovedConn=b.ChildRemoved:Connect(y)for b,W in pairs(X.char:GetChildren())do A(W)end end function Invisicam.SetMode(W,b)AssertTypes(b,"\110\117\109\098\101\114")for X,A in pairs(MODE)do if A==b then W.mode=b W.behaviorFunction=W.behaviors[W.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(b)return b.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 = nil
	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 d0 = v0.magnitude
			
			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 modeName, 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
