|
<script lang="ts"> |
|
import { onMount } from "svelte"; |
|
import * as BABYLON from "babylonjs"; |
|
import * as BABYLON_LOADERS from "babylonjs-loaders"; |
|
import type { FileData } from "@gradio/client"; |
|
import { resolve_wasm_src } from "@gradio/wasm/svelte"; |
|
|
|
$: { |
|
if ( |
|
BABYLON_LOADERS.OBJFileLoader != undefined && |
|
!BABYLON_LOADERS.OBJFileLoader.IMPORT_VERTEX_COLORS |
|
) { |
|
BABYLON_LOADERS.OBJFileLoader.IMPORT_VERTEX_COLORS = true; |
|
} |
|
} |
|
|
|
export let value: FileData; |
|
export let display_mode: "solid" | "point_cloud" | "wireframe"; |
|
export let clear_color: [number, number, number, number]; |
|
export let camera_position: [number | null, number | null, number | null]; |
|
export let zoom_speed: number; |
|
export let pan_speed: number; |
|
|
|
$: url = value.url; |
|
|
|
|
|
export let resolved_url: typeof url = undefined; |
|
|
|
|
|
|
|
let latest_url: typeof url; |
|
$: { |
|
|
|
|
|
|
|
|
|
|
|
resolved_url = url; |
|
|
|
if (url) { |
|
latest_url = url; |
|
const resolving_url = url; |
|
resolve_wasm_src(url).then((resolved) => { |
|
if (latest_url === resolving_url) { |
|
resolved_url = resolved ?? undefined; |
|
} else { |
|
resolved && URL.revokeObjectURL(resolved); |
|
} |
|
}); |
|
} |
|
} |
|
|
|
|
|
let canvas: HTMLCanvasElement; |
|
let scene: BABYLON.Scene; |
|
let engine: BABYLON.Engine; |
|
let point_cloud_system: BABYLON.PointsCloudSystem | null = null; |
|
let mounted = false; |
|
|
|
onMount(() => { |
|
|
|
engine = new BABYLON.Engine(canvas, true); |
|
scene = new BABYLON.Scene(engine); |
|
|
|
scene.createDefaultCameraOrLight(); |
|
scene.useRightHandedSystem = true; |
|
scene.clearColor = scene.clearColor = new BABYLON.Color4(...clear_color); |
|
|
|
engine.runRenderLoop(() => { |
|
scene.render(); |
|
}); |
|
|
|
function onWindowResize(): void { |
|
engine.resize(); |
|
} |
|
window.addEventListener("resize", onWindowResize); |
|
|
|
mounted = true; |
|
|
|
return () => { |
|
scene.dispose(); |
|
engine.dispose(); |
|
window.removeEventListener("resize", onWindowResize); |
|
}; |
|
}); |
|
|
|
$: mounted && load_model(resolved_url); |
|
|
|
function load_model(url: string | undefined): void { |
|
if (scene) { |
|
|
|
scene.meshes.forEach((mesh) => { |
|
mesh.dispose(); |
|
}); |
|
|
|
if (point_cloud_system) { |
|
point_cloud_system.dispose(); |
|
point_cloud_system = null; |
|
} |
|
|
|
|
|
if (url) { |
|
BABYLON.SceneLoader.ShowLoadingScreen = false; |
|
BABYLON.SceneLoader.Append( |
|
url, |
|
"", |
|
scene, |
|
() => { |
|
if (display_mode === "point_cloud") { |
|
create_point_cloud(scene); |
|
} else if (display_mode === "wireframe") { |
|
create_wireframe(scene); |
|
} else { |
|
create_camera(scene, camera_position, zoom_speed, pan_speed); |
|
} |
|
}, |
|
undefined, |
|
undefined, |
|
"." + value.path.split(".").pop() |
|
); |
|
} |
|
} |
|
} |
|
|
|
function create_camera( |
|
scene: BABYLON.Scene, |
|
camera_position: [number | null, number | null, number | null], |
|
zoom_speed: number, |
|
pan_speed: number |
|
): void { |
|
scene.createDefaultCamera(true, true, true); |
|
var helperCamera = scene.activeCamera! as BABYLON.ArcRotateCamera; |
|
if (camera_position[0] !== null) { |
|
helperCamera.alpha = BABYLON.Tools.ToRadians(camera_position[0]); |
|
} |
|
if (camera_position[1] !== null) { |
|
helperCamera.beta = BABYLON.Tools.ToRadians(camera_position[1]); |
|
} |
|
if (camera_position[2] !== null) { |
|
helperCamera.radius = camera_position[2]; |
|
} |
|
helperCamera.lowerRadiusLimit = 0.1; |
|
const updateCameraSensibility = (): void => { |
|
helperCamera.wheelPrecision = 250 / (helperCamera.radius * zoom_speed); |
|
helperCamera.panningSensibility = |
|
(10000 * pan_speed) / helperCamera.radius; |
|
}; |
|
updateCameraSensibility(); |
|
helperCamera.attachControl(true); |
|
helperCamera.onAfterCheckInputsObservable.add(updateCameraSensibility); |
|
} |
|
|
|
export function reset_camera_position( |
|
camera_position: [number | null, number | null, number | null], |
|
zoom_speed: number, |
|
pan_speed: number |
|
): void { |
|
if (scene) { |
|
scene.removeCamera(scene.activeCamera!); |
|
create_camera(scene, camera_position, zoom_speed, pan_speed); |
|
} |
|
} |
|
|
|
function create_point_cloud(scene: BABYLON.Scene): void { |
|
const meshes = scene.meshes; |
|
const pointPositions: BABYLON.Vector3[] = []; |
|
|
|
meshes.forEach((mesh) => { |
|
if (mesh instanceof BABYLON.Mesh) { |
|
const positions = mesh.getVerticesData( |
|
BABYLON.VertexBuffer.PositionKind |
|
); |
|
if (positions) { |
|
for (let i = 0; i < positions.length; i += 3) { |
|
pointPositions.push( |
|
new BABYLON.Vector3( |
|
positions[i], |
|
positions[i + 1], |
|
positions[i + 2] |
|
) |
|
); |
|
} |
|
} |
|
mesh.setEnabled(false); |
|
} |
|
}); |
|
|
|
point_cloud_system = new BABYLON.PointsCloudSystem( |
|
"point_cloud_system", |
|
1, |
|
scene |
|
); |
|
|
|
point_cloud_system.addPoints( |
|
pointPositions.length, |
|
(particle: BABYLON.CloudPoint, i: number) => { |
|
particle.position = pointPositions[i]; |
|
particle.color = new BABYLON.Color4( |
|
Math.random(), |
|
Math.random(), |
|
Math.random(), |
|
1.0 |
|
); |
|
} |
|
); |
|
|
|
point_cloud_system.buildMeshAsync().then((mesh) => { |
|
mesh.alwaysSelectAsActiveMesh = true; |
|
create_camera(scene, camera_position, zoom_speed, pan_speed); |
|
}); |
|
} |
|
|
|
function create_wireframe(scene: BABYLON.Scene): void { |
|
scene.meshes.forEach((mesh) => { |
|
if (mesh instanceof BABYLON.Mesh) { |
|
mesh.material = new BABYLON.StandardMaterial( |
|
"wireframeMaterial", |
|
scene |
|
); |
|
mesh.material.wireframe = true; |
|
} |
|
create_camera(scene, camera_position, zoom_speed, pan_speed); |
|
}); |
|
} |
|
</script> |
|
|
|
<canvas bind:this={canvas}></canvas> |
|
|