@pmndrs/viverse 0.1.19 → 0.2.0
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/bvh-bone-map.json +24 -0
- package/dist/animation/bvh.d.ts +4 -0
- package/dist/animation/bvh.js +8 -0
- package/dist/animation/default.d.ts +1 -0
- package/dist/animation/default.js +18 -0
- package/dist/animation/fbx.d.ts +2 -2
- package/dist/animation/gltf.d.ts +2 -2
- package/dist/animation/index.d.ts +16 -17
- package/dist/animation/index.js +118 -67
- package/dist/animation/mask.d.ts +3 -0
- package/dist/animation/mask.js +3 -0
- package/dist/camera.d.ts +3 -7
- package/dist/camera.js +16 -24
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/input/index.d.ts +6 -7
- package/dist/input/index.js +5 -4
- package/dist/input/keyboard.d.ts +3 -4
- package/dist/input/keyboard.js +8 -10
- package/dist/input/pointer-capture.d.ts +3 -3
- package/dist/input/pointer-capture.js +11 -12
- package/dist/input/pointer-lock.d.ts +3 -3
- package/dist/input/pointer-lock.js +10 -10
- package/dist/input/screen-joystick.d.ts +6 -10
- package/dist/input/screen-joystick.js +29 -36
- package/dist/input/screen-jump-button.d.ts +1 -1
- package/dist/model/index.d.ts +10 -13
- package/dist/model/index.js +26 -29
- package/dist/physics/index.d.ts +2 -5
- package/dist/physics/index.js +7 -16
- package/dist/simple-character/defaults.d.ts +2 -0
- package/dist/simple-character/defaults.js +2 -0
- package/dist/simple-character/index.d.ts +101 -0
- package/dist/simple-character/index.js +109 -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 +25 -0
- package/dist/simple-character/state/jump-forward.d.ts +5 -0
- package/dist/simple-character/state/jump-forward.js +39 -0
- package/dist/simple-character/state/jump-loop.d.ts +3 -0
- package/dist/simple-character/state/jump-loop.js +23 -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 +38 -0
- package/dist/simple-character/state/movement.d.ts +3 -0
- package/dist/simple-character/state/movement.js +59 -0
- package/dist/simple-character/update-input-velocity.d.ts +5 -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 +28 -39
- package/package.json +2 -2
- package/dist/simple-character.d.ts +0 -106
- package/dist/simple-character.js +0 -342
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export async function loadDefaultCharacterAnimationUrl(type) {
|
|
2
|
+
switch (type) {
|
|
3
|
+
case 'idle':
|
|
4
|
+
return (await import('../assets/idle.js')).url;
|
|
5
|
+
case 'jumpDown':
|
|
6
|
+
return (await import('../assets/jump-down.js')).url;
|
|
7
|
+
case 'jumpForward':
|
|
8
|
+
return (await import('../assets/jump-forward.js')).url;
|
|
9
|
+
case 'jumpLoop':
|
|
10
|
+
return (await import('../assets/jump-loop.js')).url;
|
|
11
|
+
case 'jumpUp':
|
|
12
|
+
return (await import('../assets/jump-up.js')).url;
|
|
13
|
+
case 'run':
|
|
14
|
+
return (await import('../assets/run.js')).url;
|
|
15
|
+
case 'walk':
|
|
16
|
+
return (await import('../assets/walk.js')).url;
|
|
17
|
+
}
|
|
18
|
+
}
|
package/dist/animation/fbx.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { AnimationClip } from 'three';
|
|
2
|
-
import {
|
|
2
|
+
import type { CharacterModel } from '../model/index.js';
|
|
3
3
|
import type { VRMHumanBoneName } from '@pixiv/three-vrm';
|
|
4
|
-
export declare function loadVrmModelFbxAnimations(model:
|
|
4
|
+
export declare function loadVrmModelFbxAnimations(model: CharacterModel, url: string, removeXZMovement: boolean, boneMap?: Record<string, VRMHumanBoneName>): Promise<Array<AnimationClip>>;
|
package/dist/animation/gltf.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { AnimationClip } from 'three';
|
|
2
|
-
import {
|
|
3
|
-
export declare function loadVrmModelGltfAnimations(model:
|
|
2
|
+
import type { CharacterModel, VRMHumanBoneName } from '../model/index.js';
|
|
3
|
+
export declare function loadVrmModelGltfAnimations(model: CharacterModel, url: string, removeXZMovement: boolean, boneMap?: Record<string, VRMHumanBoneName>): Promise<Array<AnimationClip>>;
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { VRMHumanBoneName } from '@pixiv/three-vrm';
|
|
2
2
|
import { AnimationClip, Object3D } from 'three';
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import { loadDefaultCharacterAnimationUrl } from './default.js';
|
|
4
|
+
import type { CharacterModel } from '../model/index.js';
|
|
5
|
+
export declare function fixModelAnimationClip(model: CharacterModel, clip: AnimationClip, clipScene: Object3D | undefined, removeXZMovement: boolean, boneMap?: Record<string, VRMHumanBoneName>): void;
|
|
5
6
|
export * from './gltf.js';
|
|
6
7
|
export * from './fbx.js';
|
|
7
8
|
export * from './vrma.js';
|
|
8
9
|
export * from './utils.js';
|
|
9
|
-
export type
|
|
10
|
-
|
|
11
|
-
url: string
|
|
10
|
+
export type CharacterAnimationMask = (boneName: VRMHumanBoneName) => boolean;
|
|
11
|
+
export type CharacterAnimationOptions = {
|
|
12
|
+
url: string | {
|
|
13
|
+
default: Parameters<typeof loadDefaultCharacterAnimationUrl>[0];
|
|
14
|
+
};
|
|
15
|
+
type?: 'mixamo' | 'gltf' | 'vrma' | 'fbx' | 'bvh';
|
|
12
16
|
removeXZMovement?: boolean;
|
|
13
17
|
trimTime?: {
|
|
14
18
|
start?: number;
|
|
@@ -16,17 +20,12 @@ export type ModelAnimationOptions = {
|
|
|
16
20
|
};
|
|
17
21
|
boneMap?: Record<string, VRMHumanBoneName>;
|
|
18
22
|
scaleTime?: number;
|
|
23
|
+
mask?: CharacterAnimationMask;
|
|
19
24
|
};
|
|
20
|
-
export
|
|
21
|
-
declare
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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>;
|
|
25
|
+
export type Tail<T extends any[]> = T extends [any, ...infer Rest] ? Rest : never;
|
|
26
|
+
export declare function flattenCharacterAnimationOptions(options: Exclude<CharacterAnimationOptions, false>): Tail<Parameters<typeof loadCharacterAnimation>>;
|
|
27
|
+
export declare function loadCharacterAnimation(model: CharacterModel, url: string | {
|
|
28
|
+
default: Parameters<typeof loadDefaultCharacterAnimationUrl>[0];
|
|
29
|
+
}, type?: CharacterAnimationOptions['type'], removeXZMovement?: boolean, trimStartTime?: number | undefined, trimEndTime?: number | undefined, boneMap?: Record<string, VRMHumanBoneName> | undefined, scaleTime?: number | undefined, mask?: CharacterAnimationMask): Promise<AnimationClip>;
|
|
32
30
|
export declare const mixamoBoneMap: Record<string, VRMHumanBoneName>;
|
|
31
|
+
export declare const bvhBoneMap: Record<string, VRMHumanBoneName>;
|
package/dist/animation/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { VRM } from '@pixiv/three-vrm';
|
|
2
2
|
import { Euler, Quaternion, QuaternionKeyframeTrack, Vector3, VectorKeyframeTrack, } from 'three';
|
|
3
|
+
import _bvhBoneMap from './bvh-bone-map.json';
|
|
4
|
+
import { loadVrmModelBvhAnimations } from './bvh.js';
|
|
5
|
+
import { loadDefaultCharacterAnimationUrl } from './default.js';
|
|
3
6
|
import { loadVrmModelFbxAnimations } from './fbx.js';
|
|
4
7
|
import { loadVrmModelGltfAnimations } from './gltf.js';
|
|
8
|
+
import _mixamoBoneMap from './mixamo-bone-map.json';
|
|
5
9
|
import { scaleAnimationClipTime, trimAnimationClip } from './utils.js';
|
|
6
10
|
import { loadVrmModelVrmaAnimations } from './vrma.js';
|
|
7
|
-
import { cached } from '../utils.js';
|
|
8
|
-
import _mixamoBoneMap from './mixamo-bone-map.json';
|
|
9
11
|
//helper variables for the quaternion retargeting
|
|
10
12
|
const baseThisLocalRestRotation_inverse = new Quaternion();
|
|
11
13
|
const baseThisLocalCurrentRotation = new Quaternion();
|
|
@@ -20,41 +22,77 @@ const position = new Vector3();
|
|
|
20
22
|
const nonVrmRotationOffset = new Quaternion().setFromEuler(new Euler(0, Math.PI, 0));
|
|
21
23
|
//TODO: currently assumes the model is not yet transformed - loaded for the first time
|
|
22
24
|
export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement, boneMap) {
|
|
23
|
-
const
|
|
24
|
-
if (
|
|
25
|
+
const hipsClipBoneName = boneMap == null ? 'hips' : Object.entries(boneMap).find(([, vrmBoneName]) => vrmBoneName === 'hips')?.[0];
|
|
26
|
+
if (hipsClipBoneName == null) {
|
|
25
27
|
throw new Error('Failed to determine hips bone name for VRM animation. Please check the bone map or animation file.');
|
|
26
28
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
let restRoot;
|
|
30
|
+
let restRootParent;
|
|
31
|
+
if (!(model instanceof VRM)) {
|
|
32
|
+
restRoot = model.scene.getObjectByName('rest_root');
|
|
33
|
+
if (restRoot == null) {
|
|
34
|
+
throw new Error(`Model rest root not found.`);
|
|
35
|
+
}
|
|
36
|
+
restRootParent = restRoot?.parent;
|
|
37
|
+
restRoot.parent = null;
|
|
38
|
+
}
|
|
39
|
+
let positionScale = 1;
|
|
40
|
+
let clipSceneHips;
|
|
41
|
+
if (clipScene != null) {
|
|
42
|
+
clipSceneHips = clipScene.getObjectByName(hipsClipBoneName);
|
|
43
|
+
clipSceneHips?.parent?.updateMatrixWorld();
|
|
44
|
+
const vrmHipsPosition = model instanceof VRM
|
|
45
|
+
? model.humanoid.normalizedRestPose.hips?.position
|
|
46
|
+
: model.scene.getObjectByName('rest_hips')?.getWorldPosition(new Vector3()).toArray();
|
|
47
|
+
if (clipSceneHips == null || vrmHipsPosition == null) {
|
|
48
|
+
throw new Error('Failed to load animation: missing animation hips object or VRM hips position.');
|
|
49
|
+
}
|
|
50
|
+
// Adjust with reference to hips height.
|
|
51
|
+
const motionHipsHeight = clipSceneHips.getWorldPosition(position).y;
|
|
52
|
+
const [_, vrmHipsHeight] = vrmHipsPosition;
|
|
53
|
+
positionScale = vrmHipsHeight / motionHipsHeight;
|
|
34
54
|
}
|
|
35
|
-
// Adjust with reference to hips height.
|
|
36
|
-
const motionHipsHeight = clipSceneHips.getWorldPosition(position).y;
|
|
37
|
-
const [_, vrmHipsHeight] = vrmHipsPosition;
|
|
38
|
-
const positionScale = vrmHipsHeight / motionHipsHeight;
|
|
39
55
|
for (const track of clip.tracks) {
|
|
40
56
|
// Convert each tracks for VRM use, and push to `tracks`
|
|
41
|
-
const [
|
|
42
|
-
const vrmBoneName = boneMap?.[
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
57
|
+
const [clipBoneName, propertyName] = track.name.split('.');
|
|
58
|
+
const vrmBoneName = (boneMap?.[clipBoneName] ?? clipBoneName);
|
|
59
|
+
const targetNormalizedBoneName = model instanceof VRM ? model.humanoid.getNormalizedBoneNode(vrmBoneName)?.name : vrmBoneName;
|
|
60
|
+
if (targetNormalizedBoneName == null) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const trackName = `${targetNormalizedBoneName}.${propertyName}`;
|
|
64
|
+
let targetLocalBoneTransform;
|
|
65
|
+
let targetParentWorldBoneTransform;
|
|
66
|
+
//for vrm targetLocalBoneTransform and targetParentWorldBoneTransform are the identity quaternion
|
|
67
|
+
if (model instanceof VRM) {
|
|
68
|
+
targetLocalBoneTransform = { rotation: [0, 0, 0, 1] };
|
|
69
|
+
targetParentWorldBoneTransform = { rotation: [0, 0, 0, 1] };
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
const targetBone = model.scene.getObjectByName(`rest_${vrmBoneName}`);
|
|
73
|
+
if (targetBone != null) {
|
|
74
|
+
targetLocalBoneTransform = { rotation: targetBone.quaternion.toArray() };
|
|
75
|
+
}
|
|
76
|
+
if (targetBone?.parent != null) {
|
|
77
|
+
targetParentWorldBoneTransform = { rotation: targetBone.parent.getWorldQuaternion(new Quaternion()).toArray() };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (targetLocalBoneTransform == null) {
|
|
47
81
|
continue;
|
|
48
82
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (vrmNodeName == null || baseBone == null) {
|
|
83
|
+
let baseBone = clipScene?.getObjectByName(clipBoneName);
|
|
84
|
+
if (clipScene != null && baseBone == null) {
|
|
52
85
|
continue;
|
|
53
86
|
}
|
|
54
87
|
if (track instanceof QuaternionKeyframeTrack) {
|
|
55
88
|
// Store rotations of rest-pose.
|
|
56
|
-
|
|
57
|
-
|
|
89
|
+
if (baseBone != null) {
|
|
90
|
+
baseThisLocalRestRotation_inverse.copy(baseBone.quaternion).invert();
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
baseThisLocalRestRotation_inverse.identity();
|
|
94
|
+
}
|
|
95
|
+
if (baseBone?.parent != null) {
|
|
58
96
|
baseBone.parent.getWorldQuaternion(baseParentWorldRestRotation);
|
|
59
97
|
baseParentWorldRestRotation_inverse.copy(baseParentWorldRestRotation).invert();
|
|
60
98
|
}
|
|
@@ -62,9 +100,9 @@ export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement,
|
|
|
62
100
|
baseParentWorldRestRotation.identity();
|
|
63
101
|
baseParentWorldRestRotation_inverse.identity();
|
|
64
102
|
}
|
|
65
|
-
targetThisLocalRestRotation.
|
|
66
|
-
if (
|
|
67
|
-
|
|
103
|
+
targetThisLocalRestRotation.fromArray(targetLocalBoneTransform.rotation ?? [0, 0, 0, 1]);
|
|
104
|
+
if (targetParentWorldBoneTransform != null) {
|
|
105
|
+
targetParentWorldRestRotation.fromArray(targetParentWorldBoneTransform.rotation ?? [0, 0, 0, 1]);
|
|
68
106
|
targetParentWorldRestRotation_inverse.copy(targetParentWorldRestRotation).invert();
|
|
69
107
|
}
|
|
70
108
|
else {
|
|
@@ -90,16 +128,18 @@ export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement,
|
|
|
90
128
|
}
|
|
91
129
|
targetThisLocalCurrentRotation.toArray(track.values, i);
|
|
92
130
|
}
|
|
93
|
-
track.name =
|
|
131
|
+
track.name = trackName;
|
|
94
132
|
}
|
|
95
133
|
else if (track instanceof VectorKeyframeTrack) {
|
|
96
|
-
|
|
134
|
+
if (vrmBoneName != 'hips' && vrmBoneName != 'root') {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
97
137
|
if (propertyName != 'position') {
|
|
98
138
|
continue;
|
|
99
139
|
}
|
|
100
140
|
for (let i = 0; i < track.values.length; i += 3) {
|
|
101
141
|
position.fromArray(track.values, i);
|
|
102
|
-
if (clipSceneHips
|
|
142
|
+
if (clipSceneHips?.parent != null) {
|
|
103
143
|
if (vrmBoneName === 'hips') {
|
|
104
144
|
position.applyMatrix4(clipSceneHips.parent.matrixWorld);
|
|
105
145
|
}
|
|
@@ -108,6 +148,9 @@ export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement,
|
|
|
108
148
|
}
|
|
109
149
|
}
|
|
110
150
|
position.multiplyScalar(positionScale);
|
|
151
|
+
if (!(model instanceof VRM) && vrmBoneName === 'hips') {
|
|
152
|
+
position.applyQuaternion(nonVrmRotationOffset);
|
|
153
|
+
}
|
|
111
154
|
if (model instanceof VRM) {
|
|
112
155
|
if (model.meta.metaVersion === '0') {
|
|
113
156
|
position.negate();
|
|
@@ -120,15 +163,53 @@ export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement,
|
|
|
120
163
|
}
|
|
121
164
|
position.toArray(track.values, i);
|
|
122
165
|
}
|
|
166
|
+
track.name = trackName;
|
|
123
167
|
}
|
|
124
168
|
}
|
|
169
|
+
if (restRoot != null && restRootParent != null) {
|
|
170
|
+
restRoot.parent = restRootParent;
|
|
171
|
+
}
|
|
125
172
|
}
|
|
126
173
|
export * from './gltf.js';
|
|
127
174
|
export * from './fbx.js';
|
|
128
175
|
export * from './vrma.js';
|
|
129
176
|
export * from './utils.js';
|
|
130
|
-
|
|
177
|
+
export function flattenCharacterAnimationOptions(options) {
|
|
178
|
+
return [
|
|
179
|
+
options.url,
|
|
180
|
+
options.type,
|
|
181
|
+
options.removeXZMovement,
|
|
182
|
+
options.trimTime?.start,
|
|
183
|
+
options.trimTime?.end,
|
|
184
|
+
options.boneMap,
|
|
185
|
+
options.scaleTime,
|
|
186
|
+
options.mask,
|
|
187
|
+
];
|
|
188
|
+
}
|
|
189
|
+
export async function loadCharacterAnimation(model, url, type, removeXZMovement = false, trimStartTime, trimEndTime, boneMap, scaleTime, mask) {
|
|
190
|
+
if (typeof url === 'object') {
|
|
191
|
+
url = await loadDefaultCharacterAnimationUrl(url.default);
|
|
192
|
+
type = 'gltf';
|
|
193
|
+
}
|
|
131
194
|
let clips;
|
|
195
|
+
if (type == null) {
|
|
196
|
+
const lowerCaseUrl = url.toLocaleLowerCase();
|
|
197
|
+
if (lowerCaseUrl.endsWith('.glb') || lowerCaseUrl.endsWith('.gltf')) {
|
|
198
|
+
type = 'gltf';
|
|
199
|
+
}
|
|
200
|
+
if (lowerCaseUrl.endsWith('.fbx')) {
|
|
201
|
+
type = 'fbx';
|
|
202
|
+
}
|
|
203
|
+
if (lowerCaseUrl.endsWith('.bvh')) {
|
|
204
|
+
type = 'bvh';
|
|
205
|
+
}
|
|
206
|
+
if (lowerCaseUrl.endsWith('.vrma')) {
|
|
207
|
+
type = 'vrma';
|
|
208
|
+
}
|
|
209
|
+
if (type == null) {
|
|
210
|
+
throw new Error(`Unable to infer animation type from url "${url}. Please specify the type of the animation manually."`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
132
213
|
switch (type) {
|
|
133
214
|
case 'gltf':
|
|
134
215
|
clips = await loadVrmModelGltfAnimations(model, url, removeXZMovement, boneMap);
|
|
@@ -136,6 +217,9 @@ async function uncachedLoadModelAnimation(model, type, url, removeXZMovement, tr
|
|
|
136
217
|
case 'fbx':
|
|
137
218
|
clips = await loadVrmModelFbxAnimations(model, url, removeXZMovement, boneMap);
|
|
138
219
|
break;
|
|
220
|
+
case 'bvh':
|
|
221
|
+
clips = await loadVrmModelBvhAnimations(model, url, removeXZMovement, boneMap ?? bvhBoneMap);
|
|
222
|
+
break;
|
|
139
223
|
case 'mixamo':
|
|
140
224
|
clips = await loadVrmModelFbxAnimations(model, url, removeXZMovement, boneMap ?? mixamoBoneMap);
|
|
141
225
|
break;
|
|
@@ -158,38 +242,5 @@ async function uncachedLoadModelAnimation(model, type, url, removeXZMovement, tr
|
|
|
158
242
|
}
|
|
159
243
|
return clip;
|
|
160
244
|
}
|
|
161
|
-
export function loadCharacterModelAnimation(model, options) {
|
|
162
|
-
return cached(uncachedLoadModelAnimation, [
|
|
163
|
-
model,
|
|
164
|
-
options.type,
|
|
165
|
-
options.url,
|
|
166
|
-
options.removeXZMovement ?? false,
|
|
167
|
-
options.trimTime?.start,
|
|
168
|
-
options.trimTime?.end,
|
|
169
|
-
options.boneMap,
|
|
170
|
-
options.scaleTime,
|
|
171
|
-
]);
|
|
172
|
-
}
|
|
173
|
-
const extraOptions = {
|
|
174
|
-
walk: { scaleTime: 0.5 },
|
|
175
|
-
run: { scaleTime: 0.8 },
|
|
176
|
-
jumpForward: { scaleTime: 0.9 },
|
|
177
|
-
};
|
|
178
|
-
const simpleCharacterAnimationUrls = {
|
|
179
|
-
walk: () => import('../assets/walk.js'),
|
|
180
|
-
run: () => import('../assets/run.js'),
|
|
181
|
-
idle: () => import('../assets/idle.js'),
|
|
182
|
-
jumpUp: () => import('../assets/jump-up.js'),
|
|
183
|
-
jumpLoop: () => import('../assets/jump-loop.js'),
|
|
184
|
-
jumpDown: () => import('../assets/jump-down.js'),
|
|
185
|
-
jumpForward: () => import('../assets/jump-forward.js'),
|
|
186
|
-
};
|
|
187
|
-
export const simpleCharacterAnimationNames = Object.keys(simpleCharacterAnimationUrls);
|
|
188
|
-
export async function getSimpleCharacterModelAnimationOptions(animationName) {
|
|
189
|
-
return {
|
|
190
|
-
type: 'gltf',
|
|
191
|
-
...extraOptions[animationName],
|
|
192
|
-
url: (await simpleCharacterAnimationUrls[animationName]()).url,
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
245
|
export const mixamoBoneMap = _mixamoBoneMap;
|
|
246
|
+
export const bvhBoneMap = _bvhBoneMap;
|
package/dist/camera.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Object3D, Vector3, Vector3Tuple, Ray } from 'three';
|
|
2
|
-
import {
|
|
2
|
+
import { InputSystem } from './input/index.js';
|
|
3
3
|
export declare const FirstPersonCharacterCameraBehavior: SimpleCharacterCameraBehaviorOptions;
|
|
4
4
|
export type SimpleCharacterCameraBehaviorOptions = {
|
|
5
5
|
/**
|
|
@@ -55,16 +55,12 @@ export type SimpleCharacterCameraBehaviorOptions = {
|
|
|
55
55
|
maxDistance?: number;
|
|
56
56
|
} | boolean;
|
|
57
57
|
} | boolean;
|
|
58
|
-
export declare class
|
|
59
|
-
getCamera: () => Object3D;
|
|
60
|
-
character: SimpleCharacter;
|
|
61
|
-
private readonly raycast?;
|
|
58
|
+
export declare class CharacterCameraBehavior {
|
|
62
59
|
rotationPitch: number;
|
|
63
60
|
rotationYaw: number;
|
|
64
61
|
zoomDistance: number;
|
|
65
62
|
private collisionFreeZoomDistance;
|
|
66
63
|
private firstUpdate;
|
|
67
|
-
constructor(getCamera: () => Object3D, character: SimpleCharacter, raycast?: ((ray: Ray, far: number) => number | undefined) | undefined);
|
|
68
64
|
private setRotationFromDelta;
|
|
69
65
|
private setDistanceFromDelta;
|
|
70
66
|
private computeCharacterBaseOffset;
|
|
@@ -74,5 +70,5 @@ export declare class SimpleCharacterCameraBehavior {
|
|
|
74
70
|
/**
|
|
75
71
|
* @param delta in seconds
|
|
76
72
|
*/
|
|
77
|
-
update(deltaTime: number, options?: SimpleCharacterCameraBehaviorOptions): void;
|
|
73
|
+
update(camera: Object3D, target: Object3D, inputSystem: InputSystem, deltaTime: number, raycast?: (ray: Ray, far: number) => number | undefined, options?: SimpleCharacterCameraBehaviorOptions): void;
|
|
78
74
|
}
|
package/dist/camera.js
CHANGED
|
@@ -11,25 +11,17 @@ const sphericalOffset = new Vector3();
|
|
|
11
11
|
const characterWorldPosition = new Vector3();
|
|
12
12
|
const euler = new Euler();
|
|
13
13
|
const rayHelper = new Ray();
|
|
14
|
-
export class
|
|
15
|
-
getCamera;
|
|
16
|
-
character;
|
|
17
|
-
raycast;
|
|
14
|
+
export class CharacterCameraBehavior {
|
|
18
15
|
rotationPitch = (-20 * Math.PI) / 180;
|
|
19
16
|
rotationYaw = 0;
|
|
20
17
|
zoomDistance = 4; // Changed from zoom to distance for clearer semantics
|
|
21
18
|
//internal state
|
|
22
19
|
collisionFreeZoomDistance = this.zoomDistance;
|
|
23
20
|
firstUpdate = true;
|
|
24
|
-
|
|
25
|
-
this.getCamera = getCamera;
|
|
26
|
-
this.character = character;
|
|
27
|
-
this.raycast = raycast;
|
|
28
|
-
}
|
|
29
|
-
setRotationFromDelta(delta, rotationOptions) {
|
|
21
|
+
setRotationFromDelta(camera, delta, rotationOptions) {
|
|
30
22
|
if (delta.lengthSq() < 0.0001) {
|
|
31
23
|
// use current camera rotation if very close to target
|
|
32
|
-
euler.setFromQuaternion(
|
|
24
|
+
euler.setFromQuaternion(camera.quaternion, 'YXZ');
|
|
33
25
|
this.rotationPitch = euler.x;
|
|
34
26
|
this.rotationYaw = euler.y;
|
|
35
27
|
return;
|
|
@@ -63,7 +55,7 @@ export class SimpleCharacterCameraBehavior {
|
|
|
63
55
|
/**
|
|
64
56
|
* @param delta in seconds
|
|
65
57
|
*/
|
|
66
|
-
update(deltaTime, options = true) {
|
|
58
|
+
update(camera, target, inputSystem, deltaTime, raycast, options = true) {
|
|
67
59
|
if (options === false) {
|
|
68
60
|
this.firstUpdate = true;
|
|
69
61
|
return;
|
|
@@ -74,33 +66,33 @@ export class SimpleCharacterCameraBehavior {
|
|
|
74
66
|
}
|
|
75
67
|
//compute character->camera delta through offset
|
|
76
68
|
this.computeCharacterBaseOffset(chracterBaseOffsetHelper, options.characterBaseOffset);
|
|
77
|
-
|
|
69
|
+
target.getWorldPosition(characterWorldPosition);
|
|
78
70
|
characterWorldPosition.add(chracterBaseOffsetHelper);
|
|
79
|
-
|
|
71
|
+
camera.getWorldPosition(deltaHelper);
|
|
80
72
|
deltaHelper.sub(characterWorldPosition);
|
|
81
73
|
// apply rotation input to rotationYaw and rotationPitch if not disabled or first update
|
|
82
74
|
let rotationOptions = options.rotation ?? true;
|
|
83
75
|
if (!this.firstUpdate && rotationOptions !== false) {
|
|
84
76
|
rotationOptions = rotationOptions === true ? {} : rotationOptions;
|
|
85
77
|
const rotationSpeed = rotationOptions.speed ?? 1000.0;
|
|
86
|
-
const deltaYaw =
|
|
87
|
-
const deltaPitch =
|
|
78
|
+
const deltaYaw = inputSystem.get(DeltaYawField);
|
|
79
|
+
const deltaPitch = inputSystem.get(DeltaPitchField);
|
|
88
80
|
this.rotationYaw = this.clampYaw(this.rotationYaw + deltaYaw * rotationSpeed * deltaTime, rotationOptions);
|
|
89
81
|
this.rotationPitch = this.clampPitch(this.rotationPitch + deltaPitch * rotationSpeed * deltaTime, rotationOptions);
|
|
90
82
|
}
|
|
91
83
|
else {
|
|
92
|
-
this.setRotationFromDelta(deltaHelper, typeof rotationOptions === 'boolean' ? {} : rotationOptions);
|
|
84
|
+
this.setRotationFromDelta(camera, deltaHelper, typeof rotationOptions === 'boolean' ? {} : rotationOptions);
|
|
93
85
|
}
|
|
94
86
|
// apply yaw and pitch to camera rotation
|
|
95
|
-
|
|
96
|
-
rayHelper.direction.set(0, 0, 1).applyEuler(
|
|
87
|
+
camera.rotation.set(this.rotationPitch, this.rotationYaw, 0, 'YXZ');
|
|
88
|
+
rayHelper.direction.set(0, 0, 1).applyEuler(camera.rotation);
|
|
97
89
|
rayHelper.origin.copy(characterWorldPosition);
|
|
98
90
|
// apply zoom input to zoomDistance if not disabled or first update
|
|
99
91
|
let zoomOptions = options.zoom ?? true;
|
|
100
92
|
if (!this.firstUpdate && zoomOptions !== false) {
|
|
101
93
|
zoomOptions = zoomOptions === true ? {} : zoomOptions;
|
|
102
94
|
const zoomSpeed = zoomOptions.speed ?? 1000.0;
|
|
103
|
-
const deltaZoom =
|
|
95
|
+
const deltaZoom = inputSystem.get(DeltaZoomField);
|
|
104
96
|
const zoomFactor = 1 + deltaZoom * zoomSpeed * deltaTime;
|
|
105
97
|
if (deltaZoom >= 0) {
|
|
106
98
|
this.zoomDistance *= zoomFactor;
|
|
@@ -119,19 +111,19 @@ export class SimpleCharacterCameraBehavior {
|
|
|
119
111
|
if (collisionOptions === true) {
|
|
120
112
|
collisionOptions = {};
|
|
121
113
|
}
|
|
122
|
-
let distance =
|
|
114
|
+
let distance = raycast?.(rayHelper, this.zoomDistance);
|
|
123
115
|
if (distance != null) {
|
|
124
116
|
this.collisionFreeZoomDistance = distance - (collisionOptions?.offset ?? 0.2);
|
|
125
117
|
}
|
|
126
118
|
}
|
|
127
119
|
// Calculate camera position using spherical coordinates from euler
|
|
128
120
|
sphericalOffset.set(0, 0, this.collisionFreeZoomDistance);
|
|
129
|
-
sphericalOffset.applyEuler(
|
|
121
|
+
sphericalOffset.applyEuler(camera.rotation);
|
|
130
122
|
// Get target position with offset (reuse helper vector)
|
|
131
|
-
|
|
123
|
+
target.getWorldPosition(characterWorldPosition);
|
|
132
124
|
this.computeCharacterBaseOffset(chracterBaseOffsetHelper, options.characterBaseOffset);
|
|
133
125
|
characterWorldPosition.add(chracterBaseOffsetHelper);
|
|
134
126
|
// Set camera position relative to target
|
|
135
|
-
|
|
127
|
+
camera.position.copy(characterWorldPosition).add(sphericalOffset);
|
|
136
128
|
}
|
|
137
129
|
}
|
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') {
|
package/dist/input/index.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
export declare class InputSystem {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
}>, options?: {});
|
|
1
|
+
export declare class InputSystem<T extends {} = {}> {
|
|
2
|
+
options: T;
|
|
3
|
+
readonly inputs: Array<Input>;
|
|
4
|
+
constructor(options?: T);
|
|
6
5
|
add(input: Input): void;
|
|
7
6
|
remove(input: Input): void;
|
|
8
7
|
dispose(): void;
|
|
@@ -21,8 +20,8 @@ export declare const RunField: InputField<boolean>;
|
|
|
21
20
|
export declare const DeltaZoomField: InputField<number>;
|
|
22
21
|
export declare const DeltaYawField: InputField<number>;
|
|
23
22
|
export declare const DeltaPitchField: InputField<number>;
|
|
24
|
-
export interface Input {
|
|
25
|
-
get<T>(field: InputField<T
|
|
23
|
+
export interface Input<O = {}> {
|
|
24
|
+
get<T>(field: InputField<T>, options: O): T | undefined;
|
|
26
25
|
dispose?(): void;
|
|
27
26
|
}
|
|
28
27
|
export * from './pointer-lock.js';
|
package/dist/input/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export class InputSystem {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
options;
|
|
3
|
+
inputs = [];
|
|
4
|
+
constructor(options = {}) {
|
|
5
|
+
this.options = options;
|
|
5
6
|
}
|
|
6
7
|
add(input) {
|
|
7
8
|
this.inputs.push(input);
|
|
@@ -20,7 +21,7 @@ export class InputSystem {
|
|
|
20
21
|
get(field) {
|
|
21
22
|
let current;
|
|
22
23
|
for (const input of this.inputs) {
|
|
23
|
-
const result = input.get(field);
|
|
24
|
+
const result = input.get(field, this.options);
|
|
24
25
|
if (result == null) {
|
|
25
26
|
continue;
|
|
26
27
|
}
|
package/dist/input/keyboard.d.ts
CHANGED
|
@@ -7,11 +7,10 @@ export type LocomotionKeyboardInputOptions = {
|
|
|
7
7
|
keyboardRunKeys?: Array<string>;
|
|
8
8
|
keyboardJumpKeys?: Array<string>;
|
|
9
9
|
};
|
|
10
|
-
export declare class LocomotionKeyboardInput implements Input {
|
|
11
|
-
private readonly options;
|
|
10
|
+
export declare class LocomotionKeyboardInput implements Input<LocomotionKeyboardInputOptions> {
|
|
12
11
|
private readonly abortController;
|
|
13
12
|
private readonly keyState;
|
|
14
|
-
constructor(domElement: HTMLElement
|
|
15
|
-
get<T>(field: InputField<T
|
|
13
|
+
constructor(domElement: HTMLElement);
|
|
14
|
+
get<T>(field: InputField<T>, options: LocomotionKeyboardInputOptions): T | undefined;
|
|
16
15
|
dispose(): void;
|
|
17
16
|
}
|
package/dist/input/keyboard.js
CHANGED
|
@@ -6,11 +6,9 @@ const DefaultMoveRightKeys = ['KeyD'];
|
|
|
6
6
|
const DefaultRunKeys = ['ShiftRight', 'ShiftLeft'];
|
|
7
7
|
const DefaultJumpKeys = ['Space'];
|
|
8
8
|
export class LocomotionKeyboardInput {
|
|
9
|
-
options;
|
|
10
9
|
abortController = new AbortController();
|
|
11
10
|
keyState = new Map();
|
|
12
|
-
constructor(domElement
|
|
13
|
-
this.options = options;
|
|
11
|
+
constructor(domElement) {
|
|
14
12
|
domElement.tabIndex = 0;
|
|
15
13
|
domElement.addEventListener('keydown', (event) => {
|
|
16
14
|
let state = this.keyState.get(event.code);
|
|
@@ -41,9 +39,9 @@ export class LocomotionKeyboardInput {
|
|
|
41
39
|
signal: this.abortController.signal,
|
|
42
40
|
});
|
|
43
41
|
}
|
|
44
|
-
get(field) {
|
|
42
|
+
get(field, options) {
|
|
45
43
|
if (field === LastTimeJumpPressedField) {
|
|
46
|
-
const jumpKeys =
|
|
44
|
+
const jumpKeys = options.keyboardJumpKeys ?? DefaultJumpKeys;
|
|
47
45
|
const pressed = jumpKeys
|
|
48
46
|
.map((key) => this.keyState.get(key)?.pressTime ?? null)
|
|
49
47
|
.filter((t) => t != null);
|
|
@@ -52,19 +50,19 @@ export class LocomotionKeyboardInput {
|
|
|
52
50
|
let keys;
|
|
53
51
|
switch (field) {
|
|
54
52
|
case MoveForwardField:
|
|
55
|
-
keys =
|
|
53
|
+
keys = options.keyboardMoveForwardKeys ?? DefaultMoveForwardKeys;
|
|
56
54
|
break;
|
|
57
55
|
case MoveBackwardField:
|
|
58
|
-
keys =
|
|
56
|
+
keys = options.keyboardMoveBackwardKeys ?? DefaultMoveBackwardKeys;
|
|
59
57
|
break;
|
|
60
58
|
case MoveLeftField:
|
|
61
|
-
keys =
|
|
59
|
+
keys = options.keyboardMoveLeftKeys ?? DefaultMoveLeftKeys;
|
|
62
60
|
break;
|
|
63
61
|
case MoveRightField:
|
|
64
|
-
keys =
|
|
62
|
+
keys = options.keyboardMoveRightKeys ?? DefaultMoveRightKeys;
|
|
65
63
|
break;
|
|
66
64
|
case RunField:
|
|
67
|
-
keys =
|
|
65
|
+
keys = options.keyboardRunKeys ?? DefaultRunKeys;
|
|
68
66
|
break;
|
|
69
67
|
}
|
|
70
68
|
if (keys == null) {
|