@react-three/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.
@@ -0,0 +1,9 @@
1
+ import { CharacterAnimationOptions, StartAnimationOptions } from '@pmndrs/viverse';
2
+ import { RootState } from '@react-three/fiber';
3
+ import { ActionParams } from '@react-three/timeline';
4
+ import { AnimationActionLoopStyles } from 'three';
5
+ export declare function CharacterAnimationAction({ until, init, dependencies, update, fadeDuration, paused, loop, ...animationOptions }: {
6
+ dependencies?: Array<unknown>;
7
+ until?: () => Promise<unknown>;
8
+ loop?: AnimationActionLoopStyles;
9
+ } & Omit<ActionParams<RootState>, 'until'> & CharacterAnimationOptions & StartAnimationOptions): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { startAnimation } from '@pmndrs/viverse';
3
+ import { Action, animationFinished } from '@react-three/timeline';
4
+ import { useMemo } from 'react';
5
+ import { LoopRepeat } from 'three';
6
+ import { useCharacterModel } from './model.js';
7
+ import { useCharacterAnimationLoader } from './utils.js';
8
+ export function CharacterAnimationAction({ until, init, dependencies, update, fadeDuration = 0.1, paused, loop, ...animationOptions }) {
9
+ const model = useCharacterModel();
10
+ const clip = useCharacterAnimationLoader(model, animationOptions);
11
+ const animation = useMemo(() => model.mixer.clipAction(clip), [clip, model]);
12
+ animation.clampWhenFinished = true;
13
+ animation.loop = loop ?? LoopRepeat;
14
+ return (_jsx(Action, { init: () => {
15
+ startAnimation(animation, model.currentAnimations, { fadeDuration, paused });
16
+ return init?.();
17
+ }, until: until ?? (() => animationFinished(animation)), update: update, dependencies: dependencies }));
18
+ }
package/dist/bone.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { VRMHumanBoneName } from '@pixiv/three-vrm';
2
+ import { ReactNode } from 'react';
3
+ export declare function CharacterModelBone({ bone, children }: {
4
+ bone: VRMHumanBoneName;
5
+ children?: ReactNode;
6
+ }): import("react/jsx-runtime").JSX.Element | null;
package/dist/bone.js ADDED
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { VRM } from '@pixiv/three-vrm';
3
+ import { createPortal } from '@react-three/fiber';
4
+ import { useMemo } from 'react';
5
+ import { Fragment } from 'react/jsx-runtime';
6
+ import { useCharacterModel } from './model.js';
7
+ export function CharacterModelBone({ bone, children }) {
8
+ const model = useCharacterModel();
9
+ const boneObject = useMemo(() => (model instanceof VRM ? model.humanoid.getRawBoneNode(bone) : model.scene.getObjectByName(bone)), [model, bone]);
10
+ if (boneObject == null) {
11
+ return null;
12
+ }
13
+ return (_jsx(Fragment, { children: createPortal(_jsx("group", { quaternion: model.boneRotationOffset, children: children }), boneObject) }, boneObject.id));
14
+ }
package/dist/gamepad.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  import { Input } from '@pmndrs/viverse';
2
- export declare function useXRControllerInput(): Input;
2
+ export declare function useXRControllerInput(): Input<{}>;
package/dist/index.d.ts CHANGED
@@ -67,10 +67,15 @@ export declare function useViversePublicAvatarList(): Awaited<ReturnType<AvatarC
67
67
  * Uses React Suspense for data fetching.
68
68
  */
69
69
  export declare function useViversePublicAvatarByID(id: string): Awaited<ReturnType<AvatarClient['getPublicAvatarByID']>> | undefined;
70
- export * from './character.js';
71
70
  export * from './material.js';
72
- export { mixamoBoneMap, FirstPersonCharacterCameraBehavior, SimpleCharacter as SimpleCharacterImpl, type SimpleCharacterOptions, LocomotionKeyboardInput, PointerCaptureInput, PointerLockInput, type InputField, InputSystem, type LocomotionKeyboardInputOptions, type PointerLockInputOptions, type PointerCaptureInputOptions, ScreenJoystickInput, type ScreenJoystickInputOptions, type SimpleCharacterInputOptions, ScreenJumpButtonInput, VRMHumanBoneName, } from '@pmndrs/viverse';
71
+ export * as Vanilla from '@pmndrs/viverse';
73
72
  export * from '@viverse/sdk';
74
73
  export * from '@viverse/sdk/avatar-client';
75
74
  export * from './gamepad.js';
76
75
  export * from './mobile.js';
76
+ export * from './physics.js';
77
+ export * from './utils.js';
78
+ export * from './model.js';
79
+ export * from './animation.js';
80
+ export * from './bone.js';
81
+ export * from './simple.js';
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { Client } from '@viverse/sdk';
3
3
  import AvatarClient from '@viverse/sdk/avatar-client';
4
4
  import { createContext, useCallback, useContext } from 'react';
5
5
  import { suspend, clear } from 'suspend-react';
6
- import { BvhPhysicsWorld } from './character.js';
6
+ import { BvhPhysicsWorld } from './physics.js';
7
7
  // auth
