115 lines
4.6 KiB
JavaScript
115 lines
4.6 KiB
JavaScript
import * as THREE from 'three';
|
|
import { V3, assert } from '../core/math.js';
|
|
|
|
// [name, parent, local offset] — rest local rotations are all identity, so the
|
|
// rest pose is an A-pose and every rest world position falls out of the offsets.
|
|
export const BONEDEF = [
|
|
['root', null, [0, 0, 0]],
|
|
['pelvis', 'root', [0, 1.0, 0]],
|
|
['spine1', 'pelvis', [0, 0.09, 0.004]],
|
|
['spine2', 'spine1', [0, 0.12, 0.005]],
|
|
['spine3', 'spine2', [0, 0.13, 0.005]],
|
|
['neck', 'spine3', [0, 0.1, 0.012]],
|
|
['head', 'neck', [0, 0.075, 0.008]],
|
|
['clavicleL', 'spine3', [0.075, 0.048, 0]],
|
|
['upperArmL', 'clavicleL', [0.135, -0.022, 0]],
|
|
['forearmL', 'upperArmL', [0.15, -0.252, 0.01]],
|
|
['handL', 'forearmL', [0.105, -0.227, 0.016]],
|
|
['clavicleR', 'spine3', [-0.075, 0.048, 0]],
|
|
['upperArmR', 'clavicleR', [-0.135, -0.022, 0]],
|
|
['forearmR', 'upperArmR', [-0.15, -0.252, 0.01]],
|
|
['handR', 'forearmR', [-0.105, -0.227, 0.016]],
|
|
['thighL', 'pelvis', [0.105, -0.05, 0.005]],
|
|
['shinL', 'thighL', [0.02, -0.44, 0.006]],
|
|
['footL', 'shinL', [0.005, -0.437, -0.012]],
|
|
['toeL', 'footL', [-0.004, -0.055, 0.112]],
|
|
['thighR', 'pelvis', [-0.105, -0.05, 0.005]],
|
|
['shinR', 'thighR', [-0.02, -0.44, 0.006]],
|
|
['footR', 'shinR', [-0.005, -0.437, -0.012]],
|
|
['toeR', 'footR', [0.004, -0.055, 0.112]],
|
|
];
|
|
|
|
/** Child bone that defines each bone's capsule segment axis. */
|
|
export const SEG_CHILD = {
|
|
pelvis: 'spine1', spine1: 'spine2', spine2: 'spine3', spine3: 'neck', neck: 'head',
|
|
clavicleL: 'upperArmL', upperArmL: 'forearmL', forearmL: 'handL',
|
|
clavicleR: 'upperArmR', upperArmR: 'forearmR', forearmR: 'handR',
|
|
thighL: 'shinL', shinL: 'footL', footL: 'toeL',
|
|
thighR: 'shinR', shinR: 'footR', footR: 'toeR',
|
|
root: null, head: null, handL: null, handR: null, toeL: null, toeR: null,
|
|
};
|
|
|
|
/** Per-bone skin influence radius for the capsule falloff. */
|
|
export const BONE_RADIUS = {
|
|
root: 0.2, pelvis: 0.175, spine1: 0.165, spine2: 0.17, spine3: 0.175, neck: 0.08, head: 0.125,
|
|
clavicleL: 0.07, upperArmL: 0.078, forearmL: 0.068, handL: 0.06,
|
|
clavicleR: 0.07, upperArmR: 0.078, forearmR: 0.068, handR: 0.06,
|
|
thighL: 0.125, shinL: 0.098, footL: 0.075, toeL: 0.055,
|
|
thighR: 0.125, shinR: 0.098, footR: 0.075, toeR: 0.055,
|
|
};
|
|
|
|
/**
|
|
* Body regions from GDD 5.3. Every bone belongs to exactly one region, and
|
|
* damage, armor coverage and ragdoll limb-disable all key off these.
|
|
*/
|
|
export const REGION = {
|
|
HEAD: 'head',
|
|
TORSO: 'torso',
|
|
UPPER_ARM_L: 'upperArmL', LOWER_ARM_L: 'lowerArmL',
|
|
UPPER_ARM_R: 'upperArmR', LOWER_ARM_R: 'lowerArmR',
|
|
UPPER_LEG_L: 'upperLegL', LOWER_LEG_L: 'lowerLegL',
|
|
UPPER_LEG_R: 'upperLegR', LOWER_LEG_R: 'lowerLegR',
|
|
};
|
|
|
|
export const BONE_REGION = {
|
|
head: REGION.HEAD, neck: REGION.HEAD,
|
|
pelvis: REGION.TORSO, spine1: REGION.TORSO, spine2: REGION.TORSO, spine3: REGION.TORSO,
|
|
clavicleL: REGION.TORSO, clavicleR: REGION.TORSO,
|
|
upperArmL: REGION.UPPER_ARM_L, forearmL: REGION.LOWER_ARM_L, handL: REGION.LOWER_ARM_L,
|
|
upperArmR: REGION.UPPER_ARM_R, forearmR: REGION.LOWER_ARM_R, handR: REGION.LOWER_ARM_R,
|
|
thighL: REGION.UPPER_LEG_L, shinL: REGION.LOWER_LEG_L, footL: REGION.LOWER_LEG_L, toeL: REGION.LOWER_LEG_L,
|
|
thighR: REGION.UPPER_LEG_R, shinR: REGION.LOWER_LEG_R, footR: REGION.LOWER_LEG_R, toeR: REGION.LOWER_LEG_R,
|
|
};
|
|
|
|
export function buildSkeleton() {
|
|
const bones = {};
|
|
const list = [];
|
|
for (const [name, parentName, off] of BONEDEF) {
|
|
const b = new THREE.Bone();
|
|
b.name = name;
|
|
b.position.set(off[0], off[1], off[2]);
|
|
if (parentName) bones[parentName].add(b);
|
|
bones[name] = b;
|
|
list.push(b);
|
|
}
|
|
const root = bones.root;
|
|
root.updateMatrixWorld(true);
|
|
const restWorld = {};
|
|
for (const b of list) restWorld[b.name] = b.getWorldPosition(new THREE.Vector3());
|
|
const skeleton = new THREE.Skeleton(list);
|
|
const index = {};
|
|
list.forEach((b, i) => { index[b.name] = i; });
|
|
return { bones, list, index, skeleton, restWorld, rootBone: root };
|
|
}
|
|
|
|
/** The capsule segment a bone deforms, in rest world space. */
|
|
export function boneSegment(name, restWorld) {
|
|
const a = restWorld[name];
|
|
const child = SEG_CHILD[name];
|
|
let b;
|
|
if (child) b = restWorld[child];
|
|
else if (name === 'head') b = a.clone().add(V3(0, 0.15, 0.012));
|
|
else if (name.startsWith('hand')) {
|
|
const s = name.endsWith('L') ? 1 : -1;
|
|
b = a.clone().add(V3(s * 0.045, -0.095, 0.008));
|
|
} else b = a.clone().add(V3(0, -0.012, 0.085)); // toes
|
|
return { a, b, r: BONE_RADIUS[name] };
|
|
}
|
|
|
|
export function assertNoNaNBones(skelData) {
|
|
for (const b of skelData.list) {
|
|
const e = b.matrixWorld.elements;
|
|
for (let i = 0; i < 16; i++) assert(Number.isFinite(e[i]), 'NaN in bone matrix ' + b.name);
|
|
}
|
|
}
|