Initial commit
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user