From 63c1a9dfa8b04ab9725a9a49ef34cd3fc6ade56c Mon Sep 17 00:00:00 2001 From: Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> Date: Mon, 5 Oct 2026 22:24:22 +0900 Subject: bluebey-studio: 小物の3軸回転・線画対応と、まんがのコマ枠・共有リンク短縮 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 小物にX/Y/Zの3軸回転とフラット/線画スタイル対応を追加し、鉛筆の芯を円錐に埋め、ノートと消しゴムを追加。まんがは各コマに枠を描きzip出力を廃止。共有リンクは既定値の省略で約2,429→321文字に短縮。縦書きは句読点のぶら下げと長音・小書き文字の位置を補正。線画の帽子にトーンを付け、removeMeshでマテリアルを復元して切替時の破損を修正。 --- public/bluebey-studio/src/styles.js | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) (limited to 'public/bluebey-studio/src/styles.js') diff --git a/public/bluebey-studio/src/styles.js b/public/bluebey-studio/src/styles.js index 1b6a08d..8efdf71 100644 --- a/public/bluebey-studio/src/styles.js +++ b/public/bluebey-studio/src/styles.js @@ -110,6 +110,11 @@ export class Styles { this.gradientMap = makeGradientMap(); this.toonMaterials = new Map(); this.paperMaterial = new THREE.MeshBasicMaterial({ color: paper, toneMapped: false }); + // A hat is not flat: in a line drawing a white hat on a white head merges + // into one shape, so hats get a light tone instead of paper (see addMesh). + this.toneMaterial = new THREE.MeshBasicMaterial({ color: toneOf(paper), toneMapped: false }); + /** Runtime meshes that want the tone fill rather than paper in 線画. */ + this.toneMeshes = new Set(); this.invisibleMaterial = new THREE.MeshBasicMaterial({ colorWrite: false, depthWrite: true }); this.outlineWidth = outlineWidth; @@ -186,11 +191,15 @@ export class Styles { /** * Register a mesh that is built at runtime (a hat), so it follows the render * styles and gets an outline hull like the GLB parts. `removeMesh` undoes it. + * + * `options.tone` gives the mesh the light tone fill in 線画 instead of paper, + * for parts whose shape would otherwise merge with the body (the hats). */ - addMesh(mesh) { + addMesh(mesh, options = {}) { if (!mesh || this.originals.has(mesh)) return; this.originals.set(mesh, mesh.material); this.meshes.push(mesh); + if (options.tone) this.toneMeshes.add(mesh); const kind = this.kindOf(mesh); if (!OUTLINE_SKIP.has(kind)) { const overlay = OUTLINE_SILHOUETTE_ONLY.has(kind); @@ -217,6 +226,7 @@ export class Styles { /** Take a runtime mesh (and its outline hull) back out. */ removeMesh(mesh) { if (!mesh || !this.originals.has(mesh)) return; + const original = this.originals.get(mesh); const hull = mesh.userData.hull; if (hull) { hull.removeFromParent(); @@ -224,7 +234,12 @@ export class Styles { if (i >= 0) this.outlineMeshes.splice(i, 1); delete mesh.userData.hull; } + // Put the mesh's own material back before dropping it: while a line style is + // on it is wearing the *shared* paper/tone material, and the caller is about + // to dispose it - which would blank every part using it. + if (original) mesh.material = original; this.originals.delete(mesh); + this.toneMeshes.delete(mesh); const j = this.meshes.indexOf(mesh); if (j >= 0) this.meshes.splice(j, 1); } @@ -243,11 +258,14 @@ export class Styles { const original = this.originals.get(mesh); let material; if (line) { - // Paper on paper in `lineart`; invisible in `outline`, which is the pass + // Paper on paper in `lineart`; a light tone for hats, which would + // otherwise merge into the head; invisible in `outline`, which is the pass // the SVG trace reads. Either way every edge - the body, the nose, and // the leaves - is drawn by the screen-space outline, so no part needs a // material trick of its own any more. - material = value === 'lineart' ? this.paperMaterial : this.invisibleMaterial; + material = value === 'lineart' + ? (this.toneMeshes.has(mesh) ? this.toneMaterial : this.paperMaterial) + : this.invisibleMaterial; } else if (value === 'flat') { material = this.toonFor(original); } else { @@ -307,6 +325,7 @@ export class Styles { setPaper(color) { this.paper = color; this.paperMaterial.color.set(color); + this.toneMaterial.color.copy(toneOf(color)); } setOutlineVisible(visible) { @@ -360,6 +379,7 @@ export class Styles { for (const entry of this.hullMaterials.values()) entry.material.dispose(); this.hullMaterials.clear(); this.paperMaterial.dispose(); + this.toneMaterial.dispose(); this.invisibleMaterial.dispose(); this.gradientMap.dispose(); for (const material of this.toonMaterials.values()) material.dispose(); @@ -367,6 +387,11 @@ export class Styles { } } +/** A step towards the ink, for the light fill a hat gets in 線画. */ +function toneOf(paper) { + return new THREE.Color(paper).lerp(new THREE.Color('#2a1e33'), 0.14); +} + /** A 3-step ramp gives crisper cartoon bands than the shader's default. */ function makeGradientMap() { const steps = new Uint8Array([90, 165, 255]); -- cgit v1.3.1