aboutsummaryrefslogtreecommitdiffhomepage
path: root/public/bluebey-studio/src/look.js
diff options
context:
space:
mode:
authorYasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com>2026-10-07 23:09:40 +0900
committerYasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com>2026-10-07 23:09:40 +0900
commitbc2821adb11a30244fc4663f4fafb756877d9508 (patch)
treefea8d30d2eb1e67f895097dbf7ec4d152361243e /public/bluebey-studio/src/look.js
parent0d50c5ede0812ba7b67b775c9cd0bf52d7b65e02 (diff)
bluebey-studio: public/ の外へ移動し非公開化
Diffstat (limited to 'public/bluebey-studio/src/look.js')
-rw-r--r--public/bluebey-studio/src/look.js184
1 files changed, 0 insertions, 184 deletions
diff --git a/public/bluebey-studio/src/look.js b/public/bluebey-studio/src/look.js
deleted file mode 100644
index d755c3b..0000000
--- a/public/bluebey-studio/src/look.js
+++ /dev/null
@@ -1,184 +0,0 @@
-import * as THREE from 'three';
-import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
-
-/**
- * The "look" of the scene, as opposed to the character's shape: body colours,
- * mirroring, shadows and the lighting environment.
- *
- * These all live here rather than in `main.js` because they are a *policy* about
- * how the model should be presented, and they need to be re-applied as a group
- * whenever any of them changes. `main.js` only has to call `apply()`.
- *
- * Two things are worth knowing:
- *
- * - Colours are written onto the model's ORIGINAL materials. The flat/toon
- * style caches its own materials derived from them, so `styles.refreshColors()`
- * has to be called afterwards or the toon shading keeps the old colour.
- *
- * - Shadow softness is `LightShadow.radius` with a PCF soft shadow map, plus a
- * hand-drawn contact blob for the "ground shadow only" mode. (VSM was tried
- * for its wider blur and cut the shadow off in a straight line where it met
- * the feet - see the note in `apply`.)
- */
-
-export const ENVIRONMENTS = [
- { value: 'gradient', label: 'スタジオ(明るい)' },
- { value: 'room', label: '室内(自然な反射)' },
- { value: 'none', label: 'なし(のっぺり)' },
-];
-
-/** Material name -> which entry of `render.colors` it takes. */
-const PART_COLORS = {
- blb: 'body',
- Hand: 'accent',
- Foots: 'feet',
- Nose: 'nose',
- Leaf: 'leaf',
- Vein: 'vein',
-};
-
-const clamp01 = (value) => Math.min(1, Math.max(0, Number(value) || 0));
-
-/**
- * A soft blob that sits under the feet: the shadow a toy would cast on a table.
- * It is a plain plane with a radial gradient, so it is cheap and it works in the
- * line-art styles too, where the real shadow map is switched off.
- */
-function makeContactShadow(size) {
- const canvas = document.createElement('canvas');
- canvas.width = 128;
- canvas.height = 128;
- const ctx = canvas.getContext('2d');
- const gradient = ctx.createRadialGradient(64, 64, 4, 64, 64, 62);
- gradient.addColorStop(0, 'rgba(0,0,0,0.55)');
- gradient.addColorStop(0.55, 'rgba(0,0,0,0.28)');
- gradient.addColorStop(1, 'rgba(0,0,0,0)');
- ctx.fillStyle = gradient;
- ctx.fillRect(0, 0, 128, 128);
-
- const texture = new THREE.CanvasTexture(canvas);
- texture.colorSpace = THREE.SRGBColorSpace;
- const material = new THREE.MeshBasicMaterial({
- map: texture,
- transparent: true,
- depthWrite: false,
- toneMapped: false,
- });
- const mesh = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), material);
- mesh.rotation.x = -Math.PI / 2;
- mesh.renderOrder = -0.5;
- mesh.userData.isHelper = true;
- mesh.name = 'contact-shadow';
- mesh.scale.setScalar(Math.max(1, size.x) * 1.15);
- return mesh;
-}
-
-export function createLook({ renderer, scene, styles, model, ground, key, character, gradientEnvironment }) {
- const pmrem = new THREE.PMREMGenerator(renderer);
- const roomEnvironment = pmrem.fromScene(new RoomEnvironment(), 0.06).texture;
-
- const contact = makeContactShadow(model.size);
- scene.add(contact);
-
- const environmentFor = (name) => {
- if (name === 'room') return roomEnvironment;
- if (name === 'none') return null;
- return gradientEnvironment ?? null;
- };
-
- function applyColors(colors) {
- if (!colors) return;
- let touched = false;
- for (const mesh of model.parts.body) {
- const material = styles.originals.get(mesh) ?? mesh.material;
- const part = PART_COLORS[material?.name];
- if (!part || !colors[part]) continue;
- if (!material.color) material.color = new THREE.Color();
- material.color.set(colors[part]);
- touched = true;
- }
- // The toon materials are cached copies, so they need the same edit.
- if (touched) styles.refreshColors?.();
- return touched;
- }
-
- return {
- contact,
-
- /**
- * @param {object} render `state.render`
- * @param {{ line?: boolean }} [context] `line` is true in the line-art
- * styles, where shadows and reflections are deliberately switched off.
- */
- apply(render, { line = false } = {}) {
- if (!render) return;
- applyColors(render.colors);
-
- // Mirroring by negative scale is safe: the renderer flips the winding for
- // a negative determinant, and the normals go through the inverse-transpose.
- character.scale.x = render.mirror ? -1 : 1;
-
- const wantsShadow = render.shadow !== false && !line;
- const blobOnly = render.contactShadow === true;
- ground.visible = wantsShadow && !blobOnly;
- ground.material.opacity = clamp01(render.shadowOpacity ?? 0.22);
- key.castShadow = wantsShadow && !blobOnly;
- key.shadow.needsUpdate = true;
-
- // The blob is drawn in BOTH modes. On its own it *is* the shadow (the
- // "ground shadow only" setting); with the shadow map on it fills the gap
- // where the body hides its own shadow right at the feet, which otherwise
- // reads as "the shadow is cut off". It is the only thing keeping the
- // character looking like it is standing on the floor rather than above it.
- // The blob sits at the feet. It used to be drawn whenever the shadow was on,
- // which left a faint ring behind at the origin once the character was moved;
- // it is now opt-in (`render.contactBlob`, default off). It is always shown in
- // the 「接地影だけ」 mode, where it is the only shadow there is.
- contact.visible = wantsShadow && (blobOnly || render.contactBlob === true);
- contact.material.opacity = clamp01((render.shadowOpacity ?? 0.22) * (blobOnly ? 2.6 : 1.6));
- const spread = Math.max(0.2, (model.size.x * 1.15) / Math.max(0.05, render.shadowSoftness ?? 1.6));
- contact.scale.setScalar(Math.max(0.5, model.size.x * 1.35 - spread * 0.25));
- contact.position.y = 0.012;
-
- const softness = Number(render.shadowSoftness) || 1.6;
- // PCF, always. three 0.186 removed PCFSoftShadowMap (asking for it warns and
- // falls back to this anyway), and VSM needs a depth-variance bias that cut
- // the shadow away in a straight line right at the feet. `radius` is what
- // softens PCF.
- if (renderer.shadowMap.type !== THREE.PCFShadowMap) {
- renderer.shadowMap.type = THREE.PCFShadowMap;
- // Every shadow-receiving material has to be recompiled for the switch.
- ground.material.needsUpdate = true;
- for (const mesh of model.parts.body) {
- const material = mesh.material;
- if (Array.isArray(material)) material.forEach((m) => { m.needsUpdate = true; });
- else if (material) material.needsUpdate = true;
- }
- }
- // PCF's radius is in *shadow map texels*, and the map covers about 15 world
- // units, so a radius of 1-2 is invisible. This scales it into something the
- // eye can see without the sampling turning into noise.
- if ('radius' in key.shadow) key.shadow.radius = Math.max(0.5, softness * 5);
-
- const environment = line ? null : environmentFor(render.environment);
- if (scene.environment !== environment) scene.environment = environment;
- if ('environmentIntensity' in scene) {
- scene.environmentIntensity = Number(render.envIntensity ?? 1);
- }
- },
-
- /** The colours a theme would set, as a plain `{ part: '#rrggbb' }`. */
- palette(theme) {
- return { ...(theme?.colors ?? {}) };
- },
-
- dispose() {
- contact.geometry.dispose();
- contact.material.map?.dispose();
- contact.material.dispose();
- contact.removeFromParent();
- roomEnvironment.dispose();
- pmrem.dispose();
- },
- };
-}