Skip to content

Commit 67b7cce

Browse files
committed
feat: Add VRMSpringBoneLimitHinge
The implementation of the hinge limit and its helper I believe the current `calculateLimit` implementation has the improvement window TODOs: - implement polar limit - implement loader plugin support for the limits See: vrm-c/vrm-specification#496
1 parent ccaf582 commit 67b7cce

File tree

5 files changed

+127
-0
lines changed

5 files changed

+127
-0
lines changed

packages/three-vrm-springbone/src/VRMSpringBoneLimitCone.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import * as THREE from 'three';
22
import { VRMSpringBoneLimit } from './VRMSpringBoneLimit';
33

4+
/**
5+
* Represents the cone limit of a spring bone defined in `VRMC_springBone_limit`.
6+
*/
47
export class VRMSpringBoneLimitCone extends VRMSpringBoneLimit {
58
/**
69
* The angle of the cone limit in radians.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import * as THREE from 'three';
2+
import { VRMSpringBoneLimit } from './VRMSpringBoneLimit';
3+
4+
/**
5+
* Represents the hinge limit of a spring bone defined in `VRMC_springBone_limit`.
6+
*/
7+
export class VRMSpringBoneLimitHinge extends VRMSpringBoneLimit {
8+
/**
9+
* The angle of the hinge limit in radians.
10+
* If the angle is set to π or greater, the angle will be interpreted as π by the implementation.
11+
* When the angle is set to π, the hinge shape becomes a full disc.
12+
*/
13+
public angle: number;
14+
15+
public constructor(params?: { angle?: number; rotation?: THREE.Quaternion }) {
16+
super();
17+
18+
this.angle = params?.angle ?? Math.PI;
19+
this.rotation = params?.rotation ?? new THREE.Quaternion();
20+
}
21+
22+
public calculateLimit(tailDir: THREE.Vector3): boolean {
23+
// bring the direction into the local space of the limit
24+
tailDir.applyQuaternion(this._totalRotationInvCache);
25+
26+
// kill the x component and normalize the direction
27+
let isLimited = false;
28+
if (tailDir.x !== 0.0) {
29+
tailDir.x = 0.0;
30+
tailDir.normalize();
31+
}
32+
33+
// calculate the current angle of the tail
34+
const dryAngle = Math.acos(tailDir.y);
35+
36+
if (dryAngle > this.angle) {
37+
// now we have to apply the limit
38+
isLimited = true;
39+
40+
// set the y component to the cos of the angle
41+
tailDir.y = Math.cos(this.angle);
42+
43+
// multiply the z component by the ratio of the sins of the angles
44+
const ratio = Math.sin(this.angle) / Math.sin(dryAngle);
45+
tailDir.z *= ratio;
46+
47+
// normalize the direction just in case
48+
tailDir.normalize();
49+
}
50+
51+
// change the direction back to the world space
52+
tailDir.applyQuaternion(this._totalRotationCache);
53+
54+
return isLimited;
55+
}
56+
}

packages/three-vrm-springbone/src/helpers/VRMSpringBoneLimitHelper.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { VRMSpringBoneJoint } from '../VRMSpringBoneJoint';
44
import { VRMSpringBoneLimitCone } from '../VRMSpringBoneLimitCone';
55
import { LimitConeBufferGeometry } from './utils/LimitConeBufferGeometry';
66
import { LimitBufferGeometry } from './utils/LimitBufferGeometry';
7+
import { VRMSpringBoneLimitHinge } from '../VRMSpringBoneLimitHinge';
8+
import { LimitHingeBufferGeometry } from './utils/LimitHingeBufferGeometry';
79

810
const _vec3WorldPosition = /*@__PURE__*/ new THREE.Vector3();
911
const _vec3Scale = /*@__PURE__*/ new THREE.Vector3();
@@ -23,6 +25,8 @@ export class VRMSpringBoneLimitHelper extends THREE.Group {
2325

2426
if (this.limit instanceof VRMSpringBoneLimitCone) {
2527
this._geometry = new LimitConeBufferGeometry(this.limit);
28+
} else if (this.limit instanceof VRMSpringBoneLimitHinge) {
29+
this._geometry = new LimitHingeBufferGeometry(this.limit);
2630
} else {
2731
throw new Error('VRMSpringBoneLimitHelper: Unknown limit type detected');
2832
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import * as THREE from 'three';
2+
import { LimitBufferGeometry } from './LimitBufferGeometry';
3+
import { VRMSpringBoneLimitHinge } from '../../VRMSpringBoneLimitHinge';
4+
5+
const ARC_SEGMENTS = 17;
6+
7+
export class LimitHingeBufferGeometry extends THREE.BufferGeometry implements LimitBufferGeometry {
8+
private readonly _attrPos: THREE.BufferAttribute;
9+
private readonly _attrIndex: THREE.BufferAttribute;
10+
private readonly _limit: VRMSpringBoneLimitHinge;
11+
private _currentAngle = 0;
12+
13+
public constructor(limit: VRMSpringBoneLimitHinge) {
14+
super();
15+
16+
this._limit = limit;
17+
18+
this._attrPos = new THREE.BufferAttribute(new Float32Array(3 * (ARC_SEGMENTS + 1)), 3);
19+
this.setAttribute('position', this._attrPos);
20+
21+
this._attrIndex = new THREE.BufferAttribute(new Uint16Array(2 * (ARC_SEGMENTS + 1)), 1);
22+
this.setIndex(this._attrIndex);
23+
24+
this._buildIndex();
25+
this.update();
26+
}
27+
28+
public update(): void {
29+
let shouldUpdateGeometry = false;
30+
31+
if (this._currentAngle !== this._limit.angle) {
32+
this._currentAngle = this._limit.angle;
33+
shouldUpdateGeometry = true;
34+
}
35+
36+
if (shouldUpdateGeometry) {
37+
this._buildPosition();
38+
}
39+
}
40+
41+
private _buildPosition(): void {
42+
for (let i = 0; i < ARC_SEGMENTS; i++) {
43+
const t = this._currentAngle * (2.0 * (i / (ARC_SEGMENTS - 1)) - 1.0);
44+
45+
this._attrPos.setXYZ(i, 0, Math.cos(t), Math.sin(t));
46+
}
47+
48+
this._attrPos.setXYZ(ARC_SEGMENTS, 0, 0, 0);
49+
50+
this._attrPos.needsUpdate = true;
51+
}
52+
53+
private _buildIndex(): void {
54+
for (let i = 0; i < ARC_SEGMENTS - 1; i++) {
55+
this._attrIndex.setXY(i * 2, i, i + 1);
56+
}
57+
58+
this._attrIndex.setXY(ARC_SEGMENTS * 2 - 2, ARC_SEGMENTS - 1, ARC_SEGMENTS);
59+
this._attrIndex.setXY(ARC_SEGMENTS * 2, ARC_SEGMENTS, 0);
60+
61+
this._attrIndex.needsUpdate = true;
62+
}
63+
}

packages/three-vrm-springbone/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export * from './VRMSpringBoneJoint';
99
export * from './VRMSpringBoneJointSettings';
1010
export * from './VRMSpringBoneLimit';
1111
export * from './VRMSpringBoneLimitCone';
12+
export * from './VRMSpringBoneLimitHinge';
1213
export * from './VRMSpringBoneLoaderPlugin';
1314
export * from './VRMSpringBoneLoaderPluginOptions';
1415
export * from './VRMSpringBoneManager';

0 commit comments

Comments
 (0)