import type { IViewer } from "./IViewer"; import * as BABYLON from "@babylonjs/core"; import "@babylonjs/loaders/glTF"; import "@babylonjs/loaders/OBJ"; export class BabylonViewer implements IViewer { canvas: HTMLCanvasElement; engine: BABYLON.Engine; scene: BABYLON.Scene; camera: BABYLON.ArcRotateCamera; triangleCount: number = 0; constructor(canvas: HTMLCanvasElement) { this.canvas = canvas; this.engine = new BABYLON.Engine(canvas, true); this.scene = new BABYLON.Scene(this.engine); this.scene.clearColor = BABYLON.Color4.FromHexString("#1A1B1EFF"); this.camera = new BABYLON.ArcRotateCamera( "camera", Math.PI / 3, Math.PI / 3, 30, BABYLON.Vector3.Zero(), this.scene ); this.camera.angularSensibilityY = 1000; this.camera.panningSensibility = 500; this.camera.wheelPrecision = 5; this.camera.inertia = 0.9; this.camera.panningInertia = 0.9; this.camera.lowerRadiusLimit = 3; this.camera.upperRadiusLimit = 100; this.camera.setTarget(BABYLON.Vector3.Zero()); this.camera.attachControl(this.canvas, true); this.camera.onAfterCheckInputsObservable.add(() => { this.camera.wheelPrecision = 150 / this.camera.radius; this.camera.panningSensibility = 10000 / this.camera.radius; }); this.handleResize = this.handleResize.bind(this); window.addEventListener("resize", this.handleResize); } handleResize() { this.engine.resize(); } async loadScene(url: string, loadingBarCallback?: (progress: number) => void) { // Load scene await BABYLON.SceneLoader.AppendAsync("", url, this.scene, (event) => { const progress = event.loaded / event.total; loadingBarCallback?.(progress); }); // Dispose of all cameras and lights this.scene.cameras.forEach((camera) => { if (camera !== this.camera) { camera.dispose(); } }); this.scene.lights.forEach((light) => { light.dispose(); }); // Add lights const light = new BABYLON.HemisphericLight("hemi", new BABYLON.Vector3(0, 1, 0), this.scene); light.intensity = 1; light.diffuse = new BABYLON.Color3(1, 1, 1); light.groundColor = new BABYLON.Color3(0.3, 0.3, 0.3); const sun = new BABYLON.DirectionalLight("sun", new BABYLON.Vector3(-0.5, -1, -0.5), this.scene); sun.intensity = 2; sun.diffuse = new BABYLON.Color3(1, 1, 1); // Center and scale model const parentNode = new BABYLON.TransformNode("parent", this.scene); const standardSize = 10; let scaleFactor = 1; let center = BABYLON.Vector3.Zero(); if (this.scene.meshes.length > 0) { let bounds = this.scene.meshes[0].getBoundingInfo().boundingBox; let min = bounds.minimumWorld; let max = bounds.maximumWorld; for (let i = 1; i < this.scene.meshes.length; i++) { bounds = this.scene.meshes[i].getBoundingInfo().boundingBox; min = BABYLON.Vector3.Minimize(min, bounds.minimumWorld); max = BABYLON.Vector3.Maximize(max, bounds.maximumWorld); } const extent = max.subtract(min).scale(0.5); const size = extent.length(); center = BABYLON.Vector3.Center(min, max); scaleFactor = standardSize / size; } this.triangleCount = 0; this.scene.meshes.forEach((mesh) => { mesh.setParent(parentNode); if (mesh.getTotalVertices() > 0) { this.triangleCount += mesh.getTotalIndices() / 3; } }); parentNode.position = center.scale(-1 * scaleFactor); parentNode.scaling.scaleInPlace(scaleFactor); // Run render loop this.engine.runRenderLoop(() => { this.scene.render(); }); } dispose() { if (this.scene) { this.scene.dispose(); } if (this.engine) { this.engine.dispose(); } window.removeEventListener("resize", this.handleResize); } async capture(): Promise { if (!this.engine || !this.camera) return null; const cachedColor = this.scene.clearColor; this.scene.clearColor = BABYLON.Color4.FromHexString("#00000000"); let data = await new Promise((resolve) => { BABYLON.Tools.CreateScreenshotUsingRenderTarget(this.engine, this.camera, 512, (result) => { resolve(result); }); }); this.scene.clearColor = cachedColor; return data; } setRenderMode(mode: string) { this.scene.forceWireframe = mode === "wireframe"; } getStats(): { name: string; value: any }[] { const fps = this.engine.getFps().toFixed(); const triangleCount = this.triangleCount.toLocaleString(); return [ { name: "FPS", value: fps }, { name: "Triangles", value: triangleCount }, ]; } }