@pmndrs/viverse 0.1.20 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/animation/bone-map.d.ts +4 -0
- package/dist/animation/bone-map.js +11 -0
- package/dist/animation/default.d.ts +9 -0
- package/dist/animation/default.js +25 -0
- package/dist/animation/index.d.ts +12 -19
- package/dist/animation/index.js +76 -72
- package/dist/animation/mask.d.ts +6 -0
- package/dist/animation/mask.js +53 -0
- package/dist/camera.d.ts +7 -7
- package/dist/camera.js +33 -26
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/input/action.d.ts +41 -0
- package/dist/input/action.js +97 -0
- package/dist/input/index.d.ts +12 -27
- package/dist/input/index.js +17 -69
- package/dist/input/keyboard.d.ts +29 -7
- package/dist/input/keyboard.js +84 -72
- package/dist/input/pointer-capture.d.ts +6 -11
- package/dist/input/pointer-capture.js +10 -30
- package/dist/input/pointer-lock.d.ts +6 -11
- package/dist/input/pointer-lock.js +8 -29
- package/dist/input/screen-joystick.d.ts +12 -11
- package/dist/input/screen-joystick.js +39 -49
- package/dist/input/screen-jump-button.d.ts +2 -4
- package/dist/input/screen-jump-button.js +7 -12
- package/dist/model/index.d.ts +9 -13
- package/dist/model/index.js +12 -34
- package/dist/physics/index.d.ts +2 -5
- package/dist/physics/index.js +7 -16
- package/dist/simple-character/apply-input-options.d.ts +2 -0
- package/dist/simple-character/apply-input-options.js +28 -0
- package/dist/simple-character/defaults.d.ts +2 -0
- package/dist/simple-character/defaults.js +2 -0
- package/dist/simple-character/index.d.ts +116 -0
- package/dist/simple-character/index.js +104 -0
- package/dist/simple-character/state/index.d.ts +6 -0
- package/dist/simple-character/state/index.js +6 -0
- package/dist/simple-character/state/jump-down.d.ts +3 -0
- package/dist/simple-character/state/jump-down.js +26 -0
- package/dist/simple-character/state/jump-forward.d.ts +5 -0
- package/dist/simple-character/state/jump-forward.js +41 -0
- package/dist/simple-character/state/jump-loop.d.ts +3 -0
- package/dist/simple-character/state/jump-loop.js +24 -0
- package/dist/simple-character/state/jump-start.d.ts +4 -0
- package/dist/simple-character/state/jump-start.js +30 -0
- package/dist/simple-character/state/jump-up.d.ts +5 -0
- package/dist/simple-character/state/jump-up.js +40 -0
- package/dist/simple-character/state/movement.d.ts +3 -0
- package/dist/simple-character/state/movement.js +63 -0
- package/dist/simple-character/update-input-velocity.d.ts +4 -0
- package/dist/simple-character/update-input-velocity.js +25 -0
- package/dist/simple-character/update-rotation.d.ts +6 -0
- package/dist/simple-character/update-rotation.js +40 -0
- package/dist/utils.d.ts +12 -5
- package/dist/utils.js +37 -39
- package/package.json +2 -2
- package/dist/animation/bvh.d.ts +0 -4
- package/dist/animation/bvh.js +0 -8
- package/dist/animation/fbx.d.ts +0 -4
- package/dist/animation/fbx.js +0 -8
- package/dist/animation/gltf.d.ts +0 -3
- package/dist/animation/gltf.js +0 -8
- package/dist/animation/vrma.d.ts +0 -3
- package/dist/animation/vrma.js +0 -8
- package/dist/simple-character.d.ts +0 -107
- package/dist/simple-character.js +0 -344
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const IdleAnimationUrl = Symbol('idle-animation-url');
|
|
2
|
+
export const JumpUpAnimationUrl = Symbol('jump-up-animation-url');
|
|
3
|
+
export const JumpDownAnimationUrl = Symbol('jump-down-animation-url');
|
|
4
|
+
export const JumpForwardAnimationUrl = Symbol('jump-forward-animation-url');
|
|
5
|
+
export const JumpLoopAnimationUrl = Symbol('jump-loop-animation-url');
|
|
6
|
+
export const RunAnimationUrl = Symbol('run-animation-url');
|
|
7
|
+
export const WalkAnimationUrl = Symbol('walk-animation-url');
|
|
8
|
+
export async function resolveDefaultCharacterAnimationUrl(url) {
|
|
9
|
+
switch (url) {
|
|
10
|
+
case IdleAnimationUrl:
|
|
11
|
+
return (await import('../assets/idle.js')).url;
|
|
12
|
+
case JumpDownAnimationUrl:
|
|
13
|
+
return (await import('../assets/jump-down.js')).url;
|
|
14
|
+
case JumpForwardAnimationUrl:
|
|
15
|
+
return (await import('../assets/jump-forward.js')).url;
|
|
16
|
+
case JumpLoopAnimationUrl:
|
|
17
|
+
return (await import('../assets/jump-loop.js')).url;
|
|
18
|
+
case JumpUpAnimationUrl:
|
|
19
|
+
return (await import('../assets/jump-up.js')).url;
|
|
20
|
+
case RunAnimationUrl:
|
|
21
|
+
return (await import('../assets/run.js')).url;
|
|
22
|
+
case WalkAnimationUrl:
|
|
23
|
+
return (await import('../assets/walk.js')).url;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { VRMHumanBoneName } from '@pixiv/three-vrm';
|
|
2
2
|
import { AnimationClip, Object3D } from 'three';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export
|
|
7
|
-
export * from './vrma.js';
|
|
3
|
+
import { DefaultUrl } from './default.js';
|
|
4
|
+
import { type CharacterAnimationMask } from './mask.js';
|
|
5
|
+
import { type CharacterModel } from '../model/index.js';
|
|
6
|
+
export declare function fixModelAnimationClip(model: CharacterModel, clip: AnimationClip, clipScene: Object3D | undefined, removeXZMovement: boolean): void;
|
|
8
7
|
export * from './utils.js';
|
|
9
|
-
export
|
|
8
|
+
export * from './default.js';
|
|
9
|
+
export * from './mask.js';
|
|
10
|
+
export type CharacterAnimationOptions = {
|
|
11
|
+
url: string | DefaultUrl;
|
|
10
12
|
type?: 'mixamo' | 'gltf' | 'vrma' | 'fbx' | 'bvh';
|
|
11
|
-
url: string;
|
|
12
13
|
removeXZMovement?: boolean;
|
|
13
14
|
trimTime?: {
|
|
14
15
|
start?: number;
|
|
@@ -16,18 +17,10 @@ export type ModelAnimationOptions = {
|
|
|
16
17
|
};
|
|
17
18
|
boneMap?: Record<string, VRMHumanBoneName>;
|
|
18
19
|
scaleTime?: number;
|
|
20
|
+
mask?: CharacterAnimationMask;
|
|
19
21
|
};
|
|
20
|
-
export
|
|
21
|
-
declare
|
|
22
|
-
|
|
23
|
-
run: () => Promise<typeof import("../assets/run.js")>;
|
|
24
|
-
idle: () => Promise<typeof import("../assets/idle.js")>;
|
|
25
|
-
jumpUp: () => Promise<typeof import("../assets/jump-up.js")>;
|
|
26
|
-
jumpLoop: () => Promise<typeof import("../assets/jump-loop.js")>;
|
|
27
|
-
jumpDown: () => Promise<typeof import("../assets/jump-down.js")>;
|
|
28
|
-
jumpForward: () => Promise<typeof import("../assets/jump-forward.js")>;
|
|
29
|
-
};
|
|
30
|
-
export declare const simpleCharacterAnimationNames: Array<keyof typeof simpleCharacterAnimationUrls>;
|
|
31
|
-
export declare function getSimpleCharacterModelAnimationOptions(animationName: keyof typeof simpleCharacterAnimationUrls): Promise<ModelAnimationOptions>;
|
|
22
|
+
export type Tail<T extends any[]> = T extends [any, ...infer Rest] ? Rest : never;
|
|
23
|
+
export declare function flattenCharacterAnimationOptions(options: Exclude<CharacterAnimationOptions, false>): Tail<Parameters<typeof loadCharacterAnimation>>;
|
|
24
|
+
export declare function loadCharacterAnimation(model: CharacterModel, url: string | DefaultUrl, type?: CharacterAnimationOptions['type'], removeXZMovement?: boolean, trimStartTime?: number | undefined, trimEndTime?: number | undefined, boneMap?: Record<string, VRMHumanBoneName> | undefined, scaleTime?: number | undefined, mask?: CharacterAnimationMask): Promise<AnimationClip>;
|
|
32
25
|
export declare const mixamoBoneMap: Record<string, VRMHumanBoneName>;
|
|
33
26
|
export declare const bvhBoneMap: Record<string, VRMHumanBoneName>;
|
package/dist/animation/index.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { VRM } from '@pixiv/three-vrm';
|
|
2
2
|
import { Euler, Quaternion, QuaternionKeyframeTrack, Vector3, VectorKeyframeTrack, } from 'three';
|
|
3
|
+
import { BVHLoader } from 'three/examples/jsm/loaders/BVHLoader.js';
|
|
4
|
+
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js';
|
|
5
|
+
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
6
|
+
import { applyBoneMap } from './bone-map.js';
|
|
3
7
|
import _bvhBoneMap from './bvh-bone-map.json';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { loadVrmModelGltfAnimations } from './gltf.js';
|
|
8
|
+
import { resolveDefaultCharacterAnimationUrl } from './default.js';
|
|
9
|
+
import { applyMask } from './mask.js';
|
|
7
10
|
import _mixamoBoneMap from './mixamo-bone-map.json';
|
|
8
11
|
import { scaleAnimationClipTime, trimAnimationClip } from './utils.js';
|
|
9
|
-
import {
|
|
10
|
-
import { cached } from '../utils.js';
|
|
12
|
+
import { vrmaLoader } from '../model/index.js';
|
|
11
13
|
//helper variables for the quaternion retargeting
|
|
12
14
|
const baseThisLocalRestRotation_inverse = new Quaternion();
|
|
13
15
|
const baseThisLocalCurrentRotation = new Quaternion();
|
|
@@ -21,11 +23,7 @@ const targetThisLocalCurrentRotation = new Quaternion();
|
|
|
21
23
|
const position = new Vector3();
|
|
22
24
|
const nonVrmRotationOffset = new Quaternion().setFromEuler(new Euler(0, Math.PI, 0));
|
|
23
25
|
//TODO: currently assumes the model is not yet transformed - loaded for the first time
|
|
24
|
-
export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement
|
|
25
|
-
const hipsClipBoneName = boneMap == null ? 'hips' : Object.entries(boneMap).find(([, vrmBoneName]) => vrmBoneName === 'hips')?.[0];
|
|
26
|
-
if (hipsClipBoneName == null) {
|
|
27
|
-
throw new Error('Failed to determine hips bone name for VRM animation. Please check the bone map or animation file.');
|
|
28
|
-
}
|
|
26
|
+
export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement) {
|
|
29
27
|
let restRoot;
|
|
30
28
|
let restRootParent;
|
|
31
29
|
if (!(model instanceof VRM)) {
|
|
@@ -39,24 +37,22 @@ export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement,
|
|
|
39
37
|
let positionScale = 1;
|
|
40
38
|
let clipSceneHips;
|
|
41
39
|
if (clipScene != null) {
|
|
42
|
-
clipSceneHips = clipScene.getObjectByName(
|
|
40
|
+
clipSceneHips = clipScene.getObjectByName('hips');
|
|
43
41
|
clipSceneHips?.parent?.updateMatrixWorld();
|
|
44
42
|
const vrmHipsPosition = model instanceof VRM
|
|
45
43
|
? model.humanoid.normalizedRestPose.hips?.position
|
|
46
44
|
: model.scene.getObjectByName('rest_hips')?.getWorldPosition(new Vector3()).toArray();
|
|
47
|
-
if (clipSceneHips
|
|
48
|
-
|
|
45
|
+
if (clipSceneHips != null && vrmHipsPosition != null) {
|
|
46
|
+
// Adjust with reference to hips height.
|
|
47
|
+
const motionHipsHeight = clipSceneHips.getWorldPosition(position).y;
|
|
48
|
+
const [_, vrmHipsHeight] = vrmHipsPosition;
|
|
49
|
+
positionScale = vrmHipsHeight / motionHipsHeight;
|
|
49
50
|
}
|
|
50
|
-
// Adjust with reference to hips height.
|
|
51
|
-
const motionHipsHeight = clipSceneHips.getWorldPosition(position).y;
|
|
52
|
-
const [_, vrmHipsHeight] = vrmHipsPosition;
|
|
53
|
-
positionScale = vrmHipsHeight / motionHipsHeight;
|
|
54
51
|
}
|
|
55
52
|
for (const track of clip.tracks) {
|
|
56
53
|
// Convert each tracks for VRM use, and push to `tracks`
|
|
57
54
|
const [clipBoneName, propertyName] = track.name.split('.');
|
|
58
|
-
const
|
|
59
|
-
const targetNormalizedBoneName = model instanceof VRM ? model.humanoid.getNormalizedBoneNode(vrmBoneName)?.name : vrmBoneName;
|
|
55
|
+
const targetNormalizedBoneName = model instanceof VRM ? model.humanoid.getNormalizedBoneNode(clipBoneName)?.name : clipBoneName;
|
|
60
56
|
if (targetNormalizedBoneName == null) {
|
|
61
57
|
continue;
|
|
62
58
|
}
|
|
@@ -69,7 +65,7 @@ export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement,
|
|
|
69
65
|
targetParentWorldBoneTransform = { rotation: [0, 0, 0, 1] };
|
|
70
66
|
}
|
|
71
67
|
else {
|
|
72
|
-
const targetBone = model.scene.getObjectByName(`rest_${
|
|
68
|
+
const targetBone = model.scene.getObjectByName(`rest_${clipBoneName}`);
|
|
73
69
|
if (targetBone != null) {
|
|
74
70
|
targetLocalBoneTransform = { rotation: targetBone.quaternion.toArray() };
|
|
75
71
|
}
|
|
@@ -123,7 +119,7 @@ export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement,
|
|
|
123
119
|
targetThisLocalCurrentRotation.x *= -1;
|
|
124
120
|
targetThisLocalCurrentRotation.z *= -1;
|
|
125
121
|
}
|
|
126
|
-
if (!(model instanceof VRM) &&
|
|
122
|
+
if (!(model instanceof VRM) && clipBoneName === 'hips') {
|
|
127
123
|
targetThisLocalCurrentRotation.premultiply(nonVrmRotationOffset);
|
|
128
124
|
}
|
|
129
125
|
targetThisLocalCurrentRotation.toArray(track.values, i);
|
|
@@ -131,7 +127,7 @@ export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement,
|
|
|
131
127
|
track.name = trackName;
|
|
132
128
|
}
|
|
133
129
|
else if (track instanceof VectorKeyframeTrack) {
|
|
134
|
-
if (
|
|
130
|
+
if (clipBoneName != 'hips' && clipBoneName != 'root') {
|
|
135
131
|
continue;
|
|
136
132
|
}
|
|
137
133
|
if (propertyName != 'position') {
|
|
@@ -140,7 +136,7 @@ export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement,
|
|
|
140
136
|
for (let i = 0; i < track.values.length; i += 3) {
|
|
141
137
|
position.fromArray(track.values, i);
|
|
142
138
|
if (clipSceneHips?.parent != null) {
|
|
143
|
-
if (
|
|
139
|
+
if (clipBoneName === 'hips') {
|
|
144
140
|
position.applyMatrix4(clipSceneHips.parent.matrixWorld);
|
|
145
141
|
}
|
|
146
142
|
else {
|
|
@@ -148,7 +144,7 @@ export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement,
|
|
|
148
144
|
}
|
|
149
145
|
}
|
|
150
146
|
position.multiplyScalar(positionScale);
|
|
151
|
-
if (!(model instanceof VRM) &&
|
|
147
|
+
if (!(model instanceof VRM) && clipBoneName === 'hips') {
|
|
152
148
|
position.applyQuaternion(nonVrmRotationOffset);
|
|
153
149
|
}
|
|
154
150
|
if (model instanceof VRM) {
|
|
@@ -157,7 +153,7 @@ export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement,
|
|
|
157
153
|
position.y *= -1;
|
|
158
154
|
}
|
|
159
155
|
}
|
|
160
|
-
if (
|
|
156
|
+
if (clipBoneName === 'hips' && removeXZMovement) {
|
|
161
157
|
position.x = 0;
|
|
162
158
|
position.z = 0;
|
|
163
159
|
}
|
|
@@ -170,12 +166,32 @@ export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement,
|
|
|
170
166
|
restRoot.parent = restRootParent;
|
|
171
167
|
}
|
|
172
168
|
}
|
|
173
|
-
export * from './gltf.js';
|
|
174
|
-
export * from './fbx.js';
|
|
175
|
-
export * from './vrma.js';
|
|
176
169
|
export * from './utils.js';
|
|
177
|
-
|
|
170
|
+
export * from './default.js';
|
|
171
|
+
export * from './mask.js';
|
|
172
|
+
export function flattenCharacterAnimationOptions(options) {
|
|
173
|
+
return [
|
|
174
|
+
options.url,
|
|
175
|
+
options.type,
|
|
176
|
+
options.removeXZMovement,
|
|
177
|
+
options.trimTime?.start,
|
|
178
|
+
options.trimTime?.end,
|
|
179
|
+
options.boneMap,
|
|
180
|
+
options.scaleTime,
|
|
181
|
+
options.mask,
|
|
182
|
+
];
|
|
183
|
+
}
|
|
184
|
+
const gltfLoader = new GLTFLoader();
|
|
185
|
+
const fbxLoader = new FBXLoader();
|
|
186
|
+
const bvhLoader = new BVHLoader();
|
|
187
|
+
export async function loadCharacterAnimation(model, url, type, removeXZMovement = false, trimStartTime, trimEndTime, boneMap, scaleTime, mask) {
|
|
188
|
+
if (typeof url === 'symbol') {
|
|
189
|
+
url = await resolveDefaultCharacterAnimationUrl(url);
|
|
190
|
+
type = 'gltf';
|
|
191
|
+
}
|
|
178
192
|
let clips;
|
|
193
|
+
let clipScene;
|
|
194
|
+
let defaultBoneMap;
|
|
179
195
|
if (type == null) {
|
|
180
196
|
const lowerCaseUrl = url.toLocaleLowerCase();
|
|
181
197
|
if (lowerCaseUrl.endsWith('.glb') || lowerCaseUrl.endsWith('.gltf')) {
|
|
@@ -195,29 +211,51 @@ async function uncachedLoadModelAnimation(model, type, url, removeXZMovement, tr
|
|
|
195
211
|
}
|
|
196
212
|
}
|
|
197
213
|
switch (type) {
|
|
198
|
-
case 'gltf':
|
|
199
|
-
|
|
214
|
+
case 'gltf': {
|
|
215
|
+
const { animations, scene } = await gltfLoader.loadAsync(url);
|
|
216
|
+
clips = animations;
|
|
217
|
+
clipScene = scene;
|
|
200
218
|
break;
|
|
201
|
-
|
|
202
|
-
|
|
219
|
+
}
|
|
220
|
+
case 'fbx': {
|
|
221
|
+
const scene = await fbxLoader.loadAsync(url);
|
|
222
|
+
clips = scene.animations;
|
|
223
|
+
clipScene = scene;
|
|
203
224
|
break;
|
|
204
|
-
|
|
205
|
-
|
|
225
|
+
}
|
|
226
|
+
case 'bvh': {
|
|
227
|
+
const { clip, skeleton } = await bvhLoader.loadAsync(url);
|
|
228
|
+
clips = [clip];
|
|
229
|
+
boneMap ??= bvhBoneMap;
|
|
206
230
|
break;
|
|
207
|
-
|
|
208
|
-
|
|
231
|
+
}
|
|
232
|
+
case 'mixamo': {
|
|
233
|
+
const scene = await fbxLoader.loadAsync(url);
|
|
234
|
+
clips = scene.animations;
|
|
235
|
+
clipScene = scene;
|
|
236
|
+
boneMap ??= mixamoBoneMap;
|
|
209
237
|
break;
|
|
238
|
+
}
|
|
210
239
|
case 'vrma':
|
|
211
240
|
if (!(model instanceof VRM)) {
|
|
212
241
|
throw new Error(`Model must be an instance of VRM to load VRMA animations`);
|
|
213
242
|
}
|
|
214
|
-
clips = await
|
|
243
|
+
clips = (await vrmaLoader.loadAsync(url)).userData.vrmAnimations;
|
|
215
244
|
break;
|
|
216
245
|
}
|
|
217
246
|
if (clips.length != 1) {
|
|
218
247
|
throw new Error(`Expected exactly one animation clip, but got ${clips.length} for url ${url}`);
|
|
219
248
|
}
|
|
220
249
|
const [clip] = clips;
|
|
250
|
+
if (boneMap != null) {
|
|
251
|
+
applyBoneMap(clip, clipScene, boneMap);
|
|
252
|
+
}
|
|
253
|
+
if (mask != null) {
|
|
254
|
+
applyMask(clip, mask);
|
|
255
|
+
}
|
|
256
|
+
if (type != 'vrma') {
|
|
257
|
+
fixModelAnimationClip(model, clip, clipScene, removeXZMovement);
|
|
258
|
+
}
|
|
221
259
|
if (trimStartTime != null || trimEndTime != null) {
|
|
222
260
|
trimAnimationClip(clip, trimStartTime, trimEndTime);
|
|
223
261
|
}
|
|
@@ -226,39 +264,5 @@ async function uncachedLoadModelAnimation(model, type, url, removeXZMovement, tr
|
|
|
226
264
|
}
|
|
227
265
|
return clip;
|
|
228
266
|
}
|
|
229
|
-
export function loadCharacterModelAnimation(model, options) {
|
|
230
|
-
return cached(uncachedLoadModelAnimation, [
|
|
231
|
-
model,
|
|
232
|
-
options.type,
|
|
233
|
-
options.url,
|
|
234
|
-
options.removeXZMovement ?? false,
|
|
235
|
-
options.trimTime?.start,
|
|
236
|
-
options.trimTime?.end,
|
|
237
|
-
options.boneMap,
|
|
238
|
-
options.scaleTime,
|
|
239
|
-
]);
|
|
240
|
-
}
|
|
241
|
-
const extraOptions = {
|
|
242
|
-
walk: { scaleTime: 0.5 },
|
|
243
|
-
run: { scaleTime: 0.8 },
|
|
244
|
-
jumpForward: { scaleTime: 0.9 },
|
|
245
|
-
};
|
|
246
|
-
const simpleCharacterAnimationUrls = {
|
|
247
|
-
walk: () => import('../assets/walk.js'),
|
|
248
|
-
run: () => import('../assets/run.js'),
|
|
249
|
-
idle: () => import('../assets/idle.js'),
|
|
250
|
-
jumpUp: () => import('../assets/jump-up.js'),
|
|
251
|
-
jumpLoop: () => import('../assets/jump-loop.js'),
|
|
252
|
-
jumpDown: () => import('../assets/jump-down.js'),
|
|
253
|
-
jumpForward: () => import('../assets/jump-forward.js'),
|
|
254
|
-
};
|
|
255
|
-
export const simpleCharacterAnimationNames = Object.keys(simpleCharacterAnimationUrls);
|
|
256
|
-
export async function getSimpleCharacterModelAnimationOptions(animationName) {
|
|
257
|
-
return {
|
|
258
|
-
type: 'gltf',
|
|
259
|
-
...extraOptions[animationName],
|
|
260
|
-
url: (await simpleCharacterAnimationUrls[animationName]()).url,
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
267
|
export const mixamoBoneMap = _mixamoBoneMap;
|
|
264
268
|
export const bvhBoneMap = _bvhBoneMap;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { VRMHumanBoneName } from '@pixiv/three-vrm';
|
|
2
|
+
import type { AnimationClip } from 'three';
|
|
3
|
+
export type CharacterAnimationMask = (boneName: VRMHumanBoneName) => boolean;
|
|
4
|
+
export declare function applyMask(clip: AnimationClip, mask: CharacterAnimationMask): void;
|
|
5
|
+
export declare const upperBody: CharacterAnimationMask;
|
|
6
|
+
export declare const lowerBody: CharacterAnimationMask;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export function applyMask(clip, mask) {
|
|
2
|
+
clip.tracks = clip.tracks.filter((track) => mask(track.name.split('.')[0]));
|
|
3
|
+
}
|
|
4
|
+
const upperBodyParts = [
|
|
5
|
+
'spine',
|
|
6
|
+
'chest',
|
|
7
|
+
'upperChest',
|
|
8
|
+
'neck',
|
|
9
|
+
'head',
|
|
10
|
+
'leftEye',
|
|
11
|
+
'rightEye',
|
|
12
|
+
'jaw',
|
|
13
|
+
'leftShoulder',
|
|
14
|
+
'leftUpperArm',
|
|
15
|
+
'leftLowerArm',
|
|
16
|
+
'leftHand',
|
|
17
|
+
'rightShoulder',
|
|
18
|
+
'rightUpperArm',
|
|
19
|
+
'rightLowerArm',
|
|
20
|
+
'rightHand',
|
|
21
|
+
'leftThumbMetacarpal',
|
|
22
|
+
'leftThumbProximal',
|
|
23
|
+
'leftThumbDistal',
|
|
24
|
+
'leftIndexProximal',
|
|
25
|
+
'leftIndexIntermediate',
|
|
26
|
+
'leftIndexDistal',
|
|
27
|
+
'leftMiddleProximal',
|
|
28
|
+
'leftMiddleIntermediate',
|
|
29
|
+
'leftMiddleDistal',
|
|
30
|
+
'leftRingProximal',
|
|
31
|
+
'leftRingIntermediate',
|
|
32
|
+
'leftRingDistal',
|
|
33
|
+
'leftLittleProximal',
|
|
34
|
+
'leftLittleIntermediate',
|
|
35
|
+
'leftLittleDistal',
|
|
36
|
+
'rightThumbMetacarpal',
|
|
37
|
+
'rightThumbProximal',
|
|
38
|
+
'rightThumbDistal',
|
|
39
|
+
'rightIndexProximal',
|
|
40
|
+
'rightIndexIntermediate',
|
|
41
|
+
'rightIndexDistal',
|
|
42
|
+
'rightMiddleProximal',
|
|
43
|
+
'rightMiddleIntermediate',
|
|
44
|
+
'rightMiddleDistal',
|
|
45
|
+
'rightRingProximal',
|
|
46
|
+
'rightRingIntermediate',
|
|
47
|
+
'rightRingDistal',
|
|
48
|
+
'rightLittleProximal',
|
|
49
|
+
'rightLittleIntermediate',
|
|
50
|
+
'rightLittleDistal',
|
|
51
|
+
];
|
|
52
|
+
export const upperBody = (name) => upperBodyParts.includes(name);
|
|
53
|
+
export const lowerBody = (name) => !upperBodyParts.includes(name);
|
package/dist/camera.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Object3D, Vector3, Vector3Tuple, Ray } from 'three';
|
|
2
|
-
import { SimpleCharacter } from './simple-character.js';
|
|
3
2
|
export declare const FirstPersonCharacterCameraBehavior: SimpleCharacterCameraBehaviorOptions;
|
|
4
3
|
export type SimpleCharacterCameraBehaviorOptions = {
|
|
5
4
|
/**
|
|
@@ -55,16 +54,16 @@ export type SimpleCharacterCameraBehaviorOptions = {
|
|
|
55
54
|
maxDistance?: number;
|
|
56
55
|
} | boolean;
|
|
57
56
|
} | boolean;
|
|
58
|
-
export declare class
|
|
59
|
-
getCamera: () => Object3D;
|
|
60
|
-
character: SimpleCharacter;
|
|
61
|
-
private readonly raycast?;
|
|
57
|
+
export declare class CharacterCameraBehavior {
|
|
62
58
|
rotationPitch: number;
|
|
63
59
|
rotationYaw: number;
|
|
64
60
|
zoomDistance: number;
|
|
65
61
|
private collisionFreeZoomDistance;
|
|
66
62
|
private firstUpdate;
|
|
67
|
-
|
|
63
|
+
private readonly abortController;
|
|
64
|
+
private readonly yawReader;
|
|
65
|
+
private readonly pitchReader;
|
|
66
|
+
private readonly zoomReader;
|
|
68
67
|
private setRotationFromDelta;
|
|
69
68
|
private setDistanceFromDelta;
|
|
70
69
|
private computeCharacterBaseOffset;
|
|
@@ -74,5 +73,6 @@ export declare class SimpleCharacterCameraBehavior {
|
|
|
74
73
|
/**
|
|
75
74
|
* @param delta in seconds
|
|
76
75
|
*/
|
|
77
|
-
update(deltaTime: number, options?: SimpleCharacterCameraBehaviorOptions): void;
|
|
76
|
+
update(camera: Object3D, target: Object3D, deltaTime: number, raycast?: (ray: Ray, far: number) => number | undefined, options?: SimpleCharacterCameraBehaviorOptions): void;
|
|
77
|
+
dispose(): void;
|
|
78
78
|
}
|
package/dist/camera.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Vector3, Euler, Ray } from 'three';
|
|
1
|
+
import { Vector3, Euler, Ray, Quaternion } from 'three';
|
|
2
2
|
import { clamp } from 'three/src/math/MathUtils.js';
|
|
3
|
-
import {
|
|
3
|
+
import { RotatePitchAction, RotateYawAction, ZoomAction } from './input/index.js';
|
|
4
4
|
export const FirstPersonCharacterCameraBehavior = {
|
|
5
5
|
characterBaseOffset: [0, 1.6, 0],
|
|
6
6
|
zoom: { maxDistance: 0, minDistance: 0 },
|
|
@@ -11,25 +11,22 @@ const sphericalOffset = new Vector3();
|
|
|
11
11
|
const characterWorldPosition = new Vector3();
|
|
12
12
|
const euler = new Euler();
|
|
13
13
|
const rayHelper = new Ray();
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
character;
|
|
17
|
-
raycast;
|
|
14
|
+
const quaternionHelper = new Quaternion();
|
|
15
|
+
export class CharacterCameraBehavior {
|
|
18
16
|
rotationPitch = (-20 * Math.PI) / 180;
|
|
19
17
|
rotationYaw = 0;
|
|
20
18
|
zoomDistance = 4; // Changed from zoom to distance for clearer semantics
|
|
21
19
|
//internal state
|
|
22
20
|
collisionFreeZoomDistance = this.zoomDistance;
|
|
23
21
|
firstUpdate = true;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
setRotationFromDelta(delta, rotationOptions) {
|
|
22
|
+
abortController = new AbortController();
|
|
23
|
+
yawReader = RotateYawAction.createReader(this.abortController.signal);
|
|
24
|
+
pitchReader = RotatePitchAction.createReader(this.abortController.signal);
|
|
25
|
+
zoomReader = ZoomAction.createReader(this.abortController.signal);
|
|
26
|
+
setRotationFromDelta(camera, delta, rotationOptions) {
|
|
30
27
|
if (delta.lengthSq() < 0.0001) {
|
|
31
28
|
// use current camera rotation if very close to target
|
|
32
|
-
euler.setFromQuaternion(
|
|
29
|
+
euler.setFromQuaternion(camera.quaternion, 'YXZ');
|
|
33
30
|
this.rotationPitch = euler.x;
|
|
34
31
|
this.rotationYaw = euler.y;
|
|
35
32
|
return;
|
|
@@ -63,7 +60,7 @@ export class SimpleCharacterCameraBehavior {
|
|
|
63
60
|
/**
|
|
64
61
|
* @param delta in seconds
|
|
65
62
|
*/
|
|
66
|
-
update(deltaTime, options = true) {
|
|
63
|
+
update(camera, target, deltaTime, raycast, options = true) {
|
|
67
64
|
if (options === false) {
|
|
68
65
|
this.firstUpdate = true;
|
|
69
66
|
return;
|
|
@@ -74,33 +71,38 @@ export class SimpleCharacterCameraBehavior {
|
|
|
74
71
|
}
|
|
75
72
|
//compute character->camera delta through offset
|
|
76
73
|
this.computeCharacterBaseOffset(chracterBaseOffsetHelper, options.characterBaseOffset);
|
|
77
|
-
|
|
74
|
+
target.getWorldQuaternion(quaternionHelper);
|
|
75
|
+
chracterBaseOffsetHelper.applyQuaternion(quaternionHelper);
|
|
76
|
+
target.getWorldPosition(characterWorldPosition);
|
|
78
77
|
characterWorldPosition.add(chracterBaseOffsetHelper);
|
|
79
|
-
|
|
78
|
+
camera.getWorldPosition(deltaHelper);
|
|
80
79
|
deltaHelper.sub(characterWorldPosition);
|
|
81
80
|
// apply rotation input to rotationYaw and rotationPitch if not disabled or first update
|
|
82
81
|
let rotationOptions = options.rotation ?? true;
|
|
83
82
|
if (!this.firstUpdate && rotationOptions !== false) {
|
|
84
83
|
rotationOptions = rotationOptions === true ? {} : rotationOptions;
|
|
85
84
|
const rotationSpeed = rotationOptions.speed ?? 1000.0;
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
this.yawReader.update();
|
|
86
|
+
this.pitchReader.update();
|
|
87
|
+
const deltaYaw = this.yawReader.get();
|
|
88
|
+
const deltaPitch = this.pitchReader.get();
|
|
88
89
|
this.rotationYaw = this.clampYaw(this.rotationYaw + deltaYaw * rotationSpeed * deltaTime, rotationOptions);
|
|
89
90
|
this.rotationPitch = this.clampPitch(this.rotationPitch + deltaPitch * rotationSpeed * deltaTime, rotationOptions);
|
|
90
91
|
}
|
|
91
92
|
else {
|
|
92
|
-
this.setRotationFromDelta(deltaHelper, typeof rotationOptions === 'boolean' ? {} : rotationOptions);
|
|
93
|
+
this.setRotationFromDelta(camera, deltaHelper, typeof rotationOptions === 'boolean' ? {} : rotationOptions);
|
|
93
94
|
}
|
|
94
95
|
// apply yaw and pitch to camera rotation
|
|
95
|
-
|
|
96
|
-
rayHelper.direction.set(0, 0, 1).applyEuler(
|
|
96
|
+
camera.rotation.set(this.rotationPitch, this.rotationYaw, 0, 'YXZ');
|
|
97
|
+
rayHelper.direction.set(0, 0, 1).applyEuler(camera.rotation);
|
|
97
98
|
rayHelper.origin.copy(characterWorldPosition);
|
|
98
99
|
// apply zoom input to zoomDistance if not disabled or first update
|
|
99
100
|
let zoomOptions = options.zoom ?? true;
|
|
100
101
|
if (!this.firstUpdate && zoomOptions !== false) {
|
|
101
102
|
zoomOptions = zoomOptions === true ? {} : zoomOptions;
|
|
102
103
|
const zoomSpeed = zoomOptions.speed ?? 1000.0;
|
|
103
|
-
|
|
104
|
+
this.zoomReader.update();
|
|
105
|
+
const deltaZoom = this.zoomReader.get();
|
|
104
106
|
const zoomFactor = 1 + deltaZoom * zoomSpeed * deltaTime;
|
|
105
107
|
if (deltaZoom >= 0) {
|
|
106
108
|
this.zoomDistance *= zoomFactor;
|
|
@@ -119,19 +121,24 @@ export class SimpleCharacterCameraBehavior {
|
|
|
119
121
|
if (collisionOptions === true) {
|
|
120
122
|
collisionOptions = {};
|
|
121
123
|
}
|
|
122
|
-
let distance =
|
|
124
|
+
let distance = raycast?.(rayHelper, this.zoomDistance);
|
|
123
125
|
if (distance != null) {
|
|
124
126
|
this.collisionFreeZoomDistance = distance - (collisionOptions?.offset ?? 0.2);
|
|
125
127
|
}
|
|
126
128
|
}
|
|
127
129
|
// Calculate camera position using spherical coordinates from euler
|
|
128
130
|
sphericalOffset.set(0, 0, this.collisionFreeZoomDistance);
|
|
129
|
-
sphericalOffset.applyEuler(
|
|
131
|
+
sphericalOffset.applyEuler(camera.rotation);
|
|
130
132
|
// Get target position with offset (reuse helper vector)
|
|
131
|
-
this.character.getWorldPosition(characterWorldPosition);
|
|
132
133
|
this.computeCharacterBaseOffset(chracterBaseOffsetHelper, options.characterBaseOffset);
|
|
134
|
+
target.getWorldQuaternion(quaternionHelper);
|
|
135
|
+
chracterBaseOffsetHelper.applyQuaternion(quaternionHelper);
|
|
136
|
+
target.getWorldPosition(characterWorldPosition);
|
|
133
137
|
characterWorldPosition.add(chracterBaseOffsetHelper);
|
|
134
138
|
// Set camera position relative to target
|
|
135
|
-
|
|
139
|
+
camera.position.copy(characterWorldPosition).add(sphericalOffset);
|
|
140
|
+
}
|
|
141
|
+
dispose() {
|
|
142
|
+
this.abortController.abort();
|
|
136
143
|
}
|
|
137
144
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
export
|
|
1
|
+
export * from './utils.js';
|
|
2
2
|
export * from './input/index.js';
|
|
3
|
+
export * from './utils.js';
|
|
3
4
|
export * from './camera.js';
|
|
4
5
|
export * from './physics/index.js';
|
|
5
6
|
export * from './animation/index.js';
|
|
6
7
|
export * from './material.js';
|
|
7
|
-
export * from './simple-character.js';
|
|
8
|
+
export * from './simple-character/index.js';
|
|
8
9
|
export * from './model/index.js';
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
export
|
|
1
|
+
export * from './utils.js';
|
|
2
2
|
export * from './input/index.js';
|
|
3
|
+
export * from './utils.js';
|
|
3
4
|
export * from './camera.js';
|
|
4
5
|
export * from './physics/index.js';
|
|
5
6
|
export * from './animation/index.js';
|
|
6
7
|
export * from './material.js';
|
|
7
|
-
export * from './simple-character.js';
|
|
8
|
+
export * from './simple-character/index.js';
|
|
8
9
|
export * from './model/index.js';
|
|
9
10
|
(function injectMobileClassStyle() {
|
|
10
11
|
if (typeof document === 'undefined') {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export declare class EventAction<T = void> {
|
|
2
|
+
private latestTime;
|
|
3
|
+
private readonly subscriptionListeners;
|
|
4
|
+
emit(value: T): void;
|
|
5
|
+
subscribe(callback: (value: T) => void, options?: {
|
|
6
|
+
once?: true;
|
|
7
|
+
signal?: AbortSignal;
|
|
8
|
+
}): void;
|
|
9
|
+
waitFor(signal?: AbortSignal): Promise<T>;
|
|
10
|
+
getLatestTime(): number;
|
|
11
|
+
}
|
|
12
|
+
export type StateActionWriter<T> = {
|
|
13
|
+
write(value: T): void;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* StateAction keeps the latest state per writer and merges them on read.
|
|
17
|
+
* Values persist until the writer is disposed (abortSignal aborts).
|
|
18
|
+
*/
|
|
19
|
+
export declare class StateAction<T> {
|
|
20
|
+
private readonly mergeWriters;
|
|
21
|
+
private readonly neutral;
|
|
22
|
+
private readonly absoluteActions;
|
|
23
|
+
constructor(mergeWriters: (...values: Array<T>) => T, neutral: T);
|
|
24
|
+
createWriter(abortSignal: AbortSignal): StateActionWriter<T>;
|
|
25
|
+
get(): T;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* DeltaAction accumulates transient values each frame and clears them on frame advance.
|
|
29
|
+
* Multiple writes per frame from any source are combined using combine().
|
|
30
|
+
*/
|
|
31
|
+
export declare class DeltaAction<T> {
|
|
32
|
+
private readonly combine;
|
|
33
|
+
private readonly neutral;
|
|
34
|
+
constructor(combine: (...values: Array<T>) => T, neutral: T);
|
|
35
|
+
private readonly readers;
|
|
36
|
+
write(value: T): void;
|
|
37
|
+
createReader(abortSignal: AbortSignal): {
|
|
38
|
+
update(): void;
|
|
39
|
+
get(): T;
|
|
40
|
+
};
|
|
41
|
+
}
|