8
8
  const viverseCheckAuthSymbol = Symbol('viverse-check-auth');
9
9
  const authSuspenseKeys = [];
@@ -152,10 +152,15 @@ export function useViversePublicAvatarByID(id) {
152
152
  const avatarClient = useViverseAvatarClient();
153
153
  return suspend(async () => avatarClient?.getPublicAvatarByID(id), [viversePublicAvatarByIDSymbol, avatarClient, id]);
154
154
  }
155
- export * from './character.js';
156
155
  export * from './material.js';
157
- export { mixamoBoneMap, FirstPersonCharacterCameraBehavior, SimpleCharacter as SimpleCharacterImpl, LocomotionKeyboardInput, PointerCaptureInput, PointerLockInput, InputSystem, ScreenJoystickInput, ScreenJumpButtonInput, VRMHumanBoneName, } from '@pmndrs/viverse';
156
+ export * as Vanilla from '@pmndrs/viverse';
158
157
  export * from '@viverse/sdk';
159
158
  export * from '@viverse/sdk/avatar-client';
160
159
  export * from './gamepad.js';
161
160
  export * from './mobile.js';
161
+ export * from './physics.js';
162
+ export * from './utils.js';
163
+ export * from './model.js';
164
+ export * from './animation.js';
165
+ export * from './bone.js';
166
+ export * from './simple.js';
@@ -0,0 +1,7 @@
1
+ import { CharacterModel } from '@pmndrs/viverse';
2
+ import { ReactNode } from 'react';
3
+ export declare function CharacterModelProvider({ model, children }: {
4
+ children?: ReactNode;
5
+ model: CharacterModel;
6
+ }): import("react/jsx-runtime").JSX.Element;
7
+ export declare function useCharacterModel(): CharacterModel;
package/dist/model.js ADDED
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext } from 'react';
3
+ const CharacterModelContext = createContext(undefined);
4
+ export function CharacterModelProvider({ model, children }) {
5
+ return _jsx(CharacterModelContext.Provider, { value: model, children: children });
6
+ }
7
+ export function useCharacterModel() {
8
+ const model = useContext(CharacterModelContext);
9
+ if (model == null) {
10
+ throw new Error(`useCharacterModel can only be used inside a CharacterModelProvider`);
11
+ }
12
+ return model;
13
+ }
@@ -0,0 +1,36 @@
1
+ import { BvhCharacterPhysics, BvhCharacterPhysicsOptions, BvhPhysicsWorld as VanillaBvhPhysicsWorld } from '@pmndrs/viverse';
2
+ import { ReactNode, RefObject } from 'react';
3
+ import { Object3D } from 'three';
4
+ /**
5
+ * provides the bvh physics world context
6
+ */
7
+ export declare function BvhPhysicsWorld({ children }: {
8
+ children?: ReactNode;
9
+ }): import("react/jsx-runtime").JSX.Element;
10
+ export declare function useBvhPhysicsWorld(): VanillaBvhPhysicsWorld;
11
+ export declare function useBvhCharacterPhysics(model: Object3D | RefObject<Object3D | null>, options?: BvhCharacterPhysicsOptions): BvhCharacterPhysics;
12
+ /**
13
+ * @deprecated use <BvhPhysicsBody kinematic={false} /> instead (kinematic={false} can be skipped as its the default)
14
+ */
15
+ export declare const FixedBvhPhysicsBody: import("react").ForwardRefExoticComponent<{
16
+ children?: ReactNode;
17
+ } & import("react").RefAttributes<Object3D<import("three").Object3DEventMap>>>;
18
+ /**
19
+ * allows to add all children as static (non-moving) objects as sensors to the bvh physics world
20
+ * @requires that the structure of the inner content is not changing or has a suspense boundary
21
+ * do not wrap the content inside in a suspense!
22
+ */
23
+ export declare const BvhPhysicsSensor: import("react").ForwardRefExoticComponent<{
24
+ children?: ReactNode;
25
+ isStatic?: boolean;
26
+ onIntersectedChanged?: (intersected: boolean) => void;
27
+ } & import("react").RefAttributes<Object3D<import("three").Object3DEventMap>>>;
28
+ /**
29
+ * allows to add all children as static (non-moving) or kinematic (moving) objects as obstacles to the bvh physics world
30
+ * @requires that the structure of the inner content is not changing or has a suspense boundary
31
+ * do not wrap the content inside in a suspense!
32
+ */
33
+ export declare const BvhPhysicsBody: import("react").ForwardRefExoticComponent<{
34
+ children?: ReactNode;
35
+ kinematic?: boolean;
36
+ } & import("react").RefAttributes<Object3D<import("three").Object3DEventMap>>>;
@@ -0,0 +1,78 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { BvhCharacterPhysics, BvhPhysicsWorld as VanillaBvhPhysicsWorld, } from '@pmndrs/viverse';
3
+ import { useFrame } from '@react-three/fiber';
4
+ import { createContext, forwardRef, useContext, useEffect, useImperativeHandle, useMemo, useRef, } from 'react';
5
+ import { Object3D } from 'three';
6
+ const BvhPhyiscsWorldContext = createContext(undefined);
7
+ /**
8
+ * provides the bvh physics world context
9
+ */
10
+ export function BvhPhysicsWorld({ children }) {
11
+ const world = useMemo(() => new VanillaBvhPhysicsWorld(), []);
12
+ return _jsx(BvhPhyiscsWorldContext.Provider, { value: world, children: children });
13
+ }
14
+ export function useBvhPhysicsWorld() {
15
+ const world = useContext(BvhPhyiscsWorldContext);
16
+ if (world == null) {
17
+ throw new Error('useBvhPhysicsWorld must be used within a BvhPhysicsWorld component');
18
+ }
19
+ return world;
20
+ }
21
+ export function useBvhCharacterPhysics(model, options) {
22
+ const world = useBvhPhysicsWorld();
23
+ const characterPhysics = useMemo(() => new BvhCharacterPhysics(world), [world]);
24
+ useFrame((_, delta) => {
25
+ const resolvedModel = model instanceof Object3D ? model : model.current;
26
+ if (resolvedModel == null) {
27
+ return;
28
+ }
29
+ characterPhysics.update(resolvedModel, delta, options);
30
+ });
31
+ return characterPhysics;
32
+ }
33
+ /**
34
+ * @deprecated use <BvhPhysicsBody kinematic={false} /> instead (kinematic={false} can be skipped as its the default)
35
+ */
36
+ export const FixedBvhPhysicsBody = forwardRef(({ children }, ref) => {
37
+ return _jsxs(BvhPhysicsBody, { children: [" ", children] });
38
+ });
39
+ /**
40
+ * allows to add all children as static (non-moving) objects as sensors to the bvh physics world
41
+ * @requires that the structure of the inner content is not changing or has a suspense boundary
42
+ * do not wrap the content inside in a suspense!
43
+ */
44
+ export const BvhPhysicsSensor = forwardRef(({ children, isStatic = true, onIntersectedChanged }, ref) => {
45
+ const world = useBvhPhysicsWorld();
46
+ const internalRef = useRef(null);
47
+ const listenerRef = useRef(onIntersectedChanged);
48
+ listenerRef.current = onIntersectedChanged;
49
+ useEffect(() => {
50
+ const body = internalRef.current;
51
+ if (body == null) {
52
+ return;
53
+ }
54
+ world.addSensor(body, isStatic, (intersected) => listenerRef.current?.(intersected));
55
+ return () => world.removeSensor(body);
56
+ }, [world, isStatic]);
57
+ useImperativeHandle(ref, () => internalRef.current, []);
58
+ return _jsx("group", { ref: internalRef, children: children });
59
+ });
60
+ /**
61
+ * allows to add all children as static (non-moving) or kinematic (moving) objects as obstacles to the bvh physics world
62
+ * @requires that the structure of the inner content is not changing or has a suspense boundary
63
+ * do not wrap the content inside in a suspense!
64
+ */
65
+ export const BvhPhysicsBody = forwardRef(({ children, kinematic = false }, ref) => {
66
+ const world = useBvhPhysicsWorld();
67
+ const internalRef = useRef(null);
68
+ useEffect(() => {
69
+ const body = internalRef.current;
70
+ if (body == null) {
71
+ return;
72
+ }
73
+ world.addBody(body, kinematic);
74
+ return () => world.removeBody(body);
75
+ }, [world, kinematic]);
76
+ useImperativeHandle(ref, () => internalRef.current, []);
77
+ return _jsx("group", { ref: internalRef, children: children });
78
+ });
@@ -0,0 +1,7 @@
1
+ import { SimpleCharacterOptions } from '@pmndrs/viverse';
2
+ import { ReactNode } from 'react';
3
+ import { Group, Object3D } from 'three';
4
+ export declare const SimpleCharacter: import("react").ForwardRefExoticComponent<Omit<{
5
+ children?: ReactNode;
6
+ useViverseAvatar?: boolean;
7
+ } & SimpleCharacterOptions & import("@react-three/fiber/dist/declarations/src/core/utils.js").Mutable<import("@react-three/fiber/dist/declarations/src/core/utils.js").Overwrite<Partial<import("@react-three/fiber/dist/declarations/src/core/utils.js").Overwrite<Group<import("three").Object3DEventMap>, import("@react-three/fiber").MathProps<Group<import("three").Object3DEventMap>> & import("@react-three/fiber").ReactProps<Group<import("three").Object3DEventMap>> & Partial<import("@react-three/fiber").EventHandlers>>>, Omit<import("@react-three/fiber").InstanceProps<Group<import("three").Object3DEventMap>, typeof Group>, "object">>>, "ref"> & import("react").RefAttributes<Object3D<import("three").Object3DEventMap>>>;
package/dist/simple.js ADDED
@@ -0,0 +1,67 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { RunField, shouldJump, updateSimpleCharacterInputVelocity, updateSimpleCharacterRotation, } from '@pmndrs/viverse';
3
+ import { useFrame } from '@react-three/fiber';
4
+ import { Graph, GrapthState, Parallel, RunTimeline, Switch, SwitchCase, timePassed } from '@react-three/timeline';
5
+ import { forwardRef, Suspense, useImperativeHandle, useRef } from 'react';
6
+ import { LoopOnce, Vector3 } from 'three';
7
+ import { CharacterAnimationAction } from './animation.js';
8
+ import { useViverseActiveAvatar } from './index.js';
9
+ import { CharacterModelProvider } from './model.js';
10
+ import { useBvhCharacterPhysics } from './physics.js';
11
+ import { useCharacterCameraBehavior, useCharacterModelLoader, useInputSystem } from './utils.js';
12
+ export const SimpleCharacter = forwardRef(({ input, inputOptions, cameraBehavior, children, movement, physics: physicsOptions, model, useViverseAvatar, animation, ...props }, ref) => {
13
+ const internalRef = useRef(null);
14
+ const inputSystem = useInputSystem(input, inputOptions);
15
+ useCharacterCameraBehavior(internalRef, inputSystem, cameraBehavior);
16
+ const physics = useBvhCharacterPhysics(internalRef, physicsOptions);
17
+ const lastJumpTimeRef = useRef(-Infinity);
18
+ useFrame((state) => updateSimpleCharacterInputVelocity(state.camera, inputSystem, physics, movement));
19
+ useFrame(() => {
20
+ if (model != false || movement?.jump === false) {
21
+ return;
22
+ }
23
+ const bufferTime = movement?.jump === true ? undefined : movement?.jump?.bufferTime;
24
+ if (shouldJump(physics, inputSystem, lastJumpTimeRef.current, bufferTime)) {
25
+ lastJumpTimeRef.current = performance.now() / 1000;
26
+ physics.applyVelocity(
27
+ // eslint-disable-next-line @react-three/no-new-in-loop
28
+ new Vector3(0, (typeof movement?.jump === 'object' ? movement?.jump.speed : undefined) ?? 8, 0));
29
+ }
30
+ });
31
+ useImperativeHandle(ref, () => internalRef.current, []);
32
+ return (_jsx("group", { ...props, ref: internalRef, children: model == false ? (children) : (_jsx(SimpleCharacterModel, { animation: animation, inputSystem: inputSystem, physics: physics, model: model == true ? undefined : model, movement: movement, useViverseAvatar: useViverseAvatar, children: children })) }));
33
+ });
34
+ function SimpleCharacterModel({ children, model: modelOptions, movement, physics, useViverseAvatar = true, inputSystem, animation, }) {
35
+ const avatar = useViverseActiveAvatar();
36
+ const model = useCharacterModelLoader(avatar != null && useViverseAvatar
37
+ ? {
38
+ type: 'vrm',
39
+ url: avatar?.vrmUrl,
40
+ ...modelOptions,
41
+ }
42
+ : modelOptions);
43
+ const lastJumpTimeRef = useRef(-Infinity);
44
+ useFrame((state, delta) => updateSimpleCharacterRotation(delta, physics, state.camera, model, animation));
45
+ return (_jsx(_Fragment, { children: _jsxs(CharacterModelProvider, { model: model, children: [_jsx(RunTimeline, { children: _jsxs(Graph, { enterState: "move", children: [_jsx(GrapthState, { name: "move", transitionTo: {
46
+ jumpStart: {
47
+ whenUpdate: () => shouldJump(physics, inputSystem, lastJumpTimeRef.current),
48
+ },
49
+ jumpLoop: { whenUpdate: () => !physics.isGrounded },
50
+ }, children: _jsxs(Switch, { children: [_jsx(SwitchCase, { index: 0, condition: () => physics.inputVelocity.lengthSq() === 0, children: _jsx(Suspense, { fallback: null, children: _jsx(CharacterAnimationAction, { ...animation?.idle, fadeDuration: animation?.crossFadeDuration, url: { default: 'idle' } }) }) }), movement?.run != false && (_jsx(SwitchCase, { index: 1, condition: () => inputSystem.get(RunField), children: _jsx(Suspense, { fallback: null, children: _jsx(CharacterAnimationAction, { ...animation?.run, fadeDuration: animation?.crossFadeDuration, scaleTime: 0.8, url: { default: 'run' } }) }) })), movement?.walk != false && (_jsx(SwitchCase, { index: 2, children: _jsx(Suspense, { fallback: null, children: _jsx(CharacterAnimationAction, { ...animation?.walk, fadeDuration: animation?.crossFadeDuration, scaleTime: 0.5, url: { default: 'walk' } }) }) })), _jsx(SwitchCase, { index: 3, children: _jsx(Suspense, { fallback: null, children: _jsx(CharacterAnimationAction, { ...animation?.idle, fadeDuration: animation?.crossFadeDuration, url: { default: 'idle' } }) }) })] }) }), _jsx(GrapthState, { name: "jumpStart", transitionTo: {
51
+ jumpDown: { whenUpdate: () => !physics.isGrounded },
52
+ finally: () => (inputSystem.get(RunField) ? 'jumpForward' : 'jumpUp'),
53
+ }, children: _jsx(Parallel, { type: "race", children: _jsxs(Suspense, { fallback: null, children: [_jsx(CharacterAnimationAction, { ...animation?.jumpUp, fadeDuration: animation?.crossFadeDuration ?? 0.1, until: () => timePassed(0.2, 'seconds'), update: () => void physics.inputVelocity.multiplyScalar(0.3), paused: true, url: { default: 'jumpUp' } }), _jsx(CharacterAnimationAction, { ...animation?.jumpForward, fadeDuration: animation?.crossFadeDuration ?? 0.1, paused: true, url: { default: 'jumpForward' } })] }) }) }), _jsx(GrapthState, { name: "jumpLoop", transitionTo: {
54
+ jumpDown: { whenUpdate: () => physics.isGrounded },
55
+ }, children: _jsx(Suspense, { fallback: null, children: _jsx(CharacterAnimationAction, { ...animation?.jumpLoop, fadeDuration: animation?.crossFadeDuration, url: { default: 'jumpLoop' } }) }) }), _jsx(GrapthState, { name: "jumpUp", transitionTo: {
56
+ jumpDown: {
57
+ whenUpdate: (_, _clock, actionTime) => actionTime > 0.3 && physics.isGrounded,
58
+ },
59
+ finally: 'jumpLoop',
60
+ }, children: _jsx(Suspense, { fallback: null, children: _jsx(CharacterAnimationAction, { ...animation?.jumpUp, fadeDuration: animation?.crossFadeDuration, loop: LoopOnce, init: () => {
61
+ lastJumpTimeRef.current = performance.now() / 1000;
62
+ physics.applyVelocity(new Vector3(0, (typeof movement?.jump === 'object' ? movement?.jump.speed : undefined) ?? 8, 0));
63
+ }, url: { default: 'jumpUp' } }) }) }), _jsx(GrapthState, { name: "jumpForward", transitionTo: { finally: () => (physics.isGrounded ? 'move' : 'jumpLoop') }, children: _jsx(Suspense, { fallback: null, children: _jsx(CharacterAnimationAction, { ...animation?.jumpForward, fadeDuration: animation?.crossFadeDuration, scaleTime: 0.9, init: () => {
64
+ lastJumpTimeRef.current = performance.now() / 1000;
65
+ physics.applyVelocity(new Vector3(0, 8, 0));
66
+ }, loop: LoopOnce, url: { default: 'jumpForward' } }) }) }), _jsx(GrapthState, { name: "jumpDown", transitionTo: { finally: 'move' }, children: _jsx(Suspense, { fallback: null, children: _jsx(CharacterAnimationAction, { ...animation?.jumpDown, fadeDuration: animation?.crossFadeDuration, until: () => timePassed(150, 'milliseconds'), loop: LoopOnce, url: { default: 'jumpDown' } }) }) })] }) }), _jsx("primitive", { object: model.scene }), children] }) }));
67
+ }
@@ -0,0 +1,9 @@
1
+ import { CharacterAnimationOptions, CharacterModelOptions, InputSystem, SimpleCharacterCameraBehaviorOptions, CharacterModel, Input } from '@pmndrs/viverse';
2
+ import { RefObject } from 'react';
3
+ import { Object3D } from 'three';
4
+ export declare function useCharacterCameraBehavior(model: Object3D | RefObject<Object3D | null>, inputSystem: InputSystem, options?: SimpleCharacterCameraBehaviorOptions): void;
5
+ export declare function useCharacterModelLoader(options?: CharacterModelOptions): CharacterModel;
6
+ export declare function useCharacterAnimationLoader(model: CharacterModel, options: CharacterAnimationOptions): import("three").AnimationClip;
7
+ export declare function useInputSystem<T extends object>(inputs?: ReadonlyArray<Input<T> | {
8
+ new (domElement: HTMLElement): Input<T>;
9
+ }>, options?: T): InputSystem<T>;
package/dist/utils.js ADDED
@@ -0,0 +1,67 @@
1
+ import { VRM } from '@pixiv/three-vrm';
2
+ import { CharacterCameraBehavior, flattenCharacterAnimationOptions, flattenCharacterModelOptions, InputSystem, loadCharacterModel, loadCharacterAnimation, ScreenJoystickInput, ScreenJumpButtonInput, PointerCaptureInput, LocomotionKeyboardInput, } from '@pmndrs/viverse';
3
+ import { useFrame, useThree } from '@react-three/fiber';
4
+ import { useEffect, useMemo } from 'react';
5
+ import { suspend } from 'suspend-react';
6
+ import { Object3D } from 'three';
7
+ import { useBvhPhysicsWorld } from './physics.js';
8
+ export function useCharacterCameraBehavior(model, inputSystem, options) {
9
+ const behavior = useMemo(() => new CharacterCameraBehavior(), []);
10
+ const world = useBvhPhysicsWorld();
11
+ const raycast = useMemo(() => world.raycast.bind(world), [world]);
12
+ useFrame((state, delta) => {
13
+ const resolvedModel = model instanceof Object3D ? model : model.current;
14
+ if (resolvedModel == null) {
15
+ return;
16
+ }
17
+ behavior.update(state.camera, resolvedModel, inputSystem, delta, raycast, options);
18
+ });
19
+ }
20
+ const loadCharacterModelSymbol = Symbol('loadCharacterModel');
21
+ export function useCharacterModelLoader(options) {
22
+ const model = suspend((_, ...params) => loadCharacterModel(...params), [loadCharacterModelSymbol, ...flattenCharacterModelOptions(options)]);
23
+ useFrame((_, delta) => {
24
+ if (model instanceof VRM) {
25
+ model.update(delta);
26
+ }
27
+ model.mixer.update(delta);
28
+ });
29
+ return model;
30
+ }
31
+ const loadCharacterAnimationSymbol = Symbol('loadCharacterAnimation');
32
+ export function useCharacterAnimationLoader(model, options) {
33
+ return suspend((_, ...params) => loadCharacterAnimation(...params), [loadCharacterAnimationSymbol, model, ...flattenCharacterAnimationOptions(options)]);
34
+ }
35
+ export function useInputSystem(inputs = [
36
+ ScreenJoystickInput,
37
+ ScreenJumpButtonInput,
38
+ PointerCaptureInput,
39
+ LocomotionKeyboardInput,
40
+ ], options = {}) {
41
+ const dom = useThree((s) => s.gl.domElement);
42
+ const optionsRef = useMemo(() => ({}), []);
43
+ //clearing and copying the options to optionsRef
44
+ for (const key in optionsRef) {
45
+ delete optionsRef[key];
46
+ }
47
+ Object.assign(optionsRef, options);
48
+ // eslint-disable-next-line react-hooks/exhaustive-deps
49
+ const system = useMemo(() => new InputSystem(optionsRef), []);
50
+ useEffect(() => {
51
+ const removedInputs = new Set(system.inputs);
52
+ for (const input of inputs) {
53
+ const existingInput = system.inputs.find((existingInput) => typeof input === 'function' ? existingInput instanceof input.constructor : existingInput === input);
54
+ if (existingInput != null) {
55
+ removedInputs.delete(existingInput);
56
+ continue;
57
+ }
58
+ system.add(typeof input === 'function' ? new input(dom) : input);
59
+ }
60
+ for (const removedInput of removedInputs) {
61
+ system.remove(removedInput);
62
+ removedInput.dispose?.();
63
+ }
64
+ });
65
+ useEffect(() => () => system.inputs.forEach((input) => input.dispose?.()), [system]);
66
+ return system;
67
+ }
package/package.json CHANGED
@@ -24,11 +24,12 @@
24
24
  ],
