diff options
| author | Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> | 2026-10-07 23:09:40 +0900 |
|---|---|---|
| committer | Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> | 2026-10-07 23:09:40 +0900 |
| commit | bc2821adb11a30244fc4663f4fafb756877d9508 (patch) | |
| tree | fea8d30d2eb1e67f895097dbf7ec4d152361243e /public/bluebey-studio/src/glbExport.js | |
| parent | 0d50c5ede0812ba7b67b775c9cd0bf52d7b65e02 (diff) | |
bluebey-studio: public/ の外へ移動し非公開化
Diffstat (limited to 'public/bluebey-studio/src/glbExport.js')
| -rw-r--r-- | public/bluebey-studio/src/glbExport.js | 148 |
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; - } -} |
