@react-three/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.
@@ -0,0 +1,27 @@
1
+ import { CharacterAnimationOptions, StartAnimationOptions } from '@pmndrs/viverse';
2
+ import { RootState } from '@react-three/fiber';
3
+ import { ActionParams } from '@react-three/timeline';
4
+ import { ReactNode } from 'react';
5
+ import { AnimationAction, AnimationActionLoopStyles, AnimationClip } from 'three';
6
+ export type AdditiveCharacterAnimationActionProps = Omit<CharacterAnimationActionProps, 'additiveToClip'> & {
7
+ referenceClip: CharacterAnimationOptions;
8
+ };
9
+ export declare const AdditiveCharacterAnimationAction: import("react").ForwardRefExoticComponent<Omit<CharacterAnimationActionProps, "additiveToClip"> & {
10
+ referenceClip: CharacterAnimationOptions;
11
+ } & import("react").RefAttributes<AnimationAction>>;
12
+ export type CharacterAnimationActionProps = {
13
+ dependencies?: Array<unknown>;
14
+ until?: () => Promise<unknown>;
15
+ loop?: AnimationActionLoopStyles;
16
+ additiveReferenceClip?: AnimationClip;
17
+ } & Omit<ActionParams<RootState>, 'until'> & CharacterAnimationOptions & StartAnimationOptions;
18
+ export declare function CharacterAnimationLayer({ name, children }: {
19
+ name: string;
20
+ children?: ReactNode;
21
+ }): import("react/jsx-runtime").JSX.Element;
22
+ export declare const CharacterAnimationAction: import("react").ForwardRefExoticComponent<{
23
+ dependencies?: Array<unknown>;
24
+ until?: () => Promise<unknown>;
25
+ loop?: AnimationActionLoopStyles;
26
+ additiveReferenceClip?: AnimationClip;
27
+ } & Omit<ActionParams<RootState>, "until"> & CharacterAnimationOptions & StartAnimationOptions & import("react").RefAttributes<AnimationAction>>;
@@ -0,0 +1,44 @@
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 { createContext, forwardRef, useContext, useImperativeHandle, useMemo } from 'react';
5
+ import { LoopRepeat } from 'three';
6
+ import { makeClipAdditive } from 'three/src/animation/AnimationUtils.js';
7
+ import { useCharacterModel } from './model.js';
8
+ import { useCharacterAnimationLoader } from './utils.js';
9
+ export const AdditiveCharacterAnimationAction = forwardRef(({ referenceClip: referenceClipOptions, ...props }, ref) => {
10
+ const model = useCharacterModel();
11
+ const referenceClip = useCharacterAnimationLoader(model, { ...props, ...referenceClipOptions });
12
+ return _jsx(CharacterAnimationAction, { ref: ref, additiveReferenceClip: referenceClip, ...props });
13
+ });
14
+ const CharacterAnimationLayerContext = createContext(undefined);
15
+ export function CharacterAnimationLayer({ name, children }) {
16
+ return _jsx(CharacterAnimationLayerContext.Provider, { value: name, children: children });
17
+ }
18
+ export const CharacterAnimationAction = forwardRef(({ additiveReferenceClip, until, init, dependencies, update, fadeDuration, paused, loop, sync, crossFade, layer, ...animationOptions }, ref) => {
19
+ const layerFromContext = useContext(CharacterAnimationLayerContext);
20
+ layer ??= layerFromContext;
21
+ const model = useCharacterModel();
22
+ const srcClip = useCharacterAnimationLoader(model, animationOptions);
23
+ const clip = useMemo(() => additiveReferenceClip == null ? srcClip : makeClipAdditive(srcClip.clone(), undefined, additiveReferenceClip), [srcClip, additiveReferenceClip]);
24
+ const animation = useMemo(() => model.mixer.clipAction(clip), [clip, model]);
25
+ animation.clampWhenFinished = true;
26
+ animation.loop = loop ?? LoopRepeat;
27
+ useImperativeHandle(ref, () => animation, [animation]);
28
+ return (_jsx(Action, { init: () => {
29
+ const cleanupAnimation = startAnimation(animation, model.currentAnimations, {
30
+ layer,
31
+ fadeDuration,
32
+ paused,
33
+ sync,
34
+ crossFade,
35
+ });
36
+ const cleanupInit = init?.();
37
+ return () => {
38
+ cleanupInit?.();
39
+ cleanupAnimation?.();
40
+ };
41
+ }, until: until ?? (() => animationFinished(animation)), update: update, dependencies: dependencies != null
42
+ ? [...dependencies, animationOptions.mask, fadeDuration, paused, sync, crossFade, animation, model, layer]
43
+ : undefined }));
44
+ });
package/dist/bone.d.ts ADDED
@@ -0,0 +1,10 @@
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;
7
+ /**
8
+ * @deprecated use CharacterModelBone instead
9
+ */
10
+ export declare const VrmCharacterModelBone: typeof CharacterModelBone;
package/dist/bone.js ADDED
@@ -0,0 +1,18 @@
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
+ }
15
+ /**
16
+ * @deprecated use CharacterModelBone instead
17
+ */
18
+ export const VrmCharacterModelBone = CharacterModelBone;
package/dist/gamepad.d.ts CHANGED
@@ -1,2 +1 @@
1
- import { Input } from '@pmndrs/viverse';
2
- export declare function useXRControllerInput(): Input;
1
+ export declare function useXRControllerInput(): void;
package/dist/gamepad.js CHANGED
@@ -1,38 +1,27 @@
1
- import { MoveForwardField, LastTimeJumpPressedField, MoveLeftField, MoveRightField, MoveBackwardField, RunField, } from '@pmndrs/viverse';
1
+ import { JumpAction, MoveBackwardAction, MoveForwardAction, MoveLeftAction, MoveRightAction, } from '@pmndrs/viverse';
2
+ import { useFrame } from '@react-three/fiber';
2
3
  import { useXRControllerButtonEvent, useXRInputSourceState } from '@react-three/xr';
