mirror of
https://github.com/imgfloat/server.git
synced 2026-02-05 19:49:26 +00:00
99 lines
3.7 KiB
JavaScript
99 lines
3.7 KiB
JavaScript
function init(context, state) {
|
|
const asset = Array.isArray(context.assets) ? context.assets[0] : null;
|
|
const THREE = globalThis.THREE;
|
|
if (!asset?.url || !THREE) {
|
|
state.error = "Three.js dependencies are unavailable or no model attachment was found.";
|
|
return;
|
|
}
|
|
|
|
const glCanvas = new OffscreenCanvas(context.width || 1, context.height || 1);
|
|
const renderer = new THREE.WebGLRenderer({ canvas: glCanvas, alpha: true, antialias: true });
|
|
renderer.setPixelRatio(1);
|
|
renderer.setClearColor(0x000000, 0);
|
|
|
|
const scene = new THREE.Scene();
|
|
const camera = new THREE.PerspectiveCamera(35, 1, 0.1, 100);
|
|
const ambient = new THREE.AmbientLight(0xffffff, 0.85);
|
|
const directional = new THREE.DirectionalLight(0xffffff, 0.65);
|
|
directional.position.set(1, 1, 1);
|
|
scene.add(ambient);
|
|
scene.add(directional);
|
|
|
|
const lowerUrl = asset.url.toLowerCase();
|
|
const isObj = asset.mediaType === "model/obj" || lowerUrl.endsWith(".obj");
|
|
const loader = isObj && typeof THREE.OBJLoader === "function" ? new THREE.OBJLoader() : new THREE.GLTFLoader();
|
|
|
|
fetch(asset.url)
|
|
.then((response) => {
|
|
if (!response.ok) {
|
|
throw new Error("Unable to fetch model");
|
|
}
|
|
return isObj ? response.text() : response.arrayBuffer();
|
|
})
|
|
.then((data) => {
|
|
if (isObj) {
|
|
return loader.parse(data);
|
|
}
|
|
return new Promise((resolve, reject) => {
|
|
loader.parse(data, "", resolve, reject);
|
|
});
|
|
})
|
|
.then((result) => {
|
|
const model = isObj ? result : result.scene || result.scenes?.[0];
|
|
if (!model) {
|
|
state.error = "Failed to read model data.";
|
|
return;
|
|
}
|
|
const box = new THREE.Box3().setFromObject(model);
|
|
const size = box.getSize(new THREE.Vector3());
|
|
const center = box.getCenter(new THREE.Vector3());
|
|
model.position.sub(center);
|
|
const maxDim = Math.max(size.x, size.y, size.z) || 1;
|
|
const scale = 1.5 / maxDim;
|
|
model.scale.setScalar(scale);
|
|
scene.add(model);
|
|
const distance = maxDim * 2.2;
|
|
camera.position.set(0, 0, distance);
|
|
camera.near = Math.max(0.01, distance / 100);
|
|
camera.far = distance * 100;
|
|
camera.updateProjectionMatrix();
|
|
state.model = model;
|
|
})
|
|
.catch((error) => {
|
|
state.error = error?.message || "Failed to load model.";
|
|
});
|
|
|
|
state.glCanvas = glCanvas;
|
|
state.renderer = renderer;
|
|
state.scene = scene;
|
|
state.camera = camera;
|
|
state.rotation = 0;
|
|
}
|
|
|
|
function tick(context, state) {
|
|
const { ctx, width, height, deltaMs } = context;
|
|
if (!ctx || !state.renderer || !state.scene || !state.camera || !state.glCanvas) {
|
|
return;
|
|
}
|
|
|
|
const nextWidth = Math.max(1, Math.round(width || 1));
|
|
const nextHeight = Math.max(1, Math.round(height || 1));
|
|
if (state.glCanvas.width !== nextWidth || state.glCanvas.height !== nextHeight) {
|
|
state.glCanvas.width = nextWidth;
|
|
state.glCanvas.height = nextHeight;
|
|
state.renderer.setSize(nextWidth, nextHeight, false);
|
|
state.camera.aspect = nextWidth / nextHeight;
|
|
state.camera.updateProjectionMatrix();
|
|
}
|
|
|
|
ctx.clearRect(0, 0, nextWidth, nextHeight);
|
|
|
|
if (state.model) {
|
|
state.rotation = (state.rotation + (deltaMs || 0) * 0.0008) % (Math.PI * 2);
|
|
state.model.rotation.y = state.rotation;
|
|
}
|
|
|
|
state.renderer.render(state.scene, state.camera);
|
|
ctx.drawImage(state.glCanvas, 0, 0, nextWidth, nextHeight);
|
|
}
|