text: import {
  	CubeTextureLoader,
  	FogExp2,
  	LoadingManager,
  	PerspectiveCamera,
  	Scene,
  	sRGBEncoding,
  	WebGLRenderer
  } from "three";
  
  import {
  	BlendFunction,
  	GridEffect,
  	EffectComposer,
  	EffectPass,
  	RenderPass
  } from "postprocessing";
  
  import { Pane } from "tweakpane";
  import { SpatialControls } from "spatial-controls";
  import { calculateVerticalFoV, FPSMeter } from "../utils";
  import * as Domain from "../objects/Domain";
  
  function load() {
  
  	const assets = new Map();
  	const loadingManager = new LoadingManager();
  	const cubeTextureLoader = new CubeTextureLoader(loadingManager);
  
  	const path = document.baseURI + "img/textures/skies/sunset/";
  	const format = ".png";
  	const urls = [
  		path + "px" + format, path + "nx" + format,
  		path + "py" + format, path + "ny" + format,
  		path + "pz" + format, path + "nz" + format
  	];
  
  	return new Promise((resolve, reject) => {
  
  		loadingManager.onLoad = () => resolve(assets);
  		loadingManager.onError = (url) => reject(new Error(`Failed to load ${url}`));
  
  		cubeTextureLoader.load(urls, (t) => {
  
  			t.encoding = sRGBEncoding;
  			assets.set("sky", t);
  
  		});
  
  	});
  
  }
  
  window.addEventListener("load", () => load().then((assets) => {
  
  	// Renderer
  
  	const renderer = new WebGLRenderer({
  		powerPreference: "high-performance",
  		antialias: false,
  		stencil: false,
  		depth: false
  	});
  
  	renderer.debug.checkShaderErrors = (window.location.hostname === "localhost");
  	renderer.physicallyCorrectLights = true;
  	renderer.outputEncoding = sRGBEncoding;
  
  	const container = document.querySelector(".viewport");
  	container.prepend(renderer.domElement);
  
  	// Camera & Controls
  
  	const camera = new PerspectiveCamera();
  	const controls = new SpatialControls(camera.position, camera.quaternion, renderer.domElement);
  	const settings = controls.settings;
  	settings.rotation.setSensitivity(2.2);
  	settings.rotation.setDamping(0.05);
  	settings.translation.setDamping(0.1);
  	controls.setPosition(0, 0, 1);
  	controls.lookAt(0, 0, 0);
  
  	// Scene, Lights, Objects
  
  	const scene = new Scene();
  	scene.fog = new FogExp2(0x0a0809, 0.06);
  	scene.background = assets.get("sky");
  	scene.add(Domain.createLights());
  	scene.add(Domain.createEnvironment(scene.background));
  	scene.add(Domain.createActors(scene.background));
  
  	// Post Processing
  
  	const context = renderer.getContext();
  	const composer = new EffectComposer(renderer, {
  		multisampling: Math.min(4, context.getParameter(context.MAX_SAMPLES))
  	});
  
  	const effect = new GridEffect({ scale: 2.0 });
  	composer.addPass(new RenderPass(scene, camera));
  	composer.addPass(new EffectPass(camera, effect));
  
  	// Settings
  
  	const fpsMeter = new FPSMeter();
  	const pane = new Pane({ container: container.querySelector(".tp") });
  	pane.addMonitor(fpsMeter, "fps", { label: "FPS" });
  
  	const folder = pane.addFolder({ title: "Settings" });
  	folder.addInput(effect, "lineWidth", { min: 0, max: 1, step: 1e-3 });
  	folder.addInput(effect, "scale", { min: 0, max: 2, step: 1e-3 });
  	folder.addInput(effect.blendMode.opacity, "value", { label: "opacity", min: 0, max: 1, step: 0.01 });
  	folder.addInput(effect.blendMode, "blendFunction", { options: BlendFunction });
  
  	// Resize Handler
  
  	function onResize() {
  
  		const width = container.clientWidth, height = container.clientHeight;
  		camera.aspect = width / height;
  		camera.fov = calculateVerticalFoV(90, Math.max(camera.aspect, 16 / 9));
  		camera.updateProjectionMatrix();
  		composer.setSize(width, height);
  
  	}
  
  	window.addEventListener("resize", onResize);
  	onResize();
  
  	// Render Loop
  
  	requestAnimationFrame(function render(timestamp) {
  
  		fpsMeter.update(timestamp);
  		controls.update(timestamp);
  		composer.render();
  		requestAnimationFrame(render);
  
  	});
  
  }));
  