fix: per-species scale correction for skinned mesh fish

Box3.setFromObject measures skinned geometry in skeleton space (and may
use a stale cached bounding box), which does not match the GPU's actual
rendered extent (mesh.matrixWorld * skinning * position). Add a
per-species baseScale correction factor (rendered / box3) to FishSpec and
fold it into buildFish's scale calc so each GLB normalizes to its true
size.

Measured per GLB with three.js's exact skinning formula: most models
are 1.0. The skinned blendswap oscar (1.35) and knifefish (2.58) deviate
and are corrected. The clownfish GLB is a full scene (water 'Surface'
45.3, 'TopLight' 15, 'Bubbles' 3.4) plus a small skinned body (~8.3);
box3 is dominated by the water plane, so a baseScale can't fix it without
blowing up the scene geometry - left at 1.0 pending a clean re-export.
This commit is contained in:
2026-10-07 15:19:16 +00:00
parent 8c4a7d45a5
commit a1fd66d039
2 changed files with 38 additions and 1 deletions
+12 -1
View File
@@ -20,6 +20,16 @@ export interface FishSpec {
orient?: { x?: number; y?: number; z?: number };
speed?: Range;
genetics?: Genetics;
/**
* Correction factor between the extent reported by `Box3.setFromObject`
* and the fish's true rendered extent. `Box3.setFromObject` measures
* skinned geometry in skeleton space (and may use a stale cached
* bounding box), which does not match what the GPU actually renders
* (`mesh.matrixWorld * skinning * position`). baseScale = rendered / box3,
* so the final scale becomes `targetLength / (box3 * baseScale) =
* targetLength / rendered`, normalizing every fish to its real size.
*/
baseScale?: number;
}
// ---- Helpers ----
@@ -132,7 +142,8 @@ function buildFish(gltf: GLTF, spec: FishSpec): Fish {
const box = new THREE.Box3().setFromObject(model);
const size = box.getSize(new THREE.Vector3());
const longest = Math.max(size.x, size.y, size.z);
const scale = (spec.targetLength / longest) * rand(0.92, 1.08);
const baseScale = spec.baseScale ?? 1.0;
const scale = (spec.targetLength / (longest * baseScale)) * rand(0.92, 1.08);
orient.scale.setScalar(scale);
orient.add(model);