|
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) { |
|
|
|
await BABYLON.SceneLoader.AppendAsync("", url, this.scene, (event) => { |
|
const progress = event.loaded / event.total; |
|
loadingBarCallback?.(progress); |
|
}); |
|
|
|
|
|
this.scene.cameras.forEach((camera) => { |
|
if (camera !== this.camera) { |
|
camera.dispose(); |
|
} |
|
}); |
|
this.scene.lights.forEach((light) => { |
|
light.dispose(); |
|
}); |
|
|
|
|
|
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); |
|
|
|
|
|
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); |
|
|
|
|
|
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<string | null> { |
|
if (!this.engine || !this.camera) return null; |
|
const cachedColor = this.scene.clearColor; |
|
this.scene.clearColor = BABYLON.Color4.FromHexString("#00000000"); |
|
let data = await new Promise<string>((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 }, |
|
]; |
|
} |
|
} |
|
|