function ViewportWindow.Destroy(h)h.SurfaceGUI:Destroy()end function ViewportWindow.AddSkybox(f,h)if h and h:IsA("\083\107\121")then f.SkyboxFrame:ClearAllChildren();(SkyboxModel(h)).Parent=f.SkyboxFrame end end function ViewportWindow.AddWithLookup(B,h,f,j,H)H=H or{}j=j or B.ViewportFrame f=f or defaultCloneFunc for h,u in next,h do if not u:IsA("\076\117\097\083\111\117\114\099\101\067\111\110\116\097\105\110\101\114")then local h=u.Archivable u.Archivable=true local F=u:Clone()if F then F:ClearAllChildren()f(u,F)F.Parent=j B:AddWithLookup(u:GetChildren(),f,F,H)H[u]=F end u.Archivable=h end end return H end function ViewportWindow.GetPart(h)return h.SurfaceGUI.Adornee end function ViewportWindow.GetSurfaceInfo(h)local f=h.SurfaceGUI.Adornee local j,H=f.CFrame,f.Size local B=-Vector3.FromNormalId(h.SurfaceGUI.Face)local u=math.abs(B.y)==1 and Vector3.new(B.y,0,0)or UNIT_Y local F=CFrame.fromAxisAngle(u,PI2)*B local d=(B:Cross(F)).Unit local V=j*CFrame.fromMatrix((-B*H)/2,F,d,B)local W=Vector3.new((H*F).Magnitude,(H*d).Magnitude,(H*B).Magnitude)return V,W end function ViewportWindow.RenderFrame(H,h,f,j)local B=game.Workspace.CurrentCamera h=h or B.CFrame if not(f and j)then f,j=H:GetSurfaceInfo()end local u=f*Vector3.new(0,j.y/2,0)local F=f*Vector3.new(0,-j.y/2,0)local d=h.LookVector:Cross(f.UpVector)local V=d:Dot(d)>0 and d.Unit or h.RightVector local W=CFrame.fromMatrix(h.p,V,f.UpVector,V:Cross(f.UpVector))local s=W:Inverse()local D=s*F local S=s*u local v=(D*YZ).Unit local l=(S*YZ).Unit local r=math.sign(v.y)*math.acos(v:Dot(UNIT_NZ))local L=math.sign(l.y)*math.acos(l:Dot(UNIT_NZ))local k=2*math.tan(math.rad(B.FieldOfView)/2)local C=math.tan(L)-math.tan(r)local z=C/k local Q=f:VectorToObjectSpace(f.p-h.p)local G=Q*XZ local x=Q*YZ local O=G.Unit:Dot(UNIT_NZ)local J=f:VectorToObjectSpace(h.LookVector)*XZ local P=J.Unit:Dot(G.Unit)/UNIT_NZ:Dot(G.Unit)local c=math.sqrt(1-O*O)/O local A,q=1,j.x/j.y local a=math.sign(Q.x*Q.z)*c local E=(x.y/x.z)*q local N=math.abs((P*z)*q)local T=((f-f.p)*Y_SPIN)*CFrame.new(0,0,0,A,0,0,0,q,0,a,E,N)local Z={T:GetComponents()}local o={}for h=1,#Z,1 do o[h]=math.abs(Z[h])end o=math.max(unpack(o))T=CFrame.new(Z[1],Z[2],Z[3],Z[4]/o,Z[5]/o,Z[6]/o,Z[7]/o,Z[8]/o,Z[9]/o,Z[10]/o,Z[11]/o,Z[12]/o)local R=T+h.p H.Camera.FieldOfView=B.FieldOfView H.Camera.CFrame=R H.Camera.Focus=R*CFrame.new(0,0,(h:PointToObjectSpace(f.p)).z)end
-- Public Methods

function ViewportWindow:Destroy()
	self.SurfaceGUI:Destroy()
end

function ViewportWindow:AddSkybox(skybox)
	if skybox and skybox:IsA("Sky") then
		self.SkyboxFrame:ClearAllChildren()
		SkyboxModel(skybox).Parent = self.SkyboxFrame
	end
end

function ViewportWindow:AddWithLookup(children, cloneFunc, parent, lookup)
	lookup = lookup or {}
	parent = parent or self.ViewportFrame
	cloneFunc = cloneFunc or defaultCloneFunc

	for _, part in next, children do
		if not part:IsA("LuaSourceContainer") then
			local archivable = part.Archivable
			part.Archivable = true

			local clone = part:Clone()
			if clone then
				clone:ClearAllChildren()
				cloneFunc(part, clone)
				clone.Parent = parent

				self:AddWithLookup(part:GetChildren(), cloneFunc, clone, lookup)

				lookup[part] = clone
			end

			part.Archivable = archivable
		end
	end

	return lookup
