From a4e294ec41f82271d81a26778c4dc211bb23f4f9 Mon Sep 17 00:00:00 2001 From: Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> Date: Fri, 23 Oct 2026 10:09:16 +0900 Subject: bluebey-studio: キャップと消防士の帽子を描き直し MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/bluebey-studio/src/hats.js | 75 ++++++++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 24 deletions(-) diff --git a/public/bluebey-studio/src/hats.js b/public/bluebey-studio/src/hats.js index 3763f82..571b136 100644 --- a/public/bluebey-studio/src/hats.js +++ b/public/bluebey-studio/src/hats.js @@ -72,27 +72,36 @@ function buildSilk() { return group; } -/** キャップ: a low six-panel crown with a button, a band and a long curved bill. */ +/** キャップ: a six-panel crown, a band, a button and a bill that slopes down. */ function buildCap() { const group = new THREE.Group(); const cloth = material('#3f6fd0', { roughness: 0.82 }); const dark = material('#2c4d96', { roughness: 0.86 }); const button = material('#243c74', { roughness: 0.7 }); - // The crown: a hemisphere whose flat face sits on y = 0, squashed and leaning - // a touch forward so it reads as fabric, not a bowl. - const crown = add(group, sphere(1.05, 32, 20, 0, Math.PI * 2, 0, Math.PI / 2), cloth, 0, 0.02, 0.02); - crown.scale.set(1, 0.88, 1.03); + // The crown: a dome big enough to sit over the top of the head. + const crown = add(group, sphere(1.25, 30, 20, 0, Math.PI * 2, 0, Math.PI / 2), cloth, 0, 0.0, 0.02); + crown.scale.set(1, 0.82, 1.04); + + // Six panel seams. A quarter torus per seam lies on the dome and carries the + // eye from the button down to the rim, which is what makes it read as a cap. + for (let i = 0; i < 6; i += 1) { + const a = (i / 6) * Math.PI * 2; + const seam = add(group, new THREE.TorusGeometry(1.245, 0.018, 5, 14, Math.PI / 2), dark, 0, 0.0, 0.02); + seam.scale.set(1, 0.82, 1.04); + seam.rotation.y = -a; + } // The band around the rim, and the button on top. - tone(add(group, cylinder(1.075, 1.075, 0.14, 32), dark, 0, 0.07, 0.02), 0.22); - tone(add(group, sphere(0.10, 14, 10), button, 0, 0.925, 0.02), 0.18); - - // The bill: an oval that is long at the front and tucked under the crown at - // the back, tipped down a little. - const bill = tone(add(group, cylinder(1.12, 1.12, 0.075, 32), material('#5b86e0', { roughness: 0.82 }), 0, 0.075, 0.75), 0.26); - bill.scale.set(1, 1, 1.9); - bill.rotation.x = -0.28; + tone(add(group, cylinder(1.27, 1.27, 0.15, 32), dark, 0, 0.075, 0.02), 0.22); + tone(add(group, sphere(0.11, 14, 10), button, 0, 1.03, 0.02), 0.18); + + // The bill: the front half of a disc, tilted down at the front (`rotation.x` + // positive lowers +z). It sits a little above the rim because the hat is worn + // sunk into the round head; at y = 0 the head hides the whole bill. + const bill = tone(add(group, new THREE.CylinderGeometry(1.24, 1.24, 0.08, 32, 1, false, -Math.PI / 2, Math.PI), cloth, 0, 0.45, 0.06), 0.26); + bill.scale.set(1, 1, 1.85); + bill.rotation.x = 0.30; return group; } @@ -131,20 +140,38 @@ function buildChef() { return group; } -/** 消防士の帽子: a red helmet with a brim, a top ridge and a small front badge. */ +/** 消防士の帽子: a red helmet with a wide brim that runs long at the back, a + * ridge along the top and a gold front shield. */ function buildFire() { const group = new THREE.Group(); - const red = material('#c5342f'); - const dark = material('#8f241f'); - const gold = material('#f0c24a', { metalness: 0.4, roughness: 0.4 }); - const dome = add(group, sphere(1.06, 28, 18, 0, Math.PI * 2, 0, Math.PI / 2), red, 0, 0, 0); - dome.scale.set(1, 0.95, 1); - const brim = tone(add(group, cylinder(1.34, 1.34, 0.12, 28), dark, 0, 0.06, 0.18), 0.28); - brim.scale.set(1, 1, 1.1); + const red = material('#c5342f', { roughness: 0.58 }); + const dark = material('#8f241f', { roughness: 0.62 }); + const gold = material('#f0c24a', { metalness: 0.35, roughness: 0.35 }); + const cream = material('#f7efdc', { roughness: 0.55 }); + + // The dome, wide enough to cover the top of the head. + const dome = add(group, sphere(1.42, 32, 20, 0, Math.PI * 2, 0, Math.PI / 2), red, 0, 0, 0); + dome.scale.set(1, 0.90, 1.05); + + // The brim: an oval, pushed back so it protects the neck, with a rolled edge. + const brim = add(group, cylinder(1.80, 1.70, 0.13, 36), dark, 0, 0.02, -0.16); + brim.scale.set(1, 1, 1.50); + const rim = add(group, new THREE.TorusGeometry(1.775, 0.085, 10, 48), dark, 0, 0.085, -0.16); + rim.scale.set(1, 1.50, 1); + rim.rotation.x = Math.PI / 2; + tone(rim, 0.26); + // The ridge along the top, front to back. - const crest = tone(add(group, box(0.16, 0.42, 1.5), dark, 0, 1.02, 0), 0.28); - crest.rotation.x = -0.05; - tone(add(group, sphere(0.2, 14, 12), gold, 0, 0.62, 1.02), 0.22); + const crest = tone(add(group, sphere(0.5, 18, 14), dark, 0, 1.14, -0.05), 0.26); + crest.scale.set(0.34, 0.70, 2.05); + + // The front shield, lying on the dome's face. + const shield = tone(add(group, sphere(0.52, 20, 16), gold, 0, 0.50, 1.30), 0.20); + shield.scale.set(0.76, 1.0, 0.20); + shield.rotation.x = -0.30; + const badge = tone(add(group, sphere(0.38, 18, 14), cream, 0, 0.50, 1.44), 0.14); + badge.scale.set(0.64, 0.94, 0.18); + badge.rotation.x = -0.30; return group; } -- cgit v1.3.1