Files
SlimeVR-Server/gui/src/utils/skeletonHelper.ts
lucas lelievre 2e1ec07b23 Session flightlist (#1407)
Co-authored-by: sctanf <36978460+sctanf@users.noreply.github.com>
Co-authored-by: Butterscotch! <bscotchvanilla@gmail.com>
Co-authored-by: Aed <145398159+Aed-1@users.noreply.github.com>
2025-12-02 14:49:45 +01:00

521 lines
17 KiB
TypeScript

import { Bone, Color, Matrix4, Object3D, Vector2, Vector3 } from 'three';
import { LineSegments2 } from 'three/examples/jsm/lines/LineSegments2';
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js';
import { LineSegmentsGeometry } from 'three/examples/jsm/lines/LineSegmentsGeometry.js';
import { BodyPart, BoneT } from 'solarxr-protocol';
import { Vector3FromVec3fT } from '@/maths/vector3';
import { QuaternionFromQuatT } from '@/maths/quaternion';
const _vector = new Vector3();
const _boneMatrix = new Matrix4();
const _matrixWorldInv = new Matrix4();
export class BasedSkeletonHelper extends LineSegments2 {
isSkeletonHelper: boolean;
root: Object3D;
bones: Bone[];
readonly type = 'SkeletonHelper';
get resolution() {
return this.material.resolution;
}
set resolution(v: Vector2) {
this.material.resolution = v;
}
constructor(object: Object3D) {
const bones = getBoneList(object);
const geometry = new LineSegmentsGeometry();
const vertices = [];
const colors = [];
const color1 = new Color(0, 0, 1);
const color2 = new Color(0, 1, 0);
for (let i = 0; i < bones.length; i++) {
const bone = bones[i];
if (bone.parent && (bone.parent as Bone).isBone) {
vertices.push(0, 0, 0);
vertices.push(0, 0, 0);
if (bone.parent instanceof BoneKind) {
const color = bone.parent.boneColor;
colors.push(color.r, color.g, color.b);
colors.push(color.r, color.g, color.b);
} else {
colors.push(color1.r, color1.g, color1.b);
colors.push(color2.r, color2.g, color2.b);
}
}
}
geometry.setPositions(vertices);
geometry.setColors(colors);
const material = new LineMaterial({
vertexColors: true,
// depthTest: false,
// depthWrite: false,
toneMapped: false,
transparent: true,
linewidth: 4,
});
super(geometry, material);
this.isSkeletonHelper = true;
this.root = object;
this.bones = bones;
this.matrix = object.matrixWorld;
this.matrixAutoUpdate = false;
}
updateMatrixWorld(force: boolean) {
const bones = this.bones;
const geometry = this.geometry;
const vertices = [];
_matrixWorldInv.copy(this.root.matrixWorld).invert();
for (let i = 0; i < bones.length; i++) {
const bone = bones[i];
if (bone.parent && (bone.parent as Bone).isBone) {
_boneMatrix.multiplyMatrices(_matrixWorldInv, bone.parent.matrixWorld);
_vector.setFromMatrixPosition(_boneMatrix);
vertices.push(_vector.x, _vector.y, _vector.z);
_boneMatrix.multiplyMatrices(_matrixWorldInv, bone.matrixWorld);
_vector.setFromMatrixPosition(_boneMatrix);
vertices.push(_vector.x, _vector.y, _vector.z);
}
}
geometry.setPositions(vertices);
super.updateMatrixWorld(force);
}
dispose() {
this.geometry.dispose();
if (Array.isArray(this.material)) {
this.material.forEach((x) => x.dispose());
} else {
this.material.dispose();
}
}
}
function getBoneList(object: Object3D): Bone[] {
const boneList: Bone[] = [];
if ((object as Bone).isBone) {
boneList.push(object as Bone);
}
for (let i = 0; i < object.children.length; i++) {
boneList.push(...getBoneList(object.children[i]));
}
return boneList;
}
export class BoneKind extends Bone {
boneT: BoneT;
tail: boolean;
constructor(bones: Map<BodyPart, BoneT>, bodyPart: BodyPart, tail: boolean) {
super();
const bone = bones.get(bodyPart);
if (!bone) {
throw 'Couldnt find bone ' + BodyPart[bodyPart];
}
this.boneT = bone;
this.name = BodyPart[bodyPart];
this.tail = tail;
this.updateData(bones);
}
updateData(bones: Map<BodyPart, BoneT>) {
this.boneT = bones.get(this.boneT.bodyPart) ?? this.boneT;
const parent = BoneKind.parent(this.boneT.bodyPart);
const parentBone = parent === null ? undefined : bones.get(parent);
if (this.boneT.bodyPart === BoneKind.root) {
this.position.set(0, this.boneT.headPositionG?.y ?? 0, 0);
return;
}
if (!this.tail) {
const localPosition = Vector3FromVec3fT(this.boneT.headPositionG).sub(
Vector3FromVec3fT(
parentBone === undefined
? new Vector3(0, 0, 0)
: Vector3FromVec3fT(parentBone.headPositionG)
)
);
this.position.copy(localPosition);
return;
}
const quat = QuaternionFromQuatT(this.boneT.rotationG)
// .normalize()
// .multiply(
// parentBone === undefined
// ? new Quaternion().identity()
// : QuaternionFromQuatT(parentBone.rotationG).normalize().invert().normalize()
// )
.normalize();
// console.log(this.quaternion);
// console.log(
// parentBone === undefined
// ? new Vector3(0, 0, 0)
// : Vector3FromVec3fT(parentBone.headPositionG),
// Vector3FromVec3fT(this.boneT.headPositionG)
// );
this.position.set(0, -this.boneT.boneLength, 0);
this.position.applyQuaternion(quat);
// console.log(this.position);
}
get boneColor(): Color {
switch (this.boneT.bodyPart) {
case BodyPart.NONE:
throw 'Unexpected body part';
case BodyPart.HEAD:
return new Color('gold');
case BodyPart.NECK:
return new Color('silver');
case BodyPart.UPPER_CHEST:
return new Color('chartreuse');
case BodyPart.CHEST:
return new Color('purple');
case BodyPart.WAIST:
return new Color('red');
case BodyPart.HIP:
return new Color('orange');
case BodyPart.LEFT_UPPER_LEG:
case BodyPart.RIGHT_UPPER_LEG:
return new Color('chartreuse');
case BodyPart.LEFT_LOWER_LEG:
case BodyPart.RIGHT_LOWER_LEG:
return new Color('teal');
case BodyPart.LEFT_FOOT:
case BodyPart.RIGHT_FOOT:
return new Color('gold');
case BodyPart.LEFT_LOWER_ARM:
case BodyPart.RIGHT_LOWER_ARM:
return new Color('red');
case BodyPart.LEFT_UPPER_ARM:
case BodyPart.RIGHT_UPPER_ARM:
return new Color('indianred');
case BodyPart.LEFT_HAND:
case BodyPart.RIGHT_HAND:
return new Color('fuchsia');
case BodyPart.LEFT_SHOULDER:
case BodyPart.RIGHT_SHOULDER:
return new Color('#00ffff');
case BodyPart.LEFT_HIP:
case BodyPart.RIGHT_HIP:
return new Color('pink');
case BodyPart.LEFT_THUMB_METACARPAL:
case BodyPart.LEFT_THUMB_PROXIMAL:
case BodyPart.LEFT_THUMB_DISTAL:
case BodyPart.LEFT_INDEX_PROXIMAL:
case BodyPart.LEFT_INDEX_INTERMEDIATE:
case BodyPart.LEFT_INDEX_DISTAL:
case BodyPart.LEFT_MIDDLE_PROXIMAL:
case BodyPart.LEFT_MIDDLE_INTERMEDIATE:
case BodyPart.LEFT_MIDDLE_DISTAL:
case BodyPart.LEFT_RING_PROXIMAL:
case BodyPart.LEFT_RING_INTERMEDIATE:
case BodyPart.LEFT_RING_DISTAL:
case BodyPart.LEFT_LITTLE_PROXIMAL:
case BodyPart.LEFT_LITTLE_INTERMEDIATE:
case BodyPart.LEFT_LITTLE_DISTAL:
case BodyPart.RIGHT_THUMB_METACARPAL:
case BodyPart.RIGHT_THUMB_PROXIMAL:
case BodyPart.RIGHT_THUMB_DISTAL:
case BodyPart.RIGHT_INDEX_PROXIMAL:
case BodyPart.RIGHT_INDEX_INTERMEDIATE:
case BodyPart.RIGHT_INDEX_DISTAL:
case BodyPart.RIGHT_MIDDLE_PROXIMAL:
case BodyPart.RIGHT_MIDDLE_INTERMEDIATE:
case BodyPart.RIGHT_MIDDLE_DISTAL:
case BodyPart.RIGHT_RING_PROXIMAL:
case BodyPart.RIGHT_RING_INTERMEDIATE:
case BodyPart.RIGHT_RING_DISTAL:
case BodyPart.RIGHT_LITTLE_PROXIMAL:
case BodyPart.RIGHT_LITTLE_INTERMEDIATE:
case BodyPart.RIGHT_LITTLE_DISTAL:
return new Color('pink');
}
}
static root: BodyPart = BodyPart.HEAD;
static children(part: BodyPart): BodyPart[] {
switch (part) {
case BodyPart.NONE:
throw 'Unexpected body part';
case BodyPart.HEAD:
return [BodyPart.NECK];
case BodyPart.NECK:
return [BodyPart.UPPER_CHEST, BodyPart.LEFT_SHOULDER, BodyPart.RIGHT_SHOULDER];
case BodyPart.UPPER_CHEST:
return [BodyPart.CHEST];
case BodyPart.CHEST:
return [BodyPart.WAIST];
case BodyPart.WAIST:
return [BodyPart.HIP];
case BodyPart.HIP:
return [BodyPart.LEFT_HIP, BodyPart.RIGHT_HIP];
case BodyPart.LEFT_HIP:
return [BodyPart.LEFT_UPPER_LEG];
case BodyPart.RIGHT_HIP:
return [BodyPart.RIGHT_UPPER_LEG];
case BodyPart.LEFT_UPPER_LEG:
return [BodyPart.LEFT_LOWER_LEG];
case BodyPart.RIGHT_UPPER_LEG:
return [BodyPart.RIGHT_LOWER_LEG];
case BodyPart.LEFT_LOWER_LEG:
return [BodyPart.LEFT_FOOT];
case BodyPart.RIGHT_LOWER_LEG:
return [BodyPart.RIGHT_FOOT];
case BodyPart.LEFT_FOOT:
return [];
case BodyPart.RIGHT_FOOT:
return [];
case BodyPart.LEFT_SHOULDER:
return [BodyPart.LEFT_UPPER_ARM];
case BodyPart.RIGHT_SHOULDER:
return [BodyPart.RIGHT_UPPER_ARM];
case BodyPart.LEFT_UPPER_ARM:
return [BodyPart.LEFT_LOWER_ARM];
case BodyPart.RIGHT_UPPER_ARM:
return [BodyPart.RIGHT_LOWER_ARM];
case BodyPart.LEFT_LOWER_ARM:
return [BodyPart.LEFT_HAND];
case BodyPart.RIGHT_LOWER_ARM:
return [BodyPart.RIGHT_HAND];
case BodyPart.LEFT_HAND:
return [
BodyPart.LEFT_THUMB_METACARPAL,
BodyPart.LEFT_INDEX_PROXIMAL,
BodyPart.LEFT_MIDDLE_PROXIMAL,
BodyPart.LEFT_RING_PROXIMAL,
BodyPart.LEFT_LITTLE_PROXIMAL,
];
case BodyPart.RIGHT_HAND:
return [
BodyPart.RIGHT_THUMB_METACARPAL,
BodyPart.RIGHT_INDEX_PROXIMAL,
BodyPart.RIGHT_MIDDLE_PROXIMAL,
BodyPart.RIGHT_RING_PROXIMAL,
BodyPart.RIGHT_LITTLE_PROXIMAL,
];
case BodyPart.LEFT_THUMB_METACARPAL:
return [BodyPart.LEFT_THUMB_PROXIMAL];
case BodyPart.LEFT_THUMB_PROXIMAL:
return [BodyPart.LEFT_THUMB_DISTAL];
case BodyPart.LEFT_THUMB_DISTAL:
return [];
case BodyPart.LEFT_INDEX_PROXIMAL:
return [BodyPart.LEFT_INDEX_INTERMEDIATE];
case BodyPart.LEFT_INDEX_INTERMEDIATE:
return [BodyPart.LEFT_INDEX_DISTAL];
case BodyPart.LEFT_INDEX_DISTAL:
return [];
case BodyPart.LEFT_MIDDLE_PROXIMAL:
return [BodyPart.LEFT_MIDDLE_INTERMEDIATE];
case BodyPart.LEFT_MIDDLE_INTERMEDIATE:
return [BodyPart.LEFT_MIDDLE_DISTAL];
case BodyPart.LEFT_MIDDLE_DISTAL:
return [];
case BodyPart.LEFT_RING_PROXIMAL:
return [BodyPart.LEFT_RING_INTERMEDIATE];
case BodyPart.LEFT_RING_INTERMEDIATE:
return [BodyPart.LEFT_RING_DISTAL];
case BodyPart.LEFT_RING_DISTAL:
return [];
case BodyPart.LEFT_LITTLE_PROXIMAL:
return [BodyPart.LEFT_LITTLE_INTERMEDIATE];
case BodyPart.LEFT_LITTLE_INTERMEDIATE:
return [BodyPart.LEFT_LITTLE_DISTAL];
case BodyPart.LEFT_LITTLE_DISTAL:
return [];
case BodyPart.RIGHT_THUMB_METACARPAL:
return [BodyPart.RIGHT_THUMB_PROXIMAL];
case BodyPart.RIGHT_THUMB_PROXIMAL:
return [BodyPart.RIGHT_THUMB_DISTAL];
case BodyPart.RIGHT_THUMB_DISTAL:
return [];
case BodyPart.RIGHT_INDEX_PROXIMAL:
return [BodyPart.RIGHT_INDEX_INTERMEDIATE];
case BodyPart.RIGHT_INDEX_INTERMEDIATE:
return [BodyPart.RIGHT_INDEX_DISTAL];
case BodyPart.RIGHT_INDEX_DISTAL:
return [];
case BodyPart.RIGHT_MIDDLE_PROXIMAL:
return [BodyPart.RIGHT_MIDDLE_INTERMEDIATE];
case BodyPart.RIGHT_MIDDLE_INTERMEDIATE:
return [BodyPart.RIGHT_MIDDLE_DISTAL];
case BodyPart.RIGHT_MIDDLE_DISTAL:
return [];
case BodyPart.RIGHT_RING_PROXIMAL:
return [BodyPart.RIGHT_RING_INTERMEDIATE];
case BodyPart.RIGHT_RING_INTERMEDIATE:
return [BodyPart.RIGHT_RING_DISTAL];
case BodyPart.RIGHT_RING_DISTAL:
return [];
case BodyPart.RIGHT_LITTLE_PROXIMAL:
return [BodyPart.RIGHT_LITTLE_INTERMEDIATE];
case BodyPart.RIGHT_LITTLE_INTERMEDIATE:
return [BodyPart.RIGHT_LITTLE_DISTAL];
case BodyPart.RIGHT_LITTLE_DISTAL:
return [];
}
}
static parent(part: BodyPart): BodyPart | null {
switch (part) {
case BodyPart.NONE:
throw 'Unexpected body part';
case BodyPart.HEAD:
return null;
case BodyPart.NECK:
return BodyPart.HEAD;
case BodyPart.UPPER_CHEST:
return BodyPart.NECK;
case BodyPart.CHEST:
return BodyPart.UPPER_CHEST;
case BodyPart.WAIST:
return BodyPart.CHEST;
case BodyPart.HIP:
return BodyPart.WAIST;
case BodyPart.LEFT_HIP:
case BodyPart.RIGHT_HIP:
return BodyPart.HIP;
case BodyPart.LEFT_UPPER_LEG:
return BodyPart.LEFT_HIP;
case BodyPart.RIGHT_UPPER_LEG:
return BodyPart.RIGHT_HIP;
case BodyPart.LEFT_LOWER_LEG:
return BodyPart.LEFT_UPPER_LEG;
case BodyPart.RIGHT_LOWER_LEG:
return BodyPart.RIGHT_UPPER_LEG;
case BodyPart.LEFT_FOOT:
return BodyPart.LEFT_LOWER_LEG;
case BodyPart.RIGHT_FOOT:
return BodyPart.RIGHT_LOWER_LEG;
case BodyPart.LEFT_SHOULDER:
case BodyPart.RIGHT_SHOULDER:
return BodyPart.NECK;
case BodyPart.LEFT_UPPER_ARM:
return BodyPart.LEFT_SHOULDER;
case BodyPart.RIGHT_UPPER_ARM:
return BodyPart.RIGHT_SHOULDER;
case BodyPart.LEFT_LOWER_ARM:
return BodyPart.LEFT_UPPER_ARM;
case BodyPart.RIGHT_LOWER_ARM:
return BodyPart.RIGHT_UPPER_ARM;
case BodyPart.LEFT_HAND:
return BodyPart.LEFT_LOWER_ARM;
case BodyPart.RIGHT_HAND:
return BodyPart.RIGHT_LOWER_ARM;
case BodyPart.LEFT_THUMB_METACARPAL:
return BodyPart.LEFT_HAND;
case BodyPart.LEFT_THUMB_PROXIMAL:
return BodyPart.LEFT_THUMB_METACARPAL;
case BodyPart.LEFT_THUMB_DISTAL:
return BodyPart.LEFT_THUMB_PROXIMAL;
case BodyPart.LEFT_INDEX_PROXIMAL:
return BodyPart.LEFT_HAND;
case BodyPart.LEFT_INDEX_INTERMEDIATE:
return BodyPart.LEFT_INDEX_PROXIMAL;
case BodyPart.LEFT_INDEX_DISTAL:
return BodyPart.LEFT_INDEX_INTERMEDIATE;
case BodyPart.LEFT_MIDDLE_PROXIMAL:
return BodyPart.LEFT_HAND;
case BodyPart.LEFT_MIDDLE_INTERMEDIATE:
return BodyPart.LEFT_MIDDLE_PROXIMAL;
case BodyPart.LEFT_MIDDLE_DISTAL:
return BodyPart.LEFT_MIDDLE_INTERMEDIATE;
case BodyPart.LEFT_RING_PROXIMAL:
return BodyPart.LEFT_HAND;
case BodyPart.LEFT_RING_INTERMEDIATE:
return BodyPart.LEFT_RING_PROXIMAL;
case BodyPart.LEFT_RING_DISTAL:
return BodyPart.LEFT_RING_INTERMEDIATE;
case BodyPart.LEFT_LITTLE_PROXIMAL:
return BodyPart.LEFT_HAND;
case BodyPart.LEFT_LITTLE_INTERMEDIATE:
return BodyPart.LEFT_LITTLE_PROXIMAL;
case BodyPart.LEFT_LITTLE_DISTAL:
return BodyPart.LEFT_LITTLE_INTERMEDIATE;
case BodyPart.RIGHT_THUMB_METACARPAL:
return BodyPart.RIGHT_HAND;
case BodyPart.RIGHT_THUMB_PROXIMAL:
return BodyPart.RIGHT_THUMB_METACARPAL;
case BodyPart.RIGHT_THUMB_DISTAL:
return BodyPart.RIGHT_THUMB_PROXIMAL;
case BodyPart.RIGHT_INDEX_PROXIMAL:
return BodyPart.RIGHT_HAND;
case BodyPart.RIGHT_INDEX_INTERMEDIATE:
return BodyPart.RIGHT_INDEX_PROXIMAL;
case BodyPart.RIGHT_INDEX_DISTAL:
return BodyPart.RIGHT_INDEX_INTERMEDIATE;
case BodyPart.RIGHT_MIDDLE_PROXIMAL:
return BodyPart.RIGHT_HAND;
case BodyPart.RIGHT_MIDDLE_INTERMEDIATE:
return BodyPart.RIGHT_MIDDLE_PROXIMAL;
case BodyPart.RIGHT_MIDDLE_DISTAL:
return BodyPart.RIGHT_MIDDLE_INTERMEDIATE;
case BodyPart.RIGHT_RING_PROXIMAL:
return BodyPart.RIGHT_HAND;
case BodyPart.RIGHT_RING_INTERMEDIATE:
return BodyPart.RIGHT_RING_PROXIMAL;
case BodyPart.RIGHT_RING_DISTAL:
return BodyPart.RIGHT_RING_INTERMEDIATE;
case BodyPart.RIGHT_LITTLE_PROXIMAL:
return BodyPart.RIGHT_HAND;
case BodyPart.RIGHT_LITTLE_INTERMEDIATE:
return BodyPart.RIGHT_LITTLE_PROXIMAL;
case BodyPart.RIGHT_LITTLE_DISTAL:
return BodyPart.RIGHT_LITTLE_INTERMEDIATE;
}
}
}
export function createChildren(
bones: Map<BodyPart, BoneT>,
body: BodyPart,
parentBone?: BoneKind
): (BoneKind | Bone)[] {
if (bones.size === 0) return [new Bone()];
const childrenBodies = BoneKind.children(body);
const parent = new BoneKind(bones, body, false);
parentBone?.add(parent);
if (childrenBodies.length === 0) {
const tail = new BoneKind(bones, body, true);
parent.add(tail);
return [parent, tail];
}
const children = childrenBodies.flatMap((bodyPart) =>
createChildren(bones, bodyPart, parent)
);
return [parent, ...children];
}