import { Video as BaseVideo, type FrameTime, type InitConfig } from '$/lib/Player/Video'; import * as THREE from 'three'; import type { OrbitControls } from 'three/examples/jsm/Addons.js'; export const OnceCell = (create: () => T) => { let called = false, cached = null as unknown as T return () => { if (called) return cached else { cached = create(); called = true; return cached } } } export default abstract class ThreeVideo extends BaseVideo { protected abstract ctx: CanvasRenderingContext2D protected scene!: THREE.Scene; protected camera!: THREE.PerspectiveCamera; protected renderer!: THREE.WebGLRenderer; protected threeCanvas!: HTMLCanvasElement; protected orbitControls?: OrbitControls; public async init(_config: InitConfig): Promise { this.scene = new THREE.Scene(); this.camera = new THREE.PerspectiveCamera(75, this.w / this.h, 0.1, 1000); const canvas = this.threeCanvas = document.createElement('canvas'); canvas.width = this.canvas.width canvas.height = this.canvas.height canvas.style.opacity = "0" canvas.style.position = "fixed"; canvas.style.top = "1000vh" canvas.style.left = "1000vw" document.body.appendChild(canvas) this.renderer = new THREE.WebGLRenderer({ canvas: canvas, alpha: true, powerPreference: 'high-performance', }); this.renderer.setSize(this.w, this.h); this.renderer.setAnimationLoop(() => this.renderScene()); } public renderScene(ctx?: CanvasRenderingContext2D) { if (this.orbitControls) this.orbitControls.update() this.renderer.render(this.scene, this.camera) if (ctx) ctx.drawImage(this.threeCanvas, 0, 0) } public cleanup(): void { this.renderer.dispose() this.threeCanvas.remove() } }