3
- import { useMemo, useRef } from 'react';
4
+ import { useEffect, useRef } from 'react';
4
5
  export function useXRControllerInput() {
5
6
  const leftController = useXRInputSourceState('controller', 'left');
6
- const lastAPressed = useRef(null);
7
7
  const rightController = useXRInputSourceState('controller', 'right');
8
- useXRControllerButtonEvent(rightController, 'a-button', (state) => state === 'pressed' && (lastAPressed.current = performance.now() / 1000));
9
- return useMemo(() => ({
10
- get(field) {
11
- switch (field) {
12
- case MoveForwardField:
13
- case MoveBackwardField: {
14
- const thumbstickYAxis = leftController?.gamepad?.['xr-standard-thumbstick']?.yAxis;
15
- if (thumbstickYAxis == null) {
16
- return undefined;
17
- }
18
- return field === MoveBackwardField
19
- ? Math.max(0, thumbstickYAxis)
20
- : Math.max(0, -thumbstickYAxis);
21
- }
22
- case MoveLeftField:
23
- case MoveRightField: {
24
- const thumbstickXAxis = leftController?.gamepad?.['xr-standard-thumbstick']?.xAxis;
25
- if (thumbstickXAxis == null) {
26
- return undefined;
27
- }
28
- return field === MoveLeftField ? Math.max(0, -thumbstickXAxis) : Math.max(0, thumbstickXAxis);
29
- }
30
- case LastTimeJumpPressedField:
31
- return lastAPressed.current;
32
- case RunField:
33
- return (leftController?.gamepad?.['xr-standard-trigger']?.state === 'pressed');
34
- }
35
- return undefined;
36
- },
37
- }), [leftController]);
8
+ useXRControllerButtonEvent(rightController, 'a-button', (state) => state === 'pressed' && JumpAction.emit());
9
+ const forwardWriterRef = useRef(undefined);
10
+ const backwardWriterRef = useRef(undefined);
11
+ const leftWriterRef = useRef(undefined);
12
+ const rightWriterRef = useRef(undefined);
13
+ useEffect(() => {
14
+ const abortController = new AbortController();
15
+ forwardWriterRef.current = MoveForwardAction.createWriter(abortController.signal);
16
+ backwardWriterRef.current = MoveBackwardAction.createWriter(abortController.signal);
17
+ leftWriterRef.current = MoveLeftAction.createWriter(abortController.signal);
18
+ rightWriterRef.current = MoveRightAction.createWriter(abortController.signal);
19
+ return () => abortController.abort();
20
+ }, [leftController]);
21
+ useFrame(() => {
22
+ forwardWriterRef.current?.write(-Math.min(0, leftController?.gamepad?.['xr-standard-thumbstick']?.yAxis ?? 0));
23
+ backwardWriterRef.current?.write(Math.max(0, leftController?.gamepad?.['xr-standard-thumbstick']?.yAxis ?? 0));
24
+ leftWriterRef.current?.write(-Math.min(0, leftController?.gamepad?.['xr-standard-thumbstick']?.xAxis ?? 0));
25
+ rightWriterRef.current?.write(Math.max(0, leftController?.gamepad?.['xr-standard-thumbstick']?.xAxis ?? 0));
26
+ });
38
27
  }
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,68 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { RunAction, shouldJump, updateSimpleCharacterInputVelocity, updateSimpleCharacterRotation, } from '@pmndrs/viverse';
3
+ import { IdleAnimationUrl, JumpDownAnimationUrl, JumpForwardAnimationUrl, JumpLoopAnimationUrl, JumpUpAnimationUrl, RunAnimationUrl, WalkAnimationUrl, } from '@pmndrs/viverse/src/animation/default.js';
4
+ import { useFrame } from '@react-three/fiber';
5
+ import { Graph, GrapthState, Parallel, RunTimeline, Switch, SwitchCase, timePassed } from '@react-three/timeline';
6
+ import { forwardRef, Suspense, useImperativeHandle, useRef } from 'react';
7
+ import { LoopOnce, Vector3 } from 'three';
8
+ import { CharacterAnimationAction } from './animation.js';
9
+ import { useViverseActiveAvatar } from './index.js';
10
+ import { CharacterModelProvider } from './model.js';
11
+ import { useBvhCharacterPhysics } from './physics.js';
12
+ import { useCharacterCameraBehavior, useCharacterModelLoader, useSimpleCharacterInputs } from './utils.js';
13
+ export const SimpleCharacter = forwardRef(({ input, inputOptions, cameraBehavior, children, movement, physics: physicsOptions, model, useViverseAvatar, animation, ...props }, ref) => {
14
+ useSimpleCharacterInputs(input, inputOptions);
15
+ const internalRef = useRef(null);
16
+ useCharacterCameraBehavior(internalRef, cameraBehavior);
17
+ const physics = useBvhCharacterPhysics(internalRef, physicsOptions);
18
+ const lastJumpTimeRef = useRef(-Infinity);
19
+ useFrame((state) => updateSimpleCharacterInputVelocity(state.camera, physics, movement));
20
+ useFrame(() => {
21
+ if (model != false || movement?.jump === false) {
22
+ return;
23
+ }
24
+ const bufferTime = movement?.jump === true ? undefined : movement?.jump?.bufferTime;
25
+ if (shouldJump(physics, lastJumpTimeRef.current, bufferTime)) {
26
+ lastJumpTimeRef.current = performance.now() / 1000;
27
+ physics.applyVelocity(
28
+ // eslint-disable-next-line @react-three/no-new-in-loop
29
+ new Vector3(0, (typeof movement?.jump === 'object' ? movement?.jump.speed : undefined) ?? 8, 0));
30
+ }
31
+ });
32
+ useImperativeHandle(ref, () => internalRef.current, []);
33
+ return (_jsx("group", { ...props, ref: internalRef, children: model == false ? (children) : (_jsx(SimpleCharacterModel, { animation: animation, physics: physics, model: model == true ? undefined : model, movement: movement, useViverseAvatar: useViverseAvatar, children: children })) }));
34
+ });
35
+ function SimpleCharacterModel({ children, model: modelOptions, movement, physics, useViverseAvatar = true, animation, }) {
36
+ const avatar = useViverseActiveAvatar();
37
+ const model = useCharacterModelLoader(avatar != null && useViverseAvatar
38
+ ? {
39
+ type: 'vrm',
40
+ url: avatar?.vrmUrl,
41
+ ...modelOptions,
42
+ }
43
+ : modelOptions);
44
+ const lastJumpTimeRef = useRef(-Infinity);
45
+ useFrame((state, delta) => updateSimpleCharacterRotation(delta, physics, state.camera, model, animation));
46
+ return (_jsx(_Fragment, { children: _jsxs(CharacterModelProvider, { model: model, children: [_jsx(RunTimeline, { children: _jsxs(Graph, { enterState: "move", children: [_jsx(GrapthState, { name: "move", transitionTo: {
47
+ jumpStart: {
48
+ whenUpdate: () => shouldJump(physics, lastJumpTimeRef.current),
49
+ },
50
+ jumpLoop: { whenUpdate: () => !physics.isGrounded },
51
+ }, 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: IdleAnimationUrl }) }) }), movement?.run != false && (_jsx(SwitchCase, { index: 1, condition: () => RunAction.get(), children: _jsx(Suspense, { fallback: null, children: _jsx(CharacterAnimationAction, { ...animation?.run, fadeDuration: animation?.crossFadeDuration, scaleTime: 0.8, url: RunAnimationUrl }) }) })), movement?.walk != false && (_jsx(SwitchCase, { index: 2, children: _jsx(Suspense, { fallback: null, children: _jsx(CharacterAnimationAction, { ...animation?.walk, fadeDuration: animation?.crossFadeDuration, scaleTime: 0.5, url: WalkAnimationUrl }) }) })), _jsx(SwitchCase, { index: 3, children: _jsx(Suspense, { fallback: null, children: _jsx(CharacterAnimationAction, { ...animation?.idle, fadeDuration: animation?.crossFadeDuration, url: IdleAnimationUrl }) }) })] }) }), _jsx(GrapthState, { name: "jumpStart", transitionTo: {
52
+ jumpDown: { whenUpdate: () => !physics.isGrounded },
53
+ finally: () => (RunAction.get() ? 'jumpForward' : 'jumpUp'),
54
+ }, 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: JumpUpAnimationUrl }), _jsx(CharacterAnimationAction, { ...animation?.jumpForward, fadeDuration: animation?.crossFadeDuration ?? 0.1, paused: true, url: JumpForwardAnimationUrl })] }) }) }), _jsx(GrapthState, { name: "jumpLoop", transitionTo: {
55
+ jumpDown: { whenUpdate: () => physics.isGrounded },
56
+ }, children: _jsx(Suspense, { fallback: null, children: _jsx(CharacterAnimationAction, { ...animation?.jumpLoop, fadeDuration: animation?.crossFadeDuration, url: JumpLoopAnimationUrl }) }) }), _jsx(GrapthState, { name: "jumpUp", transitionTo: {
57
+ jumpDown: {
58
+ whenUpdate: (_, _clock, actionTime) => actionTime > 0.3 && physics.isGrounded,
59
+ },
60
+ finally: 'jumpLoop',
61
+ }, children: _jsx(Suspense, { fallback: null, children: _jsx(CharacterAnimationAction, { ...animation?.jumpUp, fadeDuration: animation?.crossFadeDuration, loop: LoopOnce, init: () => {
62
+ lastJumpTimeRef.current = performance.now() / 1000;
63
+ physics.applyVelocity(new Vector3(0, (typeof movement?.jump === 'object' ? movement?.jump.speed : undefined) ?? 8, 0));
64
+ }, url: JumpUpAnimationUrl }) }) }), _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: () => {
65
+ lastJumpTimeRef.current = performance.now() / 1000;
66
+ physics.applyVelocity(new Vector3(0, 8, 0));
67
+ }, loop: LoopOnce, url: JumpForwardAnimationUrl }) }) }), _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: JumpDownAnimationUrl }) }) })] }) }), _jsx("primitive", { object: model.scene }), children] }) }));
68
+ }
@@ -0,0 +1,14 @@
1
+ import { CharacterCameraBehavior, CharacterAnimationOptions, CharacterModelOptions, SimpleCharacterCameraBehaviorOptions, CharacterModel, SimpleCharacterInputOptions } from '@pmndrs/viverse';
2
+ import { RefObject } from 'react';
3
+ import { Object3D } from 'three';
4
+ export declare function useCharacterCameraBehavior(model: Object3D | RefObject<Object3D | null>, options?: SimpleCharacterCameraBehaviorOptions): RefObject<CharacterCameraBehavior | undefined>;
5
+ export declare function useCharacterModelLoader(options?: CharacterModelOptions): CharacterModel;
6
+ export declare function useCharacterAnimationLoader(model: CharacterModel, options: CharacterAnimationOptions): import("three").AnimationClip;
7
+ /**
8
+ * @deprecated use inputs directly
9
+ */
10
+ export declare function useSimpleCharacterInputs(inputsClasses?: ReadonlyArray<{
11
+ new (domElement: HTMLElement): {
12
+ dispose(): void;
13
+ };
14
+ }>, options?: SimpleCharacterInputOptions): void;
package/dist/utils.js ADDED
@@ -0,0 +1,79 @@
1
+ import { VRM } from '@pixiv/three-vrm';
2
+ import { CharacterCameraBehavior, flattenCharacterAnimationOptions, flattenCharacterModelOptions, loadCharacterModel, loadCharacterAnimation, ScreenJoystickInput, ScreenJumpButtonInput, PointerCaptureInput, LocomotionKeyboardInput, applySimpleCharacterInputOptions, } from '@pmndrs/viverse';
3
+ import { useFrame, useThree } from '@react-three/fiber';
4
+ import { useEffect, useMemo, useRef } from 'react';
5
+ import { suspend } from 'suspend-react';
6
+ import { Object3D } from 'three';
7
+ import { useBvhPhysicsWorld } from './physics.js';
8
+ export function useCharacterCameraBehavior(model, options) {
9
+ const behaviorRef = useRef(undefined);
10
+ useEffect(() => {
11
+ const behavior = new CharacterCameraBehavior();
12
+ behaviorRef.current = behavior;
13
+ return () => {
14
+ behaviorRef.current = undefined;
15
+ behavior.dispose();
16
+ };
17
+ }, []);
18
+ const world = useBvhPhysicsWorld();
19
+ const raycast = useMemo(() => world.raycast.bind(world), [world]);
20
+ useFrame((state, delta) => {
21
+ const resolvedModel = model instanceof Object3D ? model : model.current;
22
+ if (resolvedModel == null) {
23
+ return;
24
+ }
25
+ behaviorRef.current?.update(state.camera, resolvedModel, delta, raycast, options);
26
+ });
27
+ return behaviorRef;
28
+ }
29
+ const loadCharacterModelSymbol = Symbol('loadCharacterModel');
30
+ export function useCharacterModelLoader(options) {
31
+ const model = suspend((_, ...params) => loadCharacterModel(...params), [loadCharacterModelSymbol, ...flattenCharacterModelOptions(options)]);
32
+ useFrame((_, delta) => {
33
+ if (model instanceof VRM) {
34
+ model.update(delta);
35
+ }
36
+ model.mixer.update(delta);
37
+ });
38
+ return model;
39
+ }
40
+ const loadCharacterAnimationSymbol = Symbol('loadCharacterAnimation');
41
+ export function useCharacterAnimationLoader(model, options) {
42
+ return suspend((_, ...params) => loadCharacterAnimation(...params), [loadCharacterAnimationSymbol, model, ...flattenCharacterAnimationOptions(options)]);
43
+ }
44
+ /**
45
+ * @deprecated use inputs directly
46
+ */
47
+ export function useSimpleCharacterInputs(inputsClasses = [
48
+ ScreenJoystickInput,
49
+ ScreenJumpButtonInput,
50
+ PointerCaptureInput,
51
+ LocomotionKeyboardInput,
52
+ ], options) {
53
+ const dom = useThree((s) => s.gl.domElement);
54
+ // eslint-disable-next-line react-hooks/exhaustive-deps
55
+ const inputs = useMemo(() => [], [dom]);
56
+ useEffect(() => {
57
+ const removedInputs = new Set(inputs);
58
+ for (const inputClass of inputsClasses) {
59
+ const existingInput = inputs.find((existingInput) => existingInput instanceof inputClass);
60
+ if (existingInput != null) {
61
+ removedInputs.delete(existingInput);
62
+ continue;
63
+ }
64
+ inputs.push(new inputClass(dom));
65
+ }
66
+ for (const removedInput of removedInputs) {
67
+ removedInput.dispose();
68
+ const index = inputs.indexOf(removedInput);
69
+ if (index != -1) {
70
+ inputs.splice(index, 1);
71
+ }
72
+ }
73
+ applySimpleCharacterInputOptions(inputs, options);
74
+ });
75
+ useEffect(() => () => {
76
+ inputs.forEach((input) => input.dispose());
77
+ inputs.length = 0;
78
+ }, [inputs]);
79
+ }
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.7",
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.20"
32
+ "@pmndrs/viverse": "^0.2.1"
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.20",
43
+ "version": "0.2.1",
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
- }