aboutsummaryrefslogtreecommitdiffhomepage
path: root/public
diff options
context:
space:
mode:
Diffstat (limited to 'public')
-rw-r--r--public/bluebey-studio/src/faceArt.js82
-rw-r--r--public/bluebey-studio/src/panel.js25
-rw-r--r--public/bluebey-studio/src/presets.js4
3 files changed, 31 insertions, 80 deletions
diff --git a/public/bluebey-studio/src/faceArt.js b/public/bluebey-studio/src/faceArt.js
index ba748f5..1f7c4ee 100644
--- a/public/bluebey-studio/src/faceArt.js
+++ b/public/bluebey-studio/src/faceArt.js
@@ -835,7 +835,7 @@ function drawCheeks(ctx, spec) {
*/
function drawHeadCover(ctx, spec, limits) {
const shape = spec?.shape ?? 'off';
- if (shape === 'off' || shape === 'none' || shape === 'wave') return;
+ if (shape === 'off' || shape === 'none' || shape === 'wave' || shape === 'side') return;
const size = clamp(spec.size ?? 1, 0.02, 3);
const left = limits.left;
@@ -867,18 +867,19 @@ function drawHeadCover(ctx, spec, limits) {
const halfSpan = width / 2;
const boundary = (x) => {
const t = clamp((x - cx) / halfSpan, -1, 1);
- // まえがみ: 富士額 / M字 (a widow's peak).
+ // まえがみ: 富士額 / M字 (a widow's peak). The covering dips to a point at
+ // the centre and at the two temples, receding in between - the two valleys
+ // and three points of an "M". The points scale with `size`, so a smaller
+ // まえがみ is a smaller M rather than a thin band with a tall spike.
if (shape === 'fringe') {
- const m = (1 + Math.cos(2 * Math.PI * t)) / 2;
- const peak = Math.max(0, 1 - Math.abs(t) * 3);
- return base + 300 * m + 160 * peak;
+ const dip = (x, centre, reach) => Math.max(0, 1 - Math.abs(x - centre) / reach);
+ const shape01 = Math.max(dip(t, 0, 0.36), 0.6 * Math.max(dip(t, -0.82, 0.26), dip(t, 0.82, 0.26)));
+ return base + 190 * size * shape01;
}
// はちわれ: the same V upside down - a widow's peak.
if (shape === 'fringe-up') return base - 320 * Math.max(0, 1 - Math.abs(t) * 1.5);
// カーブ: a plain shallow curve, higher (more hair) round the sides.
if (shape === 'curve') return base + 160 * t * t;
- // わけめ: a 7:3 parting - a step at 30% from the left.
- if (shape === 'side') return base + 60 - 320 / (1 + Math.exp(-(t + 0.4) * 10));
// `cap`: a rounded helmet, lowest in the middle.
return base - 100 * (1 - t * t);
};
@@ -894,23 +895,6 @@ function drawHeadCover(ctx, spec, limits) {
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
-
- if (shape === 'side') {
- // わけめ: cut a real parting in the hair, so the two halves read as combed
- // apart rather than as one smooth sweep.
- const partX = cx - halfSpan * 0.4;
- const gw = Math.max(5, width * 0.02) * (0.6 + size);
- ctx.save();
- ctx.globalCompositeOperation = 'destination-out';
- ctx.beginPath();
- ctx.moveTo(partX - gw * 0.16, top);
- ctx.lineTo(partX + gw * 0.16, top);
- ctx.lineTo(partX + gw * 0.7, boundary(partX) + 4);
- ctx.lineTo(partX - gw * 0.7, boundary(partX) + 4);
- ctx.closePath();
- ctx.fill();
- ctx.restore();
- }
ctx.restore();
}
@@ -928,8 +912,9 @@ function drawHeadCover(ctx, spec, limits) {
*/
export function drawHeadCoverWrap(ctx, spec, layout) {
const shape = spec?.shape ?? 'off';
- // `split` and `wave` are retired front-plate shapes; they have no wrap equal.
- if (shape === 'off' || shape === 'none' || shape === 'split' || shape === 'wave') return;
+ // `split`, `wave` and `side` are retired front-plate shapes; they have no wrap
+ // equal. An old saved state may still carry one, so it simply draws no hair.
+ if (shape === 'off' || shape === 'none' || shape === 'split' || shape === 'wave' || shape === 'side') return;
const size = clamp(spec.size ?? 1, 0.02, 3);
const W = layout.width;
@@ -962,30 +947,19 @@ export function drawHeadCoverWrap(ctx, spec, layout) {
// centre (the peak) and at the temples, with the corners between receded -
// the three peaks and two valleys of an "M". `t` is -1..1 across the front.
if (shape === 'fringe') {
- const m = (1 + Math.cos(2 * Math.PI * t)) / 2; // wide peaks at t = 0, +-1
- const peak = Math.max(0, 1 - Math.abs(t) * 1.6); // the central point
- const shape01 = 0.55 * m + 0.45 * peak; // 0..1 across the M
- // The recessed corners keep a hairline, and the peaks scale with `size`, so
- // a small まえがみ stays an M instead of collapsing into a spindle.
- const valley = Math.max(base, 0.10);
- // The peaks deepen with `size`, so a small まえがみ is a small M rather
- // than a tall spike on a thin band.
- const amp = 0.36 * (0.55 + 0.45 * Math.min(clamp(size, 0.05, 3), 1));
- return valley + amp * shape01;
+ const dip = (x, centre, reach) => Math.max(0, 1 - Math.abs(x - centre) / reach);
+ // The centre dips the lowest, the two temples dip less, and the hairline
+ // recedes between them - the two valleys and three points of an "M".
+ const shape01 = Math.max(dip(t, 0, 0.36), 0.6 * Math.max(dip(t, -0.82, 0.26), dip(t, 0.82, 0.26)));
+ // The points grow with `size`, so a small まえがみ is a small M rather than
+ // a thin band with a tall spike (a spindle).
+ return base + 0.16 * size * shape01;
}
// はちわれ: the same V upside down - a widow's peak.
if (shape === 'fringe-up') return base - 0.18 * Math.max(0, 1 - Math.abs(t) * 1.5);
// カーブ: a deep round cover - low at the sides, high in the middle, like the
// blue of a certain robot cat's head.
if (shape === 'curve') return base + 0.42 * t * t;
- // わけめ: a 7:3 part. The hairline is asymmetric across the *front* only and
- // comes back to the base level at the sides, so it is continuous at the back
- // seam (an angle-varying hairline there would ride the seam and step). The
- // parting line is the gap cut out below.
- if (shape === 'side') {
- const w = Math.max(0, 1 - t * t); // 0 at the sides, 1 at the front
- return base + 0.18 * w * (0.5 + 0.5 * t);
- }
return base - 0.07 * (1 - t * t); // `cap`
};
@@ -1000,26 +974,6 @@ export function drawHeadCoverWrap(ctx, spec, layout) {
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
-
- if (shape === 'side') {
- // The parting: a shallow notch cut up from the hairline at 30% (a 7:3 split).
- // Cutting all the way to the top of the head made it look bald, so only the
- // lower part of the covering is cut.
- const partX = span * 0.4;
- const gw = Math.max(6, span * 0.012) * (0.6 + size);
- const bY = clamp(vBound(partX), 0, 1) * H;
- const top = Math.max(0, bY - Math.min(110, bY * 0.6));
- ctx.save();
- ctx.globalCompositeOperation = 'destination-out';
- ctx.beginPath();
- ctx.moveTo(partX - gw * 0.35, top);
- ctx.lineTo(partX + gw * 0.35, top);
- ctx.lineTo(partX + gw * 1.1, bY + 2);
- ctx.lineTo(partX - gw * 1.1, bY + 2);
- ctx.closePath();
- ctx.fill();
- ctx.restore();
- }
ctx.restore();
}
diff --git a/public/bluebey-studio/src/panel.js b/public/bluebey-studio/src/panel.js
index 67022aa..ed7f766 100644
--- a/public/bluebey-studio/src/panel.js
+++ b/public/bluebey-studio/src/panel.js
@@ -171,9 +171,11 @@ export function buildPanel(app, root) {
right: randomEye(),
cheeks: { enabled: Math.random() < 0.5 },
beard: { shape: beardShape, ...(BEARD_KIND_DEFAULTS[beardShape] ?? { size: 1, offsetY: 0, spacing: 0 }) },
- headMark: { shape: pick(['off', 'cap', 'fringe', 'fringe-up', 'curve', 'side']) },
+ headMark: { shape: pick(['off', 'cap', 'fringe', 'fringe-up', 'curve']) },
brow: {
enabled: true,
+ // Random brows are line drawings, never ゴル風's loaded picture.
+ image: false,
angle: Math.round(rnd(-30, 40)),
height: Math.round(rnd(-10, 50)),
length: +rnd(0.6, 2.6).toFixed(2),
@@ -871,9 +873,12 @@ export function buildPanel(app, root) {
onClick: () => {
// 両津勘吉: thick げじげじ brows that are joined in the middle, sitting
// low over the eyes. `spacing` is what closes the gap - at this length
- // the two inner ends meet over the nose (see the panel's note).
+ // the two inner ends meet over the nose (see the panel's note). Turning
+ // `image` off is what makes the line drawing take over from ゴル風's
+ // loaded picture.
Object.assign(state.face.eyes.brow, {
enabled: true,
+ image: false,
angle: 10,
height: 4,
length: 2.86,
@@ -890,14 +895,8 @@ export function buildPanel(app, root) {
],
}));
- const browImageToggle = check({
- label: 'ゴル風の画像を使う',
- value: false,
- onChange: (value) => { state.face.eyes.brow.image = value; app.actions.refresh('face'); },
- });
- faceSection.add(controlRow(null, browImageToggle.el, { wide: true }));
- faceSection.add(hint('読み込んだ画像(assets/brows/gol-right.png)を眉として左右に描きます。'
- + '「ゴル風」を押すと自動で入ります。画像が無いときは今までの線で描きます。'));
+ faceSection.add(hint('「ゴル風」は読み込んだ画像(assets/brows/gol-right.png)で眉を描きます。'
+ + '「両風」など線で描く眉に切り替えると、この画像は使われません。'));
// --- glasses / sunglasses (drawn into the same texture as the eyes and brows)
faceSection = faceTabSections.glasses;
@@ -1082,7 +1081,6 @@ export function buildPanel(app, root) {
{ value: 'fringe', label: 'まえがみ' },
{ value: 'fringe-up', label: 'はちわれ' },
{ value: 'curve', label: 'カーブ' },
- { value: 'side', label: 'わけめ' },
],
value: 'off',
onChange: (value) => { state.face.eyes.headMark.shape = value; app.actions.refresh('face'); },
@@ -1127,8 +1125,8 @@ export function buildPanel(app, root) {
headMarkBox.add(headMarkColor.el);
headMarkBox.add(headMarkColor2.el);
headMarkBox.add(hint('「かぶせ」は頭にかぶせる丸い髪、「まえがみ」は中央とこめかみが下がるM字(富士額)の前髪、'
- + '「はちわれ」は中央が鋭く持ち上がった生え際、「カーブ」はドラえもんのように単純にカーブした髪、'
- + '「わけめ」は7:3に分けた髪です。「たかさ」で生え際を目の中心あたりまで下げられます。'
+ + '「はちわれ」は中央が鋭く持ち上がった生え際、「カーブ」はドラえもんのように単純にカーブした髪です。'
+ + '「たかさ」で生え際を目の中心あたりまで下げられます。'
+ '「髪を目の前に出す」を切ると、髪が目の後ろに回ります。'));
// --- 鼻ちょうちん: the snot bubble of a sleeping face.
@@ -2706,7 +2704,6 @@ export function buildPanel(app, root) {
browSliders.spacing.set(state.face.eyes.brow.spacing ?? 0);
browSliders.curve.set(state.face.eyes.brow.curve);
browSliders.taper.set(state.face.eyes.brow.taper ?? 1);
- browImageToggle.set(state.face.eyes.brow.image === true);
browColor.set(state.face.eyes.brow.color);
glassesToggle.set(state.face.eyes.glasses.enabled === true);
glassesKindSegment.set(state.face.eyes.glasses.kind ?? 'glasses');
diff --git a/public/bluebey-studio/src/presets.js b/public/bluebey-studio/src/presets.js
index 43fc12f..7f091f1 100644
--- a/public/bluebey-studio/src/presets.js
+++ b/public/bluebey-studio/src/presets.js
@@ -231,8 +231,8 @@ export function defaultState() {
color: '#f6a6b8',
},
// The hair covering's `shape` is one of `off`, `cap`, `fringe`,
- // `fringe-up`, `curve` or `side`. `overEyes` puts the hair in front of
- // the eyes (bangs over the face); off leaves the eyes on top.
+ // `fringe-up` or `curve`. `overEyes` puts the hair in front of the eyes
+ // (bangs over the face); off leaves the eyes on top.
headMark: {
shape: 'off',
size: 1,