From bc2821adb11a30244fc4663f4fafb756877d9508 Mon Sep 17 00:00:00 2001 From: Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> Date: Wed, 7 Oct 2026 23:09:40 +0900 Subject: bluebey-studio: public/ の外へ移動し非公開化 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/bluebey-studio/src/presets.js | 892 ----------------------------------- 1 file changed, 892 deletions(-) delete mode 100644 public/bluebey-studio/src/presets.js (limited to 'public/bluebey-studio/src/presets.js') diff --git a/public/bluebey-studio/src/presets.js b/public/bluebey-studio/src/presets.js deleted file mode 100644 index e4fe49f..0000000 --- a/public/bluebey-studio/src/presets.js +++ /dev/null @@ -1,892 +0,0 @@ -/** - * The complete, serialisable studio state plus the preset libraries. - * - * Everything here is plain JSON: "設定を保存" writes exactly this object, and - * "設定を読み込む" merges it back in, so a look can be archived and shared. - * - * Eye parameters are named after the character's own left/right, NOT the - * viewer's: `left` is the eye on the model's +x side, which is the half of the - * eye texture with u > 0.5. In a front view that eye appears on the right of - * the screen. - */ - -/** - * One speech bubble's defaults. - * - * A factory rather than a constant, because there are two bubbles (see - * `caption2`) and they must not share one object - editing one would edit both. - */ -function captionDefaults() { - return { - enabled: false, - text: 'こんにちは、ぶるべーです。', - // Anchor of the bubble's box, as a fraction of the image (0..1). - x: 0.58, - y: 0.16, - maxWidth: 0.36, // fraction of the image width for the text column - bubble: 'round', // round | rect | shout | none - tail: 'left', // left | right | top | bottom | topLeft | topRight | bottomLeft | bottomRight | none - fontSize: 34, // pixels at 1x; scales with the export - lineHeight: 1.42, - padding: 18, - radius: 24, - textColor: '#3f2b52', - bubbleColor: '#ffffff', - borderColor: '#55386e', - borderWidth: 4, - bold: false, - align: 'left', // left | center | right - vertical: false, // 縦書き (columns run right to left) - font: 'rounded', // rounded (vendored) | system - }; -} - -/** - * ナレーション (a narration box): a plain rectangular panel of vertical text, the - * manga narration style. Same shape as a bubble, with a rect box, no tail, and - * 縦書き on by default. - */ -function narrationDefaults() { - return { - enabled: false, - text: 'ここで、ぶるべーは考えた。', - x: 0.06, - y: 0.08, - maxWidth: 0.5, - bubble: 'rect', - tail: 'none', - fontSize: 30, - lineHeight: 1.35, - padding: 16, - radius: 8, - textColor: '#2b2433', - bubbleColor: '#ffffff', - borderColor: '#2b2433', - borderWidth: 3, - bold: false, - align: 'left', - vertical: true, - font: 'rounded', - }; -} - -/** - * Per-kind starting size and height for ひげ, applied when a kind is picked. The - * textured kinds are drawings, so they are drawn at a different scale from the - * built-in strokes; a kind with no entry resets to the neutral values. - */ -export const BEARD_KIND_DEFAULTS = { - scotch: { size: 0.46, offsetY: 74 }, - kaiser: { size: 1, offsetY: 80, spacing: -10 }, -}; - -/** - * Per-shape starting values for 髪 (headMark), applied when a shape is picked. - * `teeth` only matters for ぎざぎざ, so its default is set here rather than on - * the shared `size`/`teeth` fields. - */ -export const HEAD_MARK_KIND_DEFAULTS = { - fringe: { size: 0.83, teeth: 0.06 }, -}; - -/** - * 見えない壁 (the invisible wall) settings. The studio offers two, so a corner (or - * two opposite cuts) can be made. A wall's position is kept *relative to the - * character*, so moving ぶるべー carries its walls along (see src/clip.js). - */ -function wallDefaults() { - return { - on: false, - x: 0, y: 0, z: 0, // a point the plane passes through, relative to the character - yaw: 0, // turn about Y, degrees - tilt: 0, // lean about X, degrees - // The guide is the only part of the wall that is ever *drawn* (the wall - // itself writes depth but no colour, so it hides what is behind it - // without being visible). `guide` shows that rectangle while placing, - // which is also when it can be dragged in the viewport. - guide: false, - size: 1, // the wall's size (a real width and height, not a clip) - }; -} - -export function defaultState() { - return { - version: 1, - view: { - projection: 'persp', // persp | ortho - fov: 30, - azimuth: 0, // degrees, 0 = looking at the face - polar: 76, // degrees from +y, 90 = level with the target - distance: 0, // 0 = fit to the model automatically - orthoHeight: 0, // 0 = fit to the model automatically - targetY: 0, // 0 = centre of the model - // The mouse orbit and pan write their own target back here (see - // ViewRig.captureInto), so refreshing the view - or sharing a link - keeps - // the camera the user actually framed. - targetX: 0, - targetZ: 0, - autoRotate: false, - autoRotateSpeed: 0.8, - // solid | transparent | preset | image | effect | camera - // preset = one of the CC0 backdrops in assets/backgrounds - // image = something the user loaded (data URL) - // effect = a procedurally drawn manga effect line (no file at all) - // camera = the phone's camera, for the AR mode - background: 'solid', - backgroundColor: '#ffffff', - backgroundPreset: 'autumn_park', - backgroundEffect: 'focus', // focus | fall | speed | ellipse - backgroundImage: null, // data URL, set by 「画像を読み込む」 - backgroundFit: 'cover', // cover | contain | stretch | tile - backgroundBlur: 0, // px - backgroundDarken: 0, // 0..1 dark veil, helps the character read - backgroundScale: 1, - backgroundOffset: { x: 0, y: 0 }, - // AR: which camera, and whether to mirror (front camera feels mirrored) - cameraFacing: 'environment', // environment | user - cameraMirror: false, - gyro: false, // look around by turning the phone - }, - render: { - style: 'real', // real | flat | lineart | outline - outline: true, - outlineWidth: 0.022, - outlineColor: '#2a1e33', - paper: '#ffffff', - shadow: true, - // The soft round blob at the character's feet. Off by default: it sat at the - // origin and stayed behind when the character was moved. See look.js. - contactBlob: false, - pngScale: 2, - pngTransparent: false, - svgWidth: 2048, - lightAzimuth: -38, - lightElevation: 40, - lightIntensity: 2.1, - ambient: 0.9, - // The colour of the main light and of the ambient (sky) light. - lightColor: '#ffffff', - ambientColor: '#ffffff', - exposure: 1, - // Body colours. A theme fills these in; the colour pickers then adjust - // them one part at a time, so a theme is a starting point, not a mode. - theme: 'original', - colors: { - body: '#c8b0f0', - accent: '#8a4fe0', - nose: '#7a4fb0', - leaf: '#a6dd6a', - vein: '#7fbf3f', - feet: '#7a4fb0', - }, - // Mirror the whole character, for laying out a panel that faces its text. - mirror: false, - // Shadows: the soft blob under the feet, plus the cast shadow. - shadowOpacity: 0.22, - shadowSoftness: 1.6, - shadowOffset: 0, - contactShadow: false, // blob only, no cast shadow - // Lighting environment. 'room' is three's RoomEnvironment; 'gradient' is - // the tiny procedural sky the studio always had. - environment: 'gradient', // gradient | room | none - envIntensity: 1, - // Hand-drawn line work (SVG export and the outline pass). - handDrawn: 0, // 0 = off, 1 = a lot - handDrawnSeed: 7, - handDrawnScale: 40, - handDrawnPasses: 1, - // How the outline is drawn - the two methods take the parts they are good - // at. See MODEL-GUIDE.md §5 and src/outline.js. - // - // 'screen' (the default) hands the leaves to the screen-space edge pass, - // because a hull cannot outline a shell that thin: the expanded - // front and back cross inside it and the line breaks up. Every - // other part keeps its hull, whose line is computed from the - // geometry and so comes out smooth, where the pixel grid would - // make it stepped. - // 'hull' the inverted-hull copy everywhere - cheap, and the way it was - // done before, but the leaves then come out as a tangle. - outlineMethod: 'screen', - outlinePixels: 2.4, // 'screen' only: line width in screen pixels - // Whether a leaf gets a line where it meets the *body*. There is no right - // answer here, so it is a setting: - // on - the body counts as paper, so the leaves are outlined where they - // emerge from it. Without this a line drawing cannot tell the - // leaves and the body apart at all (both are paper). - // off - the body blocks the line, so the leaf simply passes behind it and - // nothing is drawn along the meeting. Cleaner, but the leaves and - // the body merge into one white shape. - leafBodyLine: true, - // 見えない壁 (the invisible wall). Up to two placeable rectangles that hide - // whatever is behind them, so the character can be buried in a wall and - // only the rest of the body shows. See src/clip.js. - wall: wallDefaults(), - wall2: wallDefaults(), - }, - pose: { - bones: {}, // { boneName: [x, y, z] } in degrees, rest = 0,0,0 - root: [0, 0, 0], // whole-body offset in model units - }, - anim: { - mode: 'off', // off | idle | walk - blink: true, - blinkInterval: 3.4, // seconds between blinks - lookAround: false, - speed: 1, - }, - face: { - // 帽子 (a hat on the head). `kind` is one of HAT_LIBRARY in src/hats.js. - // `x`/`z` slide it sideways / forwards, `height` lifts it off the head; - // `tiltX` leans it forward/back and `tiltZ` leans it sideways. - hat: { kind: 'none', custom: false, x: 0, z: 0, height: -0.7, tiltX: 0, tiltZ: 0 }, - eyes: { - source: 'parametric', // parametric | opened | closed | ... (hand-drawn) - linked: true, // move both eyes together - left: { shape: 'open', open: 1, lookX: 0, lookY: 0, eyeX: 0, closed: 'line', closedLines: 1, irisShape: 'circle', threeFlip: false, tear: 0.3, tearOn: false, tearY: 0, tearX: 0, tearTilt: 0, white: null, highlight: null, shapeScale: 1, lowerLid: null, lidShape: null, lidWidth: null, lidTilt: null, lashes: null, lashAngle: null, lashPos: null }, - right: { shape: 'open', open: 1, lookX: 0, lookY: 0, eyeX: 0, closed: 'line', closedLines: 1, irisShape: 'circle', threeFlip: false, tear: 0.3, tearOn: false, tearY: 0, tearX: 0, tearTilt: 0, white: null, highlight: null, shapeScale: 1, lowerLid: null, lidShape: null, lidWidth: null, lidTilt: null, lashes: null, lashAngle: null, lashPos: null }, - irisScale: 1, - lookMax: 1.2, - highlight: true, - lidWidth: 14, - lowerLid: 0, - lashes: false, - lashAngle: 0, - lashPos: 0, - // How the closing eyelid is drawn: `curve` is the soft, rounded lid; `flat` - // is a straight lid edge (the hard, narrowed look). `lidTilt` rotates the - // narrowed eye. See faceArt.js. - lidShape: 'curve', - lidTilt: 0, - heartScale: 2, - heartColor: '#e0344f', - // The tears live on each eye (see `eyes.left.tear`), so one eye can cry - // on its own; only the colour is shared. - tearColor: '#8fd8ff', - // ほっぺ (a manga blush) and 頭の模様 (a hair-like head covering) are drawn - // into the same plate as the eyes and brows. Both are generic devices, - // not a copy of any one character, and both start off. The blush is always - // four horizontal strokes, so there is no line-count field. - cheeks: { - enabled: false, - size: 1, - spacing: 0, // extra gap outwards from the eyes - offsetY: 0, // extra drop below the eyes - color: '#f6a6b8', - }, - // The hair covering's `shape` is one of `off`, `cap`, `fringe`, - // `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, - // ぎざぎざ only: how far the saw teeth reach down. - teeth: 0.12, - offsetX: 0, - offsetY: 0, - overEyes: true, - color: '#8a4fe0', - color2: '#ffffff', - }, - // 鼻ちょうちん: the snot bubble someone sleeps with. Drawn into the same - // plate as the eyes, out to the side of the nose. Starts off. - snot: { - enabled: false, - size: 1.23, - offsetX: -23, - offsetY: 0, - offsetZ: 16, - color: '#e3ecff', - }, - // ひげ: a mustache or beard worn under the nose. `shape` is one of `off`, - // `scotch`, `kaiser`, `apron` or `cat`. `spacing` widens the gap between - // the cat's left and right whiskers. Starts off. - beard: { - shape: 'off', - size: 1, - offsetY: 0, - spacing: 0, - // ねこ only: how far each whisker reaches (`length`) and how far apart the - // three lines of one side sit (`lineGap`). Neither changes the thickness. - length: 1, - lineGap: 1, - color: '#1c1624', - }, - // Eyebrows are drawn into the eye texture. Off by default, because the - // original character has no eyebrows. - brow: { - enabled: false, - // When true and a drawing (assets/brows/gol-right.png) was loaded, the - // brow is painted from that file (mirrored) instead of the strokes. - image: false, - angle: 0, - height: 0, - length: 1, - thickness: 1, - curve: 0.14, - // How wide the nose-side end is: 1 = a plain stroke, 0 = a wedge that - // comes to a point (the ゴル風 look). `spacing` widens the gap between - // the two brows. - taper: 1, - spacing: 0, - color: '#55386e', - }, - // 眼鏡 / サングラス are drawn into the same texture as the eyes and brows, - // so they travel with the head instead of being a separate 3D mesh. Off - // by default. `kind` only picks which lens defaults the panel offers; the - // drawing itself reads the colours and opacities below. - glasses: { - enabled: false, - kind: 'glasses', // glasses | sunglasses - frameColor: '#2a1e33', - frameWidth: 1, // multiplier on the frame stroke (EYE_LAYOUT.lidStroke) - lensColor: '#2b2433', - lensOpacity: 0.22, // 0..1; 眼鏡 is nearly clear, サングラス dark - lensGap: 0, // px, + moves the two lenses further apart - scale: 1, // lens size multiplier - offsetY: 0, // px, artwork units (positive = down) - tilt: 0, // degrees, both lenses turn together - }, - white: '#ffffff', - iris: '#150e1b', - line: '#55386e', - }, - mouth: { - source: 'parametric', // parametric | original - visible: true, - smile: 1, // 0 = straight line, 1 = the original smile, <0 = frown - width: 1, // the smile line's length (the chord); also the O's width - thickness: 1, - open: 0, // 0 = closed lips, 1 = wide open - tilt: 0, // degrees - offsetY: 0, // texture pixels - tongue: 0.96, // 0 hides it; scales the whole tongue - tonguePos: 0.84, // 0..1 along the smile line - corners: 0.71, // length of the corner marks, 0 hides them - cornerAngle: -8, // degrees, tilts the corner marks - round: 0, // >0 turns the mouth into a round "O" - color: '#ff1a44', - innerColor: '#4a0f1e', - tongueColor: '#ff2d2d', - cornerColor: '#725497', - line: '#55386e', - }, - }, - // ---------------------------------------------------------------- caption - // The speech bubbles drawn next to the character when exporting. They live in - // the state so they are saved, shared and undone like everything else. There - // are two of them, so a line and a reply (or a narrator and a speaker) fit. - caption: captionDefaults(), - // The second bubble starts on the other side, so the two do not overlap. - caption2: { ...captionDefaults(), text: '', x: 0.08, y: 0.44 }, - // A ナレーション box (vertical text, rect, no tail). - narration: narrationDefaults(), - // -------------------------------------------------------- 口パク (mouth flap) - // The mouth moves as if talking, with no sound at all: the device's own - // speech voices belong to the device, so a recording of them is not ours to - // hand out. Only the *length* of the text matters here. - mouthFlap: { - text: 'こんにちは。小平市のぶるべーです。', - rate: 1, // how fast the syllables come - mouthGain: 1, // how wide the mouth opens - }, - // ----------------------------------------------------------------- look at - // Where the character should look (world units). - lookAt: { - enabled: false, - x: 0, - y: 2.6, - z: 3, - turnBody: false, // also rotate the whole body a little - amount: 1, - // The turn and the gaze are *remembered* rather than derived, so switching - // the mode (or just 体も向ける) off leaves the character where it is instead - // of snapping it back to the front. See `applyLookAt` in main.js. - bodyYawDeg: 0, - freeze: null, - }, - // ------------------------------------------------------------------- props - // 小物. Each entry is a procedural prop from src/props.js. - props: { - items: [], - }, - // -------------------------------------------------------------------- 擬音 - // マンガのオノマトペ (擬音) as stickers over the picture. Each item is a crop - // of a sheet - `{ id, sheet, sx, sy, sw, sh, x, y, w, rot, flip }` - and only - // the sheet *name* is stored, never the bitmap, so a shared link stays small - // (see src/gion.js for the geometry). - gion: { - items: [], - }, - // ------------------------------------------------------------------- story - // 4コマ / 紙芝居. Each panel is a small state patch (pose, face, caption). - story: { - columns: 1, - gap: 12, - padding: 20, - sheetBackground: '#ffffff', - // How big each captured panel is, as a multiple of the viewport. 2x doubles - // each side, so the sheet PNG gets four times the pixels per panel. - scale: 1.5, - panels: [], - }, - }; -} - -/** Expression presets. Each patches a copy of the default state. */ -export const FACE_PRESETS = [ - { - id: 'normal', - label: 'ふつう', - face: { - eyes: { - left: { open: 1, lookX: 0, lookY: 0, closed: 'line' }, - right: { open: 1, lookX: 0, lookY: 0, closed: 'line' }, - irisScale: 1, - highlight: true, - }, - // Depth 1 = the hand-drawn original; the corners and tongue below are also - // the settings that reproduce it (measured against the artwork). - mouth: { smile: 1, width: 1, thickness: 1, open: 0, tilt: 0, offsetY: 0, tongue: 0.96, tonguePos: 0.84, corners: 0.71, cornerAngle: -8 }, - }, - }, - { - id: 'laugh', - label: 'にっこり', - face: { - eyes: { - left: { shape: 'arch', open: 1, lookX: 0, lookY: 0, closed: 'arch' }, - right: { shape: 'arch', open: 1, lookX: 0, lookY: 0, closed: 'arch' }, - highlight: false, - }, - // A laugh is a wide smile with squeezed-shut eyes. An *open* mouth on a - // round mascot reads as a shout, so only びっくり keeps the round O. - mouth: { smile: 1.15, width: 1.14, thickness: 1.05, open: 0, tilt: 0, offsetY: 0, round: 0, tongue: 0.96, corners: 1.25 }, - }, - }, - { - id: 'eating', - label: 'たべている', - face: { - eyes: { - left: { open: 1, lookX: 0, lookY: 0.15, closed: 'line' }, - right: { open: 1, lookX: 0, lookY: 0.15, closed: 'line' }, - irisScale: 1, - highlight: true, - }, - // A small, cute round mouth (the other round mouth, with びっくり). - mouth: { smile: 0.2, width: 0.8, thickness: 1.1, open: 0, tilt: 0, offsetY: 6, round: 0.58, tongue: 0.7, corners: 0 }, - }, - }, - { - id: 'wink', - label: 'ウインク', - face: { - eyes: { - // The shut eye is the original `eyes-close-tight` artwork: three strokes - // sharing one vertex, pointing at the nose. - left: { shape: 'line3', open: 1, lookX: 0, lookY: 0, closed: 'line', closedLines: 3 }, - right: { open: 1, lookX: 0, lookY: 0, closed: 'line', closedLines: 1 }, - irisScale: 1, - highlight: true, - }, - mouth: { smile: 1.0, width: 1.02, thickness: 1, open: 0, tilt: 0, offsetY: 0, tongue: 0, corners: 1 }, - }, - }, - { - id: 'surprised', - label: 'びっくり', - face: { - eyes: { - left: { open: 1, lookX: 0, lookY: 0, closed: 'line' }, - right: { open: 1, lookX: 0, lookY: 0, closed: 'line' }, - irisScale: 0.74, - lookMax: 0.35, - highlight: true, - }, - mouth: { smile: 0.2, width: 0.85, thickness: 1.1, open: 0, tilt: 0, offsetY: 0, round: 0.62, tongue: 0.25, corners: 0 }, - }, - }, - { - id: 'sleepy', - label: 'ねぼけ', - face: { - eyes: { - left: { open: 0.42, lookX: 0, lookY: -0.22, closed: 'line' }, - right: { open: 0.42, lookX: 0, lookY: -0.22, closed: 'line' }, - irisScale: 1, - highlight: true, - // A sleeper's snot bubble, with the defaults from the panel. - snot: { enabled: true }, - }, - mouth: { smile: 0.62, width: 0.88, thickness: 1, open: 0, tilt: -3, offsetY: 6, tongue: 0, corners: 0.8 }, - }, - }, - { - id: 'shy', - label: 'てれ', - face: { - eyes: { - // Blushing, eyes half shut and looking away. - left: { open: 0.72, lookX: -0.4, lookY: 0.08, closed: 'line' }, - right: { open: 0.72, lookX: -0.4, lookY: 0.08, closed: 'line' }, - cheeks: { enabled: true }, - irisScale: 1, - highlight: true, - }, - mouth: { smile: 0.5, width: 0.82, thickness: 1.05, open: 0, tilt: 0, offsetY: 0, tongue: 0, corners: 0.5 }, - }, - }, - { - id: 'sidelong', - label: 'じと目', - face: { - eyes: { - // Flat, narrowed lids tilted inwards - a sideways, unimpressed look. - // 上まぶたの高さ is now independent of the lower lid, so `open` carries the - // whole slit (it used to be measured from the raised lower lid). - left: { open: 0.74, lookX: 0.4, lookY: -0.12, closed: 'line' }, - right: { open: 0.74, lookX: 0.4, lookY: -0.12, closed: 'line' }, - lowerLid: 0.42, - lidShape: 'flat', - lidTilt: 12, - irisScale: 0.92, - highlight: true, - }, - mouth: { smile: 0.12, width: 0.78, thickness: 1.1, open: 0, tilt: 0, offsetY: 0, tongue: 0, corners: 0 }, - }, - }, - { - id: 'love', - label: 'すき', - face: { - eyes: { - left: { shape: 'heart', open: 1, lookX: -0.1, lookY: -0.3, closed: 'line', irisShape: 'heart', white: false }, - right: { shape: 'heart', open: 1, lookX: 0.1, lookY: -0.3, closed: 'line', irisShape: 'heart', white: false }, - irisScale: 1, - // The white behind a heart would poke out around its lobes, so hide it. - heartColor: '#e0344f', - highlight: false, - }, - mouth: { smile: 1.15, width: 1, thickness: 1.05, open: 0.16, tilt: 4, offsetY: 0, tongue: 0.9, corners: 1.3 }, - }, - }, - { - id: 'grumpy', - label: 'むっと', - face: { - eyes: { - left: { open: 0.6, lookX: 0, lookY: -0.12, closed: 'line', closedLines: 2 }, - right: { open: 0.6, lookX: 0, lookY: -0.12, closed: 'line', closedLines: 2 }, - irisScale: 0.86, - highlight: true, - }, - mouth: { smile: -0.62, width: 0.84, thickness: 1.15, open: 0, tilt: 0, offsetY: 0, tongue: 0, corners: 0 }, - }, - }, - { - id: 'cry', - label: 'ないてる', - face: { - eyes: { - // Big tears that hang low and lean outwards - the classic "crying hard" - // look. The sideways position and the tilt are mirrored between the eyes, - // so the pair stays symmetric. - left: { open: 0.84, lookX: 0, lookY: -0.3, closed: 'line', tear: 1.35, tearOn: true, tearY: 26, tearTilt: -20 }, - right: { open: 0.84, lookX: 0, lookY: -0.3, closed: 'line', tear: 1.35, tearOn: true, tearY: 26, tearTilt: 20 }, - irisScale: 1.02, - lowerLid: 0.42, - highlight: true, - }, - mouth: { smile: -0.5, width: 0.72, thickness: 1.25, open: 0, tilt: 0, offsetY: 0, tongue: 0, corners: 0 }, - }, - }, -]; - -/** - * Poses. `bones` holds per-bone XYZ degrees relative to the rest pose. - * - * The axes were measured (turn one bone 40° and watch the hand's centre move), - * and the two sides are NOT the same. That is what used to send the left hand - * behind while the right one came forward: - * - * arm / hand - * X lift. Up on both sides, so the SAME value is symmetric. - * Y roll about the flipper - which way the palm faces. - * Z forward/back. +Z is BACK on the left and FORWARD on the right, so a - * symmetric pose writes the right-hand Z with the opposite sign, while - * a walk (opposite arms) writes the SAME sign on both. - * legsupport - * X out to the side and up (same value on both sides is symmetric) - * Y twist (which way the toe points) - * Z forward/back, mirrored like `arm` - * It pivots at the middle of the body, so the feet travel a long way for - * a small angle - keep it under about 15 degrees. - * master - * X lean forward Y spin around Z roll sideways - * - * `armsupport` still exists in the rig but it is a control bone that pivots at - * the body's centre, so it swings a hand in a wide arc across the face. Every - * preset below drives `arm`/`hand` instead. - */ -export const POSE_PRESETS = [ - { id: 'stand', label: '立ち', pose: { bones: {} } }, - { - id: 'attention', - label: 'きおつけ', - pose: { - bones: { - // Both flippers dropped to the sides. A negative X lowers the arm, and - // the value is the same on both sides (only Z mirrors between them). - 'arm.l': [-62, 0, 0], - 'arm.r': [-62, 0, 0], - }, - }, - }, - { - id: 'bow', - label: 'おじぎ', - pose: { bones: { master: [22, 0, 0], 'arm.l': [-62, 0, 0], 'arm.r': [-62, 0, 0] } }, - }, - { - id: 'deep-bow', - label: 'ふかくおじぎ', - pose: { bones: { master: [42, 0, 0], 'arm.l': [-62, 0, 0], 'arm.r': [-62, 0, 0] } }, - }, - { - id: 'tilt', - label: 'くびかしげ', - pose: { - bones: { - master: [0, 0, 16], - // The roll turns the whole body about its base, which swings the feet up - // and down, so the legs take the difference. The right-hand leg (on the - // left of the screen) is the one that is planted: it opens outwards a - // little and keeps the sole down. The left-hand leg bends back behind it. - 'legsupport.r': [8, 0, 0], - 'legsupport.l': [-6, 0, 14], - }, - }, - }, - { - id: 'wave', - label: 'てをふる', - pose: { - bones: { - master: [0, 0, -4], - // The left flipper goes up and slightly forward. Y is the roll about the - // flipper's own length, which is what turns the palm to face outwards. - 'arm.l': [84, 0, -6], - 'hand.l': [0, -30, 0], - }, - }, - }, - { - id: 'banzai', - label: 'ばんざい', - pose: { - bones: { - master: [-8, 0, 0], - // Both flippers as far up as the shoulder allows (the pivot sits at the - // middle of the body, so they cannot reach above the head). - 'arm.l': [106, 0, -12], - 'arm.r': [106, 0, 12], - }, - root: [0, 0.2, 0], - }, - }, - { - id: 'stretch', - label: 'のびをする', - pose: { - bones: { - master: [-16, 0, 0], - 'arm.l': [112, 0, -8], - 'arm.r': [112, 0, 8], - }, - root: [0, 0.08, 0], - }, - }, - { - id: 'jump', - label: 'ジャンプ', - pose: { - bones: { - master: [-8, 0, 0], - 'arm.l': [68, 0, -10], - 'arm.r': [68, 0, 10], - 'legsupport.l': [10, 0, 0], - 'legsupport.r': [10, 0, 0], - }, - root: [0, 0.62, 0], - }, - }, - { - id: 'leap', - label: '大ジャンプ', - pose: { - bones: { - master: [-18, 0, 0], - 'arm.l': [92, 0, -12], - 'arm.r': [92, 0, 12], - 'legsupport.l': [14, 0, 0], - 'legsupport.r': [14, 0, 0], - }, - root: [0, 1.0, 0], - }, - }, - { - id: 'dance', - label: 'おどる', - pose: { - bones: { - master: [-4, 0, -12], - // Deliberately uneven: the left flipper is up, the right one is out in - // front, and the feet step the other way. - 'arm.l': [78, 0, -14], - 'arm.r': [14, 0, 34], - 'legsupport.l': [0, 0, 8], - 'legsupport.r': [0, 0, 8], - }, - root: [0, 0.14, 0], - }, - }, - { - id: 'tip-over', - label: 'こてん', - pose: { - bones: { - master: [0, 0, 68], - 'arm.l': [26, 0, -18], - 'arm.r': [10, 0, 18], - }, - root: [0, 0.25, 0], - }, - }, - { - id: 'lie', - label: 'ねている', - pose: { - bones: { - // Flat on its back: X tips the body backwards all the way over, so the - // face looks up. The root lift keeps the back resting on the ground - // rather than sinking through it (the body's depth becomes its height). - master: [-88, 0, 0], - // Both flippers rest against the body, loose. - 'arm.l': [-18, 0, 0], - 'arm.r': [-18, 0, 0], - }, - root: [0, 1.5, 0], - }, - }, - { id: 'side', label: 'よこむき', pose: { bones: { master: [0, 92, 0] } } }, - { id: 'turn', label: 'うしろむき', pose: { bones: { master: [0, 180, 0] } } }, - { - id: 'present', - label: '右を紹介', - pose: { - bones: { - master: [6, -26, 0], - // The right flipper sweeps forward to point at whatever is on its right - // (+Z is forwards on the right); the left one just steps aside. - 'arm.r': [16, 0, 52], - 'arm.l': [10, 0, -6], - }, - }, - }, - { - id: 'present-left', - label: '左を紹介', - pose: { - bones: { - master: [6, 26, 0], - 'arm.l': [16, 0, -52], - 'arm.r': [10, 0, 6], - }, - }, - }, -]; - -/** Deep-merge `patch` into `target` (plain objects only). */ -export function applyPatch(target, patch) { - for (const [key, value] of Object.entries(patch ?? {})) { - if (value && typeof value === 'object' && !Array.isArray(value) - && target[key] && typeof target[key] === 'object' && !Array.isArray(target[key])) { - applyPatch(target[key], value); - } else { - target[key] = Array.isArray(value) ? value.slice() : value; - } - } - return target; -} - -export function cloneState(state) { - return JSON.parse(JSON.stringify(state)); -} - -/** - * Body colour themes (C-1). Each is a full set of part colours, so picking one - * is a single assignment; the individual pickers in the panel then let you drift - * away from it without losing the rest. - * - * ぶるべー is a leaf-sprout mascot, so the themes are named after plants and - * weather rather than after colours. - */ -export const THEMES = [ - { - id: 'original', - label: 'オリジナル', - colors: { body: '#c8b0f0', accent: '#8a4fe0', nose: '#7a4fb0', leaf: '#a6dd6a', vein: '#7fbf3f', feet: '#7a4fb0' }, - }, - { - id: 'sakura', - label: 'さくら', - colors: { body: '#f7c6d9', accent: '#e0709b', nose: '#c25b7e', leaf: '#b8e08a', vein: '#8dbb54', feet: '#c25b7e' }, - }, - { - id: 'matcha', - label: 'まっちゃ', - colors: { body: '#cfe3ac', accent: '#6f9c3a', nose: '#5c8030', leaf: '#9fd463', vein: '#79ab3c', feet: '#5c8030' }, - }, - { - id: 'sora', - label: 'そら', - colors: { body: '#bcd9f7', accent: '#4a7fd4', nose: '#3f6cae', leaf: '#a8e08a', vein: '#7cbb58', feet: '#3f6cae' }, - }, - { - id: 'lemon', - label: 'レモン', - colors: { body: '#f8e9a6', accent: '#d8a520', nose: '#b3871a', leaf: '#b9de74', vein: '#8fbb45', feet: '#b3871a' }, - }, - { - id: 'momo', - label: 'もも', - colors: { body: '#f9cbae', accent: '#e07a4a', nose: '#bd6238', leaf: '#aede7c', vein: '#84b84e', feet: '#bd6238' }, - }, - { - id: 'sumi', - label: 'すみ', - colors: { body: '#9aa2b8', accent: '#4b5468', nose: '#3c4356', leaf: '#8fbf7a', vein: '#6d9a58', feet: '#3c4356' }, - }, - { - id: 'yozora', - label: 'よぞら', - colors: { body: '#8f8fd0', accent: '#403f8f', nose: '#34336f', leaf: '#7fc7b0', vein: '#5aa08c', feet: '#34336f' }, - }, -]; - -/** Bubble shapes offered for captions. */ -export const BUBBLE_STYLES = [ - { value: 'round', label: 'まる' }, - { value: 'rect', label: 'しかく' }, - { value: 'shout', label: 'さけぶ' }, - { value: 'whisper', label: 'ささやき' }, - { value: 'think', label: '思考' }, - { value: 'none', label: 'なし' }, -]; - -/** A couple of ready-made captions, so the feature is discoverable. */ -export const CAPTION_PRESETS = [ - { id: 'greeting', label: 'あいさつ', caption: { enabled: true, text: 'こんにちは、ぶるべーです。' } }, - { id: 'explain', label: '説明', caption: { enabled: true, text: 'ここがポイントです。', bubble: 'rect', tail: 'left' } }, - { id: 'surprise', label: 'びっくり', caption: { enabled: true, text: 'えっ!?', bubble: 'shout', tail: 'left', fontSize: 40, bold: true } }, -]; -- cgit v1.3.1