aboutsummaryrefslogtreecommitdiffhomepage
path: root/public/bluebey-studio/src/props.js
diff options
context:
space:
mode:
Diffstat (limited to 'public/bluebey-studio/src/props.js')
-rw-r--r--public/bluebey-studio/src/props.js63
1 files changed, 42 insertions, 21 deletions
diff --git a/public/bluebey-studio/src/props.js b/public/bluebey-studio/src/props.js
index 5ad958a..a5fcbcc 100644
--- a/public/bluebey-studio/src/props.js
+++ b/public/bluebey-studio/src/props.js
@@ -192,19 +192,28 @@ const buildMic = prop((group, material) => {
});
/**
- * 看板. A post, a frame and a blank face. The face is a separate, named child
- * tagged `userData.canvasTexture`, so a later feature can paint a CanvasTexture
- * onto it without touching the frame - and `applyPropFaceScale` can grow just
- * that surface, about its own centre, leaving the frame and posts alone. It is a
- * thin box rather than a plane: the outline pass offsets geometry along its
- * normals, and a zero-thickness plane gets a visibly doubled rim.
+ * 看板. A post, a frame and a blank face. The face is a named child tagged
+ * `userData.canvasTexture`, so a later feature can paint a CanvasTexture onto it
+ * without hunting for the writable mesh. The face *and* its frame are grouped,
+ * so `applyPropFaceScale` can grow the whole board about the face's centre while
+ * the post and base stay put. Both are thin boxes rather than planes: the outline
+ * pass offsets geometry along its normals, and a zero-thickness plane gets a
+ * visibly doubled rim.
*/
const buildSign = prop((group, material) => {
addBox(group, material('accent'), [0.52, 0.10, 0.52], [0, 0.05, 0]);
addBox(group, material('wood'), [0.16, 1.62, 0.16], [0, 0.91, 0]);
- addBox(group, material('accent'), [1.42, 0.98, 0.12], [0, 2.21, 0]);
- const face = addBox(group, material('paper'), [1.18, 0.76, 0.08], [0, 2.21, 0.06]);
+ // The board is one group so the face and its frame scale together, about the
+ // board's own centre - which is also the face's centre.
+ const board = new THREE.Group();
+ board.name = 'sign-board';
+ board.userData.faceScaleGroup = true;
+ board.position.set(0, 2.21, 0);
+ group.add(board);
+
+ addBox(board, material('accent'), [1.42, 0.98, 0.12], [0, 0, 0]);
+ const face = addBox(board, material('paper'), [1.18, 0.76, 0.08], [0, 0, 0.06]);
face.name = 'sign-face';
face.userData.canvasTexture = true;
});
@@ -284,14 +293,19 @@ const buildCan = prop((group, material) => {
addCylinder(group, material('metal', 0.45), 0.132, 0.132, 0.012, [0, 0.422, 0], 16);
// The one lid, peeled back on a hinge at the back of the mouth so its
- // underside shows. It is twisted slightly sideways so it does not read as a
- // knob sitting straight up on the can.
+ // underside shows. The hinge sits *on* the can's back rim and the disc is
+ // placed so its own rim passes through that same point (its centre is one
+ // radius in front of the hinge), so opening the hinge rotates the lid about
+ // its edge - the two rims stay joined instead of the lid floating in mid-air.
+ // It is twisted slightly sideways so it does not read as a knob sitting
+ // straight up on the can.
+ const lidRadius = 0.132;
const hinge = new THREE.Group();
- hinge.position.set(0, 0.42, -0.135);
+ hinge.position.set(0, 0.42, -lidRadius);
hinge.rotation.x = -1.05;
hinge.rotation.z = 0.4;
group.add(hinge);
- addCylinder(hinge, material('metal', 0.9), 0.132, 0.132, 0.018, [0, 0.12, 0], 18);
+ addCylinder(hinge, material('metal', 0.9), lidRadius, lidRadius, 0.018, [0, 0, lidRadius], 18);
group.rotation.z = 0.05;
});
@@ -327,8 +341,9 @@ export const PROP_DEFAULTS = {
podium: { x: -2.1, y: 0, z: 1.9, rotY: 0.30, scale: 1 },
desk: { x: -2.6, y: 0, z: 0.5, rotY: 0.40, scale: 1 },
mic: { x: 2.0, y: 0, z: 1.8, rotY: -0.40, scale: 1 },
- // `faceScale` only means something for a 看板: it grows the writing surface
- // alone, so the frame and posts stay put. The panel owns that control.
+ // `faceScale` only means something for a 看板: it grows the writing area (the
+ // face and its frame) together, so the post and base stay put. The panel owns
+ // that control.
sign: { x: 2.9, y: 0, z: 0.4, rotY: -0.25, scale: 1, faceScale: 1 },
plant: { x: -2.0, y: 0, z: -1.8, rotY: 0.25, scale: 1 },
box: { x: 2.0, y: 0, z: -1.7, rotY: 0.55, scale: 1 },
@@ -419,21 +434,27 @@ export function propFace(group) {
}
/**
- * Scale the writing surface about its own centre, so only the 看板's face grows
- * and its frame and posts stay where they are. `addBox` puts the mesh's origin at
- * the box centre, which is what makes this a scale about the centre rather than
- * about the prop's base.
+ * Scale the writing area - the 看板's face *and* the frame around it - about the
+ * board's own centre, so the post and base stay where they are. A prop that tags
+ * a group `faceScaleGroup` is scaled as a whole; anything else falls back to the
+ * writable mesh alone. `addBox` puts a mesh's origin at the box centre and the
+ * 看板's board group sits at the face centre, which is what makes this a scale
+ * about the centre rather than about the prop's base.
*
* WHY this is applied from the panel: the app rebuilds every prop from the state
* (`applyProps` in src/main.js) and hands `buildProp` only the whole-prop scale,
* so a per-item `faceScale` has to be re-applied to the fresh mesh afterwards.
*/
export function applyPropFaceScale(group, faceScale) {
- const face = propFace(group);
- if (!face) return;
+ let target = null;
+ group?.traverse?.((object) => {
+ if (!target && object.userData?.faceScaleGroup) target = object;
+ });
+ target ??= propFace(group);
+ if (!target) return;
const value = Number(faceScale);
const scale = Number.isFinite(value) ? Math.min(4, Math.max(0.2, value)) : 1;
- face.scale.setScalar(scale);
+ target.scale.setScalar(scale);
}
/** The world-space box the prop occupies; the app drops its contact shadow from it. */