-- 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