aboutsummaryrefslogtreecommitdiffhomepage
path: root/public/bluebey-studio/src/styles.js
diff options
context:
space:
mode:
authorYasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com>2026-10-05 22:24:22 +0900
committerYasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com>2026-10-05 22:24:22 +0900
commit63c1a9dfa8b04ab9725a9a49ef34cd3fc6ade56c (patch)
tree9a5b0970585a927bb37db09d23ba7bb1fc4190be /public/bluebey-studio/src/styles.js
parent39a6c31beed18f0d1fc211fda919fae7ce2d9505 (diff)
bluebey-studio: 小物の3軸回転・線画対応と、まんがのコマ枠・共有リンク短縮
小物にX/Y/Zの3軸回転とフラット/線画スタイル対応を追加し、鉛筆の芯を円錐に埋め、ノートと消しゴムを追加。まんがは各コマに枠を描きzip出力を廃止。共有リンクは既定値の省略で約2,429→321文字に短縮。縦書きは句読点のぶら下げと長音・小書き文字の位置を補正。線画の帽子にトーンを付け、removeMeshでマテリアルを復元して切替時の破損を修正。
Diffstat (limited to 'public/bluebey-studio/src/styles.js')
-rw-r--r--public/bluebey-studio/src/styles.js31
1 files changed, 28 insertions, 3 deletions
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]);