From c81104129a9ba6e26aa33a00770f76a6aa97b2a9 Mon Sep 17 00:00:00 2001 From: Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> Date: Sun, 4 Oct 2026 00:06:06 +0900 Subject: bluebey-studio: 髪の板(hair-plate)で頭の模様を後ろまで回す MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit モデルに追加された hair-plate(頭をぐるりと覆う円筒UVの板)を読み込み、頭の模様をその板にも描くようにした。これで横や後ろから見ても髪が見える。髪の板が無いモデルは従来どおり前面の板に描く。 --- public/bluebey-studio/src/model.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'public/bluebey-studio/src/model.js') diff --git a/public/bluebey-studio/src/model.js b/public/bluebey-studio/src/model.js index 083c745..68f1255 100644 --- a/public/bluebey-studio/src/model.js +++ b/public/bluebey-studio/src/model.js @@ -11,6 +11,7 @@ import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; * body parts Body, Foots, Leaf, Vein, LeftHand, RightHand, Nose * overlays eyes-* (eight hand-drawn variants, one texture each) and Mouth * face plates eyes-plate / mouth-plate, the big shells the studio draws on + * hair plate hair-plate, a shell that wraps right round the head (optional) * * The two face plates are what the artwork is painted onto. They are the front * half of the body, so wherever the drawing lands there is a surface to carry @@ -60,7 +61,7 @@ const normalizeBoneName = (name) => (name ?? '').replace(/[.\s]/g, '').toLowerCa * the ring of leaves around the base, otherwise a low or frowning mouth gets * swallowed by them. */ -const OVERLAY_INFLATE = { eyes: 0.004, mouth: 0.004 }; +const OVERLAY_INFLATE = { eyes: 0.004, mouth: 0.004, hair: 0.008 }; export async function loadModel(source, { onProgress } = {}) { const loader = new GLTFLoader(); @@ -90,6 +91,8 @@ export async function loadModel(source, { onProgress } = {}) { /** The big shells the artwork is drawn onto, if the file has them. */ let eyePlate = null; let mouthPlate = null; + /** The shell that wraps round the head, so hair shows from behind (optional). */ + let hairPlate = null; root.traverse((object) => { if (!object.isMesh) return; @@ -105,6 +108,10 @@ export async function loadModel(source, { onProgress } = {}) { mouthPlate = object; return; } + if (materialName === 'hair-plate') { + hairPlate = object; + return; + } if (materialName.startsWith('eyes-')) { eyes.set(materialName.slice('eyes-'.length), object); return; @@ -125,6 +132,8 @@ export async function loadModel(source, { onProgress } = {}) { // The drawing surface: the big plate when present, otherwise the 2022 plane. const eyeMesh = eyePlate ?? eyes.get('opened') ?? eyes.values().next().value; const mouth = mouthPlate ?? mouthOriginal; + // The wrapping hair shell, if the model carries one (`hair-plate`). + const hairMesh = hairPlate; if (!eyeMesh) throw new Error('eye mesh not found in the GLB'); if (!mouth) throw new Error('mouth mesh not found in the GLB'); @@ -154,6 +163,7 @@ export async function loadModel(source, { onProgress } = {}) { .getCenter(new THREE.Vector3()); inflateOverlay(eyeMesh, headCentre, OVERLAY_INFLATE.eyes); inflateOverlay(mouth, headCentre, OVERLAY_INFLATE.mouth); + if (hairMesh) inflateOverlay(hairMesh, headCentre, OVERLAY_INFLATE.hair); // Recentre: put the character on the origin with its feet on the ground so the // camera maths stays trivial. @@ -196,9 +206,10 @@ export async function loadModel(source, { onProgress } = {}) { body, kinds, leaves, - overlays: [eyeMesh, mouth], + overlays: [eyeMesh, mouth, ...(hairMesh ? [hairMesh] : [])], eyeMesh, mouthMesh: mouth, + hairMesh, noseMesh, hasFacePlates: Boolean(eyePlate && mouthPlate), }, -- cgit v1.3.1