25
25
  "dependencies": {
26
26
  "@viverse/sdk": "^1.2.10-alpha.0",
27
+ "@react-three/timeline": "^0.3.5",
27
28
  "suspend-react": "^0.1.3",
28
29
  "@pixiv/three-vrm": "^3.4.2",
29
30
  "zustand": "^5.0.6",
30
31
  "@react-three/xr": "^6.6.20",
31
- "@pmndrs/viverse": "^0.1.19"
32
+ "@pmndrs/viverse": "^0.2.0"
32
33
  },
33
34
  "peerDependencies": {
34
35
  "@react-three/fiber": "*"
@@ -39,7 +40,7 @@
39
40
  "@types/react": "^19.1.8",
40
41
  "react": "^19.1.0"
41
42
  },
42
- "version": "0.1.19",
43
+ "version": "0.2.0",
43
44
  "scripts": {
44
45
  "build": "tsc",
45
46
  "check:prettier": "prettier --check src",
@@ -1,51 +0,0 @@
1
- import { SimpleCharacterOptions, SimpleCharacter as SimpleCharacterImpl, VRMHumanBoneName } from '@pmndrs/viverse';
2
- import { ThreeElement } from '@react-three/fiber';
3
- import { ReactNode } from 'react';
4
- import { Group, Object3D } from 'three';
5
- declare module '@react-three/fiber' {
6
- interface ThreeElements {
7
- simpleCharacterImpl: ThreeElement<typeof SimpleCharacterImpl>;
8
- }
9
- }
10
- /**
11
- * provides the bvh physics world context
12
- */
13
- export declare function BvhPhysicsWorld({ children }: {
14
- children?: ReactNode;
15
- }): import("react/jsx-runtime").JSX.Element;
16
- /**
17
- * creates a simple character controller supporting running, walking, and jumping with a default avatar and animations with can be configutred
18
- */
19
- export declare const SimpleCharacter: import("react").ForwardRefExoticComponent<Omit<SimpleCharacterOptions & {
20
- useViverseAvatar?: boolean;
21
- children?: ReactNode;
22
- } & import("@react-three/fiber/dist/declarations/src/core/utils.js").Mutable<import("@react-three/fiber/dist/declarations/src/core/utils.js").Overwrite<Partial<import("@react-three/fiber/dist/declarations/src/core/utils.js").Overwrite<Group<import("three").Object3DEventMap>, import("@react-three/fiber").MathProps<Group<import("three").Object3DEventMap>> & import("@react-three/fiber").ReactProps<Group<import("three").Object3DEventMap>> & Partial<import("@react-three/fiber").EventHandlers>>>, Omit<import("@react-three/fiber").InstanceProps<Group<import("three").Object3DEventMap>, typeof Group>, "object">>>, "ref"> & import("react").RefAttributes<Group<import("three").Object3DEventMap>>>;
23
- /**
24
- * @deprecated use <BvhPhysicsBody kinematic={false} /> instead (kinematic={false} can be skipped as its the default)
25
- */
26
- export declare const FixedBvhPhysicsBody: import("react").ForwardRefExoticComponent<{
27
- children?: ReactNode;
28
- } & import("react").RefAttributes<Object3D<import("three").Object3DEventMap>>>;
29
- /**
30
- * allows to add all children as static (non-moving) objects as sensors to the bvh physics world
31
- * @requires that the structure of the inner content is not changing or has a suspense boundary
32
- * do not wrap the content inside in a suspense!
33
- */
34
- export declare const BvhPhysicsSensor: import("react").ForwardRefExoticComponent<{
35
- children?: ReactNode;
36
- isStatic?: boolean;
37
- onIntersectedChanged?: (intersected: boolean) => void;
38
- } & import("react").RefAttributes<Object3D<import("three").Object3DEventMap>>>;
39
- /**
40
- * allows to add all children as static (non-moving) or kinematic (moving) objects as obstacles to the bvh physics world
41
- * @requires that the structure of the inner content is not changing or has a suspense boundary
42
- * do not wrap the content inside in a suspense!
43
- */
44
- export declare const BvhPhysicsBody: import("react").ForwardRefExoticComponent<{
45
- children?: ReactNode;
46
- kinematic?: boolean;
47
- } & import("react").RefAttributes<Object3D<import("three").Object3DEventMap>>>;
48
- export declare function CharacterModelBone({ bone, children }: {
49
- bone: VRMHumanBoneName;
50
- children?: ReactNode;
51
- }): import("react/jsx-runtime").JSX.Element | null;
package/dist/character.js DELETED
@@ -1,167 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { VRM } from '@pixiv/three-vrm';
3
- import { BvhPhysicsWorld as BvhPhysicsWorldImpl, SimpleCharacter as SimpleCharacterImpl, preloadSimpleCharacterAssets, simpleCharacterAnimationNames, InputSystem, LocomotionKeyboardInput, PointerCaptureInput, ScreenJoystickInput, ScreenJumpButtonInput, extractProxy, } from '@pmndrs/viverse';
4
- import { useFrame, useThree, extend, createPortal, useStore } from '@react-three/fiber';
5
- import { createContext, forwardRef, Fragment, useCallback, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState, } from 'react';
6
- import { clear, suspend } from 'suspend-react';
7
- import { create } from 'zustand';
8
- import { useViverseActiveAvatar } from './index.js';
9
- const BvhPhyiscsWorldContext = createContext(undefined);
10
- extend({ SimpleCharacterImpl });
11
- const CharacterModelStoreContext = createContext(undefined);
12
- /**
13
- * provides the bvh physics world context
14
- */
15
- export function BvhPhysicsWorld({ children }) {
16
- const world = useMemo(() => new BvhPhysicsWorldImpl(), []);
17
- return _jsx(BvhPhyiscsWorldContext.Provider, { value: world, children: children });
18
- }
19
- const PreloadSimpleCharacterAssetsSymbol = Symbol('preload-simple-character-assets');
20
- /**
21
- * creates a simple character controller supporting running, walking, and jumping with a default avatar and animations with can be configutred
22
- */
23
- export const SimpleCharacter = forwardRef(({ children, useViverseAvatar = true, input, movement, model, physics, cameraBehavior, animation, inputOptions, ...groupProps }, ref) => {
24
- const avatar = useViverseActiveAvatar();
25
- const world = useContext(BvhPhyiscsWorldContext);
26
- if (world == null) {
27
- throw new Error('SimpleCharacter must be used within a BvhPhysicsWorld component');
28
- }
29
- const domElement = useThree((s) => s.gl.domElement);
30
- const newOptions = {
31
- inputOptions,
32
- movement,
33
- physics,
34
- cameraBehavior,
35
- animation,
36
- model: model != false && avatar != null && useViverseAvatar
37
- ? {
38
- type: avatar.vrmUrl != null ? 'vrm' : undefined,
39
- url: avatar?.vrmUrl,
40
- ...(model === true ? undefined : model),
41
- }
42
- : model,
43
- };
44
- const preloadSimpleCharacterAssetsKeys = [
45
- JSON.stringify(newOptions.model),
46
- ...simpleCharacterAnimationNames.map((name) => JSON.stringify(newOptions.animation?.[name])),
47
- ];
48
- suspend(async () => {
49
- const result = await preloadSimpleCharacterAssets(newOptions);
50
- result.model?.scene.addEventListener('dispose', () => clear([PreloadSimpleCharacterAssetsSymbol, ...preloadSimpleCharacterAssetsKeys]));
51
- return result;
52
- }, [PreloadSimpleCharacterAssetsSymbol, ...preloadSimpleCharacterAssetsKeys]);
53
- // eslint-disable-next-line react-hooks/exhaustive-deps
54
- const currentOptions = useMemo(() => ({}), preloadSimpleCharacterAssetsKeys);
55
- Object.assign(currentOptions, newOptions);
56
- const internalRef = useRef(null);
57
- const store = useMemo(() => create(() => ({ model: undefined })), []);
58
- useEffect(() => {
59
- if (internalRef.current == null) {
60
- return;
61
- }
62
- internalRef.current.inputSystem.dispose();
63
- if (input == null || 'length' in input) {
64
- internalRef.current.inputSystem = new InputSystem(domElement, input ?? [ScreenJoystickInput, ScreenJumpButtonInput, PointerCaptureInput, LocomotionKeyboardInput], extractProxy(currentOptions, 'inputOptions'));
65
- return;
66
- }
67
- internalRef.current.inputSystem = input;
68
- },
69
- // eslint-disable-next-line react-hooks/exhaustive-deps
70
- Array.isArray(input) ? [...input, domElement] : [input, domElement]);
71
- useEffect(() => {
72
- const simpleCharacter = internalRef.current;
73
- if (simpleCharacter == null) {
74
- return;
75
- }
76
- simpleCharacter.addEventListener('loaded', () => {
77
- store.setState({ model: simpleCharacter.model });
78
- });
79
- if (simpleCharacter.model != null) {
80
- store.setState({ model: simpleCharacter.model });
81
- }
82
- // eslint-disable-next-line react-hooks/exhaustive-deps
83
- }, [world, domElement, currentOptions]);
84
- // eslint-disable-next-line react-hooks/exhaustive-deps
85
- useImperativeHandle(ref, () => internalRef.current, [world, domElement, currentOptions]);
86
- useFrame((_, delta) => internalRef.current?.update(delta));
87
- const r3fStore = useStore();
88
- const getCamera = useCallback(() => r3fStore.getState().camera, [r3fStore]);
89
- return (_jsx("simpleCharacterImpl", { ...groupProps, args: [getCamera, world, domElement, currentOptions], ref: internalRef, children: _jsx(CharacterModelStoreContext.Provider, { value: store, children: children }) }));
90
- });
91
- /**
92
- * @deprecated use <BvhPhysicsBody kinematic={false} /> instead (kinematic={false} can be skipped as its the default)
93
- */
94
- export const FixedBvhPhysicsBody = forwardRef(({ children }, ref) => {
95
- return _jsxs(BvhPhysicsBody, { children: [" ", children] });
96
- });
97
- /**
98
- * allows to add all children as static (non-moving) objects as sensors to the bvh physics world
99
- * @requires that the structure of the inner content is not changing or has a suspense boundary
100
- * do not wrap the content inside in a suspense!
101
- */
102
- export const BvhPhysicsSensor = forwardRef(({ children, isStatic = true, onIntersectedChanged }, ref) => {
103
- const world = useContext(BvhPhyiscsWorldContext);
104
- if (world == null) {
105
- throw new Error('BvhPhysicsSensor must be used within a BvhPhysicsWorld component');
106
- }
107
- const internalRef = useRef(null);
108
- const listenerRef = useRef(onIntersectedChanged);
109
- listenerRef.current = onIntersectedChanged;
110
- useEffect(() => {
111
- const body = internalRef.current;
112
- if (body == null) {
113
- return;
114
- }
115
- world.addSensor(body, isStatic, (intersected) => listenerRef.current?.(intersected));
116
- return () => world.removeSensor(body);
117
- }, [world, isStatic]);
118
- useImperativeHandle(ref, () => internalRef.current, []);
119
- return _jsx("group", { ref: internalRef, children: children });
120
- });
121
- /**
122
- * allows to add all children as static (non-moving) or kinematic (moving) objects as obstacles to the bvh physics world
123
- * @requires that the structure of the inner content is not changing or has a suspense boundary
124
- * do not wrap the content inside in a suspense!
125
- */
126
- export const BvhPhysicsBody = forwardRef(({ children, kinematic = false }, ref) => {
127
- const world = useContext(BvhPhyiscsWorldContext);
128
- if (world == null) {
129
- throw new Error('FixedPhysicsBody must be used within a BvhPhysicsWorld component');
130
- }
131
- const internalRef = useRef(null);
132
- useEffect(() => {
133
- const body = internalRef.current;
134
- if (body == null) {
135
- return;
136
- }
137
- world.addBody(body, kinematic);
138
- return () => world.removeBody(body);
139
- }, [world, kinematic]);
140
- useImperativeHandle(ref, () => internalRef.current, []);
141
- return _jsx("group", { ref: internalRef, children: children });
142
- });
143
- export function CharacterModelBone({ bone, children }) {
144
- const [state, setState] = useState(undefined);
145
- const store = useContext(CharacterModelStoreContext);
146
- useEffect(() => {
147
- if (store == null) {
148
- return;
149
- }
150
- const updateContainer = ({ model }) => {
151
- if (model == null) {
152
- return;
153
- }
154
- const boneObject = model instanceof VRM ? model.humanoid.getRawBoneNode(bone) : model.scene.getObjectByName(bone);
155
- if (boneObject == null) {
156
- return;
157
- }
158
- setState({ boneObject: boneObject, boneRotationOffset: model.boneRotationOffset });
159
- };
160
- updateContainer(store.getState());
161
- return store.subscribe(updateContainer);
162
- }, [bone, store]);
163
- if (state == null) {
164
- return null;
165
- }
166
- return (_jsx(Fragment, { children: createPortal(_jsx("group", { quaternion: state.boneRotationOffset, children: children }), state.boneObject) }, state.boneObject.id));
167
- }