end

function ViewportWindow:GetPart()
	return self.SurfaceGUI.Adornee
end

function ViewportWindow:GetSurfaceInfo()
	local part = self.SurfaceGUI.Adornee
	local partCF, partSize = part.CFrame, part.Size

	local back = -Vector3.FromNormalId(self.SurfaceGUI.Face)
	local axis = (math.abs(back.y) == 1) and Vector3.new(back.y, 0, 0) or UNIT_Y
	local right = CFrame.fromAxisAngle(axis, PI2) * back
	local top = back:Cross(right).Unit

	local cf = partCF * CFrame.fromMatrix(-back * partSize / 2, right, top, back)
	local size = Vector3.new((partSize * right).Magnitude, (partSize * top).Magnitude, (partSize * back).Magnitude)

	return cf, size
end

function ViewportWindow:RenderFrame(camCF, surfaceCF, surfaceSize)
	local camera = game.Workspace.CurrentCamera

	camCF = camCF or camera.CFrame
	if not (surfaceCF and surfaceSize) then
		surfaceCF, surfaceSize = self:GetSurfaceInfo()
	end

	--

	local tc = surfaceCF * Vector3.new(0, surfaceSize.y / 2, 0)
	local bc = surfaceCF * Vector3.new(0, -surfaceSize.y / 2, 0)

	local cross = camCF.LookVector:Cross(surfaceCF.UpVector)
	local right = cross:Dot(cross) > 0 and cross.Unit or camCF.RightVector

	local levelCamCF = CFrame.fromMatrix(camCF.p, right, surfaceCF.UpVector, right:Cross(surfaceCF.UpVector))
	local levelCamCFInv = levelCamCF:Inverse()

	local csbc = levelCamCFInv * bc
	local cstc = levelCamCFInv * tc
	local v1 = (csbc * YZ).Unit
	local v2 = (cstc * YZ).Unit
	local alpha = math.sign(v1.y) * math.acos(v1:Dot(UNIT_NZ))
	local beta = math.sign(v2.y) * math.acos(v2:Dot(UNIT_NZ))

	local fh = 2 * math.tan(math.rad(camera.FieldOfView) / 2)
	local hPrime = math.tan(beta) - math.tan(alpha)
	local refHeight = hPrime / fh

	--

	local c2p = surfaceCF:VectorToObjectSpace(surfaceCF.p - camCF.p)
	local c2pXZ = c2p * XZ
	local c2pYZ = c2p * YZ

	local dpX = c2pXZ.Unit:Dot(UNIT_NZ)
	local camXZ = (surfaceCF:VectorToObjectSpace(camCF.LookVector) * XZ)

	local scale = camXZ.Unit:Dot(c2pXZ.Unit) / UNIT_NZ:Dot(c2pXZ.Unit)
	local tanArcCos = math.sqrt(1 - dpX * dpX) / dpX

	--

	local w, h = 1, (surfaceSize.x / surfaceSize.y)
	local dx = math.sign(c2p.x * c2p.z) * tanArcCos
	local dy = c2pYZ.y / c2pYZ.z * h
	local d = math.abs(scale * refHeight * h)

	local ncf = (surfaceCF - surfaceCF.p) * Y_SPIN * CFrame.new(0, 0, 0, w, 0, 0, 0, h, 0, dx, dy, d)
	local c = { ncf:GetComponents() }

	local max = {}
	for i = 1, #c do
		max[i] = math.abs(c[i])
	end
	max = math.max(unpack(max))

	ncf = CFrame.new(
		c[1],
		c[2],
		c[3],
		c[4] / max,
		c[5] / max,
		c[6] / max,
		c[7] / max,
		c[8] / max,
		c[9] / max,
		c[10] / max,
		c[11] / max,
		c[12] / max
	)

	--

	--[[
	-- can set w and h to 1 and use this as an alternative scaling method, but I find the above is better
	local ratioXY = (surfaceSize.x / surfaceSize.y)
	local ratioYX = (surfaceSize.y / surfaceSize.x)
	
	if (ratioXY > ratioYX) then
		self.SurfaceGUI.CanvasSize = Vector2.new(1024, 1024 * ratioYX)
	else
		self.SurfaceGUI.CanvasSize = Vector2.new(1024 * ratioXY, 1024)
	end
	--]]

	local ncamCF = ncf + camCF.p
	self.Camera.FieldOfView = camera.FieldOfView
	self.Camera.CFrame = ncamCF
	self.Camera.Focus = ncamCF * CFrame.new(0, 0, camCF:PointToObjectSpace(surfaceCF.p).z)
end
