aboutsummaryrefslogtreecommitdiffhomepage
path: root/public/bluebey-studio/src/glbExport.js
diff options
context:
space:
mode:
Diffstat (limited to 'public/bluebey-studio/src/glbExport.js')
-rw-r--r--public/bluebey-studio/src/glbExport.js148
1 files changed, 0 insertions, 148 deletions
diff --git a/public/bluebey-studio/src/glbExport.js b/public/bluebey-studio/src/glbExport.js
deleted file mode 100644
index 6e3e997..0000000
--- a/public/bluebey-studio/src/glbExport.js
+++ /dev/null
@@ -1,148 +0,0 @@
-import * as THREE from 'three';
-import { GLTFExporter } from 'three/addons/exporters/GLTFExporter.js';
-
-/**
- * Hand the posed character back as a 3D file.
- *
- * WHY: a PNG or a WebM only travels as pixels. Being able to take the pose, the
- * expression and the props into Blender (or embed them elsewhere as a model)
- * turns the studio into a front end for the character instead of a picture
- * maker, and it costs nothing: what is on screen already *is* a glTF scene, so
- * the only job here is to hand the live objects to three's exporter with the
- * right options and a Blob around the result.
- *
- * The caller passes exactly what it wants in the file - the character root and
- * any props - so no lights, helpers or ground ever enter it. The inverted-hull
- * outlines are the one wrinkle: they are visible (they are part of the look)
- * but they are duplicates of the body geometry, so exporting them would double
- * the file and leave a black shell around the character. `needsTemporaryHide`
- * decides what to hide for the duration, and `exportGLB` restores it after.
- */
-
-const GLTF_BINARY_TYPE = 'model/gltf-binary';
-const GLTF_JSON_TYPE = 'model/gltf+json';
-/** The largest texture a GPU can be assumed to handle everywhere. */
-const MAX_TEXTURE_SIZE = 4096;
-
-/**
- * True for objects the exporter must skip: the inverted-hull outlines (named
- * `...:outline`), anything the app tagged as a helper or as excluded from the
- * export, and anything already hidden. The name check is the important one: an
- * outline is visible on screen, so `onlyVisible` alone would not drop it.
- *
- * @param {THREE.Object3D} object
- * @returns {boolean}
- */
-export function needsTemporaryHide(object) {
- if (!object) return false;
- if (typeof object.name === 'string' && object.name.endsWith(':outline')) return true;
- if (object.userData?.isHelper === true) return true;
- if (object.userData?.excludeFromExport === true) return true;
- return object.visible === false;
-}
-
-/** Triangles in a geometry, ignoring draw ranges (the export does too). */
-function triangleCount(geometry) {
- if (!geometry) return 0;
- const index = geometry.index;
- const count = index ? index.count : geometry.attributes?.position?.count ?? 0;
- return Math.floor(count / 3);
-}
-
-/**
- * Count what `exportGLB` will write, for the summary the app shows first.
- *
- * Walks the same pruned tree the exporter sees, so hiding an outline or a
- * helper is reflected in the numbers. A `SkinnedMesh` is also a `Mesh`, so it
- * counts in both `meshes` and `skinnedMeshes`; a material shared by several
- * meshes counts once, and every texture a counted material refers to counts
- * once.
- *
- * @param {THREE.Object3D[]} objects
- * @returns {{ meshes: number, skinnedMeshes: number, materials: number, textures: number, triangles: number }}
- */
-export function describeScene(objects) {
- const materials = new Set();
- const textures = new Set();
- let meshes = 0;
- let skinnedMeshes = 0;
- let triangles = 0;
-
- const visit = (object) => {
- if (!object || needsTemporaryHide(object)) return;
- if (object.isMesh) {
- meshes += 1;
- if (object.isSkinnedMesh) skinnedMeshes += 1;
- const list = Array.isArray(object.material) ? object.material : [object.material];
- for (const material of list) {
- if (!material) continue;
- materials.add(material);
- for (const value of Object.values(material)) {
- if (value && value.isTexture) textures.add(value);
- }
- }
- triangles += triangleCount(object.geometry);
- }
- for (const child of object.children ?? []) visit(child);
- };
-
- for (const root of Array.isArray(objects) ? objects : []) visit(root);
- return { meshes, skinnedMeshes, materials: materials.size, textures: textures.size, triangles };
-}
-
-/**
- * Hide every object the file must not contain, remembering what to restore.
- * Stops at the first excluded ancestor: the exporter skips a hidden subtree, so
- * there is no need to walk inside one.
- */
-function hideExcluded(object, hidden) {
- if (needsTemporaryHide(object)) {
- hidden.push({ object, visible: object.visible });
- object.visible = false;
- return;
- }
- for (const child of object.children ?? []) hideExcluded(child, hidden);
-}
-
-/**
- * Export `objects` as a GLB (or a `.gltf` JSON) Blob.
- *
- * @param {THREE.Object3D[]} objects the character root and any props
- * @param {{ binary?: boolean, name?: string }} [options]
- * @returns {Promise<Blob>} `model/gltf-binary`, or `model/gltf+json` when not binary
- */
-export async function exportGLB(objects, { binary = true, name = 'bluebey' } = {}) {
- const roots = (Array.isArray(objects) ? objects : []).filter(Boolean);
- if (roots.length === 0) {
- throw new Error('exportGLB: objects is empty, there is nothing to export');
- }
- if (describeScene(roots).meshes === 0) {
- throw new Error('exportGLB: objects contain no visible meshes to export');
- }
-
- const hidden = [];
- try {
- for (const root of roots) hideExcluded(root, hidden);
-
- // The exporter names a bare array of objects "AuxScene". Wrapping them in a
- // named Scene keeps the caller's objects where they are (we push straight
- // into `children`, exactly as the exporter does) and gives the file a real
- // scene name.
- const scene = new THREE.Scene();
- scene.name = name;
- for (const root of roots) scene.children.push(root);
-
- const exporter = new GLTFExporter();
- const result = await exporter.parseAsync(scene, {
- binary,
- onlyVisible: true,
- truncateDrawRange: false,
- maxTextureSize: MAX_TEXTURE_SIZE,
- });
-
- if (binary) return new Blob([result], { type: GLTF_BINARY_TYPE });
- return new Blob([JSON.stringify(result)], { type: GLTF_JSON_TYPE });
- } finally {
- for (const { object, visible } of hidden) object.visible = visible;
- }
-}