@react-three/viverse 0.1.2

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/LICENSE ADDED
@@ -0,0 +1,34 @@
1
+ Copyright 2024 Bela Bohlender
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8
+
9
+ Link to License of robot.vrm from "FinBeenWhere?"
10
+ https://creativecommons.org/licenses/by/4.0/
11
+
12
+ License of @pixiv/three-vrm
13
+ Copyright (c) 2019-2025 pixiv Inc.
14
+
15
+ MIT License
16
+
17
+ Permission is hereby granted, free of charge, to any person obtaining
18
+ a copy of this software and associated documentation files (the
19
+ "Software"), to deal in the Software without restriction, including
20
+ without limitation the rights to use, copy, modify, merge, publish,
21
+ distribute, sublicense, and/or sell copies of the Software, and to
22
+ permit persons to whom the Software is furnished to do so, subject to
23
+ the following conditions:
24
+
25
+ The above copyright notice and this permission notice shall be
26
+ included in all copies or substantial portions of the Software.
27
+
28
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
29
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
30
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
31
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
32
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
33
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
34
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,70 @@
1
+ <h1 align="center">@react-three/viverse</h1>
2
+ <h3 align="center">Toolkit for building and publishing React Three Fiber Apps to Viverse.</h3>
3
+ <br/>
4
+
5
+ <p align="center">
6
+ <a href="https://npmjs.com/package/@react-three/viverse" target="_blank">
7
+ <img src="https://img.shields.io/npm/v/@react-three/viverse?style=flat&colorA=000000&colorB=000000" alt="NPM" />
8
+ </a>
9
+ <a href="https://npmjs.com/package/@react-three/viverse" target="_blank">
10
+ <img src="https://img.shields.io/npm/dt/@react-three/viverse.svg?style=flat&colorA=000000&colorB=000000" alt="NPM" />
11
+ </a>
12
+ <a href="https://twitter.com/pmndrs" target="_blank">
13
+ <img src="https://img.shields.io/twitter/follow/pmndrs?label=%40pmndrs&style=flat&colorA=000000&colorB=000000&logo=twitter&logoColor=000000" alt="Twitter" />
14
+ </a>
15
+ <a href="https://discord.gg/ZZjjNvJ" target="_blank">
16
+ <img src="https://img.shields.io/discord/740090768164651008?style=flat&colorA=000000&colorB=000000&label=discord&logo=discord&logoColor=000000" alt="Discord" />
17
+ </a>
18
+ </p>
19
+
20
+ ```bash
21
+ npm install three @react-three/fiber @react-three/viverse
22
+ ```
23
+
24
+ ### What does it look like?
25
+
26
+ | A prototype map with the `<SimpleCharacter/>` component and its default model. | ![render of the code below](./docs/getting-started/basic-example.gif) |
27
+ | --------------------------------------------------------------------------- | --------------------------------------------------------------------- |
28
+
29
+ ```jsx
30
+ import { createRoot } from 'react-dom/client'
31
+ import { Sky } from '@react-three/drei'
32
+ import { Canvas } from '@react-three/fiber'
33
+ import { Viverse, SimpleCharacter, FixedBvhPhysicsBody, PrototypeBox } from '@react-three/viverse'
34
+
35
+ createRoot(document.getElementById('root')!).render(
36
+ <Canvas shadows>
37
+ <Viverse>
38
+ <Sky />
39
+ <directionalLight intensity={1.2} position={[5, 10, 10]} castShadow />
40
+ <ambientLight intensity={1} />
41
+ <SimpleCharacter />
42
+ <FixedBvhPhysicsBody>
43
+ <PrototypeBox scale={[10, 1, 15]} position={[0, -0.5, 0]} />
44
+ </FixedBvhPhysicsBody>
45
+ </Viverse>
46
+ </Canvas>,
47
+ )
48
+ ```
49
+
50
+ ## How to get started
51
+
52
+ > Some familiarity with
53
+ > react, threejs, and @react-three/fiber, is recommended.
54
+
55
+ Get started with **[building a simple game](https://pmndrs.github.io/viverse/tutorials/simple-game)**, take a look at our **[examples](https://pmndrs.github.io/viverse/getting-started/examples)**, or follow one of our **tutorials**:
56
+
57
+ - [First person controls](https://pmndrs.github.io/viverse/tutorials/first-person)
58
+ - [Augmented and virtual reality](https://pmndrs.github.io/viverse/tutorials/augmented-and-virtual-reality)
59
+ - [Accessing avatar and profile](https://pmndrs.github.io/viverse/tutorials/access-avatar-and-profile)
60
+ - [Equipping the character with items](https://pmndrs.github.io/viverse/tutorials/equipping-items)
61
+ - [Using custom animations and models](https://pmndrs.github.io/viverse/tutorials/custom-models-and-animations)
62
+ - [How to remove the viverse integrations](https://pmndrs.github.io/viverse/tutorials/remove-viverse-integrations)
63
+ - [Publish to Viverse](https://pmndrs.github.io/viverse/tutorials/publish-to-viverse)
64
+ - Building your own character controller - _Coming Soon_
65
+
66
+ ## Not into react?
67
+
68
+ > No Problem
69
+
70
+ Check out how to build games using @pmndrs/viverse and only [vanilla three.js](https://pmndrs.github.io/viverse/without-react).
@@ -0,0 +1,34 @@
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<SimpleCharacterOptions & {
20
+ useViverseAvatar?: boolean;
21
+ children?: ReactNode;
22
+ } & import("react").RefAttributes<Group<import("three").Object3DEventMap>>>;
23
+ /**
24
+ * allows to add all children as static (non-moving) objects to the bvh physics world
25
+ * @requires that the inner content is not dynamic
26
+ * do not wrap the content inside in a suspense!
27
+ */
28
+ export declare const FixedBvhPhysicsBody: import("react").ForwardRefExoticComponent<{
29
+ children?: ReactNode;
30
+ } & import("react").RefAttributes<Object3D<import("three").Object3DEventMap>>>;
31
+ export declare function CharacterModelBone({ bone, children }: {
32
+ bone: VRMHumanBoneName;
33
+ children?: ReactNode;
34
+ }): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,127 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { VRM } from '@pixiv/three-vrm';
3
+ import { BvhPhysicsWorld as BvhPhysicsWorldImpl, SimpleCharacter as SimpleCharacterImpl, preloadSimpleCharacterAssets, simpleCharacterAnimationNames, InputSystem, LocomotionKeyboardInput, PointerCaptureInput, } from '@pmndrs/viverse';
4
+ import { useFrame, useThree, extend, createPortal } from '@react-three/fiber';
5
+ import { createContext, forwardRef, Fragment, 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, input, useViverseAvatar = true, ...options }, 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 camera = useThree((s) => s.camera);
30
+ const domElement = useThree((s) => s.gl.domElement);
31
+ const newOptions = {
32
+ ...options,
33
+ model: options.model != false && avatar != null && useViverseAvatar
34
+ ? { url: avatar?.vrmUrl, ...(options.model === true ? undefined : options.model) }
35
+ : options.model,
36
+ };
37
+ const preloadSimpleCharacterAssetsKeys = [
38
+ JSON.stringify(options.model),
39
+ ...simpleCharacterAnimationNames.map((name) => JSON.stringify(options.animation?.[name])),
40
+ ];
41
+ suspend(async () => {
42
+ const result = await preloadSimpleCharacterAssets(newOptions);
43
+ result.model?.scene.addEventListener('dispose', () => clear([PreloadSimpleCharacterAssetsSymbol, ...preloadSimpleCharacterAssetsKeys]));
44
+ return result;
45
+ }, [PreloadSimpleCharacterAssetsSymbol, ...preloadSimpleCharacterAssetsKeys]);
46
+ // eslint-disable-next-line react-hooks/exhaustive-deps
47
+ const currentOptions = useMemo(() => ({}), preloadSimpleCharacterAssetsKeys);
48
+ Object.assign(currentOptions, newOptions);
49
+ const internalRef = useRef(null);
50
+ const store = useMemo(() => create(() => ({ model: undefined })), []);
51
+ useEffect(() => {
52
+ if (internalRef.current == null) {
53
+ return;
54
+ }
55
+ if (input == null || 'length' in input) {
56
+ internalRef.current.inputSystem = new InputSystem(domElement, input ?? [LocomotionKeyboardInput, PointerCaptureInput]);
57
+ return;
58
+ }
59
+ internalRef.current.inputSystem = input;
60
+ },
61
+ // eslint-disable-next-line react-hooks/exhaustive-deps
62
+ Array.isArray(input) ? [...input, domElement] : [input, domElement]);
63
+ useEffect(() => {
64
+ const simpleCharacter = internalRef.current;
65
+ if (simpleCharacter == null) {
66
+ return;
67
+ }
68
+ simpleCharacter.addEventListener('loaded', () => {
69
+ store.setState({ model: simpleCharacter.model });
70
+ });
71
+ if (simpleCharacter.model != null) {
72
+ store.setState({ model: simpleCharacter.model });
73
+ }
74
+ // eslint-disable-next-line react-hooks/exhaustive-deps
75
+ }, [camera, world, domElement, currentOptions]);
76
+ // eslint-disable-next-line react-hooks/exhaustive-deps
77
+ useImperativeHandle(ref, () => internalRef.current, [camera, world, domElement, currentOptions]);
78
+ useFrame((_, delta) => internalRef.current?.update(delta));
79
+ return (_jsx("simpleCharacterImpl", { args: [camera, world, domElement, currentOptions], ref: internalRef, children: _jsx(CharacterModelStoreContext.Provider, { value: store, children: children }) }));
80
+ });
81
+ /**
82
+ * allows to add all children as static (non-moving) objects to the bvh physics world
83
+ * @requires that the inner content is not dynamic
84
+ * do not wrap the content inside in a suspense!
85
+ */
86
+ export const FixedBvhPhysicsBody = forwardRef(({ children }, ref) => {
87
+ const world = useContext(BvhPhyiscsWorldContext);
88
+ if (world == null) {
89
+ throw new Error('FixedPhysicsBody must be used within a BvhPhysicsWorld component');
90
+ }
91
+ const internalRef = useRef(null);
92
+ useEffect(() => {
93
+ const body = internalRef.current;
94
+ if (body == null) {
95
+ return;
96
+ }
97
+ world.addFixedBody(body);
98
+ return () => world.removeFixedBody(body);
99
+ }, [world]);
100
+ useImperativeHandle(ref, () => internalRef.current, []);
101
+ return _jsx("group", { ref: internalRef, children: children });
102
+ });
103
+ export function CharacterModelBone({ bone, children }) {
104
+ const [state, setState] = useState(undefined);
105
+ const store = useContext(CharacterModelStoreContext);
106
+ useEffect(() => {
107
+ if (store == null) {
108
+ return;
109
+ }
110
+ const updateContainer = ({ model }) => {
111
+ if (model == null) {
112
+ return;
113
+ }
114
+ const boneObject = model instanceof VRM ? model.humanoid.getRawBoneNode(bone) : model.scene.getObjectByName(bone);
115
+ if (boneObject == null) {
116
+ return;
117
+ }
118
+ setState({ boneObject: boneObject, boneRotationOffset: model.boneRotationOffset });
119
+ };
120
+ updateContainer(store.getState());
121
+ return store.subscribe(updateContainer);
122
+ }, [bone, store]);
123
+ if (state == null) {
124
+ return null;
125
+ }
126
+ return (_jsx(Fragment, { children: createPortal(_jsx("group", { quaternion: state.boneRotationOffset, children: children }), state.boneObject) }, state.boneObject.id));
127
+ }
@@ -0,0 +1,2 @@
1
+ import { Input } from '@pmndrs/viverse';
2
+ export declare function useXRControllerInput(): Input;
@@ -0,0 +1,38 @@
1
+ import { MoveForwardField, LastTimeJumpPressedField, MoveLeftField, MoveRightField, MoveBackwardField, RunField, } from '@pmndrs/viverse';
2
+ import { useXRControllerButtonEvent, useXRInputSourceState } from '@react-three/xr';
3
+ import { useMemo, useRef } from 'react';
4
+ export function useXRControllerInput() {
5
+ const leftController = useXRInputSourceState('controller', 'left');
6
+ const lastAPressed = useRef(null);
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]);
38
+ }
@@ -0,0 +1,75 @@
1
+ import { ClientOptions, Client, checkAuthOptions } from '@viverse/sdk';
2
+ import AvatarClient from '@viverse/sdk/avatar-client';
3
+ import { ReactNode } from 'react';
4
+ declare global {
5
+ interface ImportMeta {
6
+ readonly env: {
7
+ readonly VITE_VIVERSE_APP_ID?: string;
8
+ };
9
+ }
10
+ }
11
+ /**
12
+ * provides the BvhPhysicsWorld context and the viverse context necassary for accessing any viverse content
13
+ * @param props.loginRequired forces the user to login before playing
14
+ */
15
+ export declare function Viverse({ children, loginRequired, checkAuth, ...options }: Partial<ClientOptions> & {
16
+ children?: ReactNode;
17
+ loginRequired?: boolean;
18
+ checkAuth?: checkAuthOptions;
19
+ }): import("react/jsx-runtime").JSX.Element;
20
+ /**
21
+ * Hook to access the Viverse client instance for making API calls.
22
+ */
23
+ export declare function useViverseClient(): Client;
24
+ /**
25
+ * Hook to access the current authentication state.
26
+ */
27
+ export declare function useViverseAuth(): {
28
+ access_token?: string;
29
+ account_id?: string;
30
+ expires_in?: number;
31
+ state?: string;
32
+ } | undefined;
33
+ /**
34
+ * Hook to access the Viverse avatar client for avatar-related operations.
35
+ */
36
+ export declare function useViverseAvatarClient(): AvatarClient | undefined;
37
+ /**
38
+ * Hook to fetch and access the user's Viverse profile information.
39
+ * Uses React Suspense for data fetching.
40
+ */
41
+ export declare function useViverseProfile(): Awaited<ReturnType<AvatarClient['getProfile']>> | undefined;
42
+ /**
43
+ * Hook that returns a function to initiate Viverse login flow.
44
+ */
45
+ export declare function useViverseLogin(): (options?: import("@viverse/sdk").iframeLoginOptions | undefined) => void;
46
+ /**
47
+ * Hook that returns a function to initiate Viverse logout flow.
48
+ */
49
+ export declare function useViverseLogout(): () => void;
50
+ /**
51
+ * Hook to fetch the user's personal avatar collection.
52
+ * Uses React Suspense for data fetching.
53
+ */
54
+ export declare function useViverseAvatarList(): Awaited<ReturnType<AvatarClient['getAvatarList']>> | undefined;
55
+ /**
56
+ * Hook to fetch the user's currently active/selected avatar.
57
+ * Uses React Suspense for data fetching.
58
+ */
59
+ export declare function useViverseActiveAvatar(): Awaited<ReturnType<AvatarClient['getActiveAvatar']>> | undefined;
60
+ /**
61
+ * Hook to fetch the list of publicly available avatars in Viverse.
62
+ * Uses React Suspense for data fetching.
63
+ */
64
+ export declare function useViversePublicAvatarList(): Awaited<ReturnType<AvatarClient['getPublicAvatarList']>> | undefined;
65
+ /**
66
+ * Hook to fetch a specific public avatar by its ID.
67
+ * Uses React Suspense for data fetching.
68
+ */
69
+ export declare function useViversePublicAvatarByID(id: string): Awaited<ReturnType<AvatarClient['getPublicAvatarByID']>> | undefined;
70
+ export * from './character.js';
71
+ export * from './material.js';
72
+ export { FirstPersonCharacterCameraBehavior, LocomotionKeyboardInput, PointerCaptureInput, PointerLockInput, VRMHumanBoneName, } from '@pmndrs/viverse';
73
+ export * from '@viverse/sdk';
74
+ export * from '@viverse/sdk/avatar-client';
75
+ export * from './gamepad.js';
package/dist/index.js ADDED
@@ -0,0 +1,156 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Client } from '@viverse/sdk';
3
+ import AvatarClient from '@viverse/sdk/avatar-client';
4
+ import { createContext, useCallback, useContext } from 'react';
5
+ import { suspend, clear } from 'suspend-react';
6
+ import { BvhPhysicsWorld } from './character.js';
7
+ // auth
8
+ const viverseCheckAuthSymbol = Symbol('viverse-check-auth');
9
+ const authSuspenseKeys = [];
10
+ function clearViverseAuthCheck() {
11
+ for (const keys of authSuspenseKeys) {
12
+ clear(keys);
13
+ }
14
+ authSuspenseKeys.length = 0;
15
+ }
16
+ function useViverseAuthCheck(client, options) {
17
+ const keys = [viverseCheckAuthSymbol, client, options?.allowedOrigin];
18
+ return suspend(async () => {
19
+ authSuspenseKeys.push(keys);
20
+ return client?.checkAuth({ allowedOrigin: options?.allowedOrigin });
21
+ }, keys);
22
+ }
23
+ // main viverse component
24
+ const ViverseClientContext = createContext(undefined);
25
+ const ViverseAuthContext = createContext(undefined);
26
+ const ViverseAvatarClientContext = createContext(undefined);
27
+ const viverseClientSymbol = Symbol('viverse-client');
28
+ const viverseAvatarClientSymbol = Symbol('viverse-avatar-client');
29
+ /**
30
+ * provides the BvhPhysicsWorld context and the viverse context necassary for accessing any viverse content
31
+ * @param props.loginRequired forces the user to login before playing
32
+ */
33
+ export function Viverse({ children, loginRequired = false, checkAuth, ...options }) {
34
+ const clientId = import.meta.env.VITE_VIVERSE_APP_ID ?? options.clientId;
35
+ const client = suspend(async () => clientId == null
36
+ ? undefined
37
+ : new Client({
38
+ domain: options.domain ?? 'account.htcvive.com',
39
+ authorizationParams: options.authorizationParams,
40
+ cookieDomain: options.cookieDomain,
41
+ httpTimeoutInMS: options.httpTimeoutInMS,
42
+ clientId,
43
+ }), [
44
+ viverseClientSymbol,
45
+ clientId,
46
+ options.authorizationParams,
47
+ options.cookieDomain,
48
+ options.domain,
49
+ options.httpTimeoutInMS,
50
+ ]);
51
+ const auth = useViverseAuthCheck(client);
52
+ if (clientId != null && auth == null && loginRequired) {
53
+ clearViverseAuthCheck();
54
+ client?.loginWithWorlds().catch(console.error);
55
+ }
56
+ const avatarClient = suspend(async () => auth?.access_token != null
57
+ ? new AvatarClient({ token: auth.access_token, baseURL: 'https://sdk-api.viverse.com/' })
58
+ : undefined, [viverseAvatarClientSymbol, auth?.access_token]);
59
+ if (client == null) {
60
+ return _jsx(BvhPhysicsWorld, { children: children });
61
+ }
62
+ return (_jsx(ViverseClientContext.Provider, { value: client, children: _jsx(ViverseAuthContext.Provider, { value: auth, children: _jsx(ViverseAvatarClientContext.Provider, { value: avatarClient, children: _jsx(BvhPhysicsWorld, { children: children }) }) }) }));
63
+ }
64
+ /**
65
+ * Hook to access the Viverse client instance for making API calls.
66
+ */
67
+ export function useViverseClient() {
68
+ const client = useContext(ViverseClientContext);
69
+ if (client == null) {
70
+ throw new Error('Viverse client is not available in context. Did you forget to wrap your component tree with <Viverse>?');
71
+ }
72
+ return client;
73
+ }
74
+ /**
75
+ * Hook to access the current authentication state.
76
+ */
77
+ export function useViverseAuth() {
78
+ return useContext(ViverseAuthContext);
79
+ }
80
+ /**
81
+ * Hook to access the Viverse avatar client for avatar-related operations.
82
+ */
83
+ export function useViverseAvatarClient() {
84
+ return useContext(ViverseAvatarClientContext);
85
+ }
86
+ const viverseProfileSymbol = Symbol('viverse-profile');
87
+ /**
88
+ * Hook to fetch and access the user's Viverse profile information.
89
+ * Uses React Suspense for data fetching.
90
+ */
91
+ export function useViverseProfile() {
92
+ const avatarClient = useViverseAvatarClient();
93
+ return suspend(async () => avatarClient?.getProfile(), [viverseProfileSymbol, avatarClient]);
94
+ }
95
+ /**
96
+ * Hook that returns a function to initiate Viverse login flow.
97
+ */
98
+ export function useViverseLogin() {
99
+ const client = useViverseClient();
100
+ return useCallback((...params) => {
101
+ clearViverseAuthCheck();
102
+ client.loginWithWorlds(...params);
103
+ }, [client]);
104
+ }
105
+ /**
106
+ * Hook that returns a function to initiate Viverse logout flow.
107
+ */
108
+ export function useViverseLogout() {
109
+ const client = useViverseClient();
110
+ return useCallback((...params) => {
111
+ clearViverseAuthCheck();
112
+ client.logoutWithWorlds(...params);
113
+ }, [client]);
114
+ }
115
+ const viverseAvatarListSymbol = Symbol('viverse-avatar-list');
116
+ /**
117
+ * Hook to fetch the user's personal avatar collection.
118
+ * Uses React Suspense for data fetching.
119
+ */
120
+ export function useViverseAvatarList() {
121
+ const avatarClient = useViverseAvatarClient();
122
+ return suspend(async () => avatarClient?.getAvatarList(), [viverseAvatarListSymbol, avatarClient]);
123
+ }
124
+ const viverseActiveAvatarSymbol = Symbol('viverse-active-avatar');
125
+ /**
126
+ * Hook to fetch the user's currently active/selected avatar.
127
+ * Uses React Suspense for data fetching.
128
+ */
129
+ export function useViverseActiveAvatar() {
130
+ const avatarClient = useViverseAvatarClient();
131
+ return suspend(async () => avatarClient?.getActiveAvatar(), [viverseActiveAvatarSymbol, avatarClient]);
132
+ }
133
+ const viversePublicAvatarListSymbol = Symbol('viverse-public-avatar-list');
134
+ /**
135
+ * Hook to fetch the list of publicly available avatars in Viverse.
136
+ * Uses React Suspense for data fetching.
137
+ */
138
+ export function useViversePublicAvatarList() {
139
+ const avatarClient = useViverseAvatarClient();
140
+ return suspend(async () => avatarClient?.getPublicAvatarList(), [viversePublicAvatarListSymbol, avatarClient]);
141
+ }
142
+ const viversePublicAvatarByIDSymbol = Symbol('viverse-public-avatar-by-id');
143
+ /**
144
+ * Hook to fetch a specific public avatar by its ID.
145
+ * Uses React Suspense for data fetching.
146
+ */
147
+ export function useViversePublicAvatarByID(id) {
148
+ const avatarClient = useViverseAvatarClient();
149
+ return suspend(async () => avatarClient?.getPublicAvatarByID(id), [viversePublicAvatarByIDSymbol, avatarClient, id]);
150
+ }
151
+ export * from './character.js';
152
+ export * from './material.js';
153
+ export { FirstPersonCharacterCameraBehavior, LocomotionKeyboardInput, PointerCaptureInput, PointerLockInput, VRMHumanBoneName, } from '@pmndrs/viverse';
154
+ export * from '@viverse/sdk';
155
+ export * from '@viverse/sdk/avatar-client';
156
+ export * from './gamepad.js';
@@ -0,0 +1,14 @@
1
+ import { PrototypeMaterial } from '@pmndrs/viverse';
2
+ import { ThreeElement } from '@react-three/fiber';
3
+ import { ColorRepresentation, Group } from 'three';
4
+ declare module '@react-three/fiber' {
5
+ interface ThreeElements {
6
+ prototypeMaterial: ThreeElement<typeof PrototypeMaterial>;
7
+ }
8
+ }
9
+ /**
10
+ * component for rendering a simple placeholder prototype box using a the Prototype Material with a prototype texture from kenney.nl
11
+ */
12
+ export declare const PrototypeBox: import("react").ForwardRefExoticComponent<Omit<import("@react-three/fiber/dist/declarations/src/core/utils").Mutable<import("@react-three/fiber/dist/declarations/src/core/utils").Overwrite<Partial<import("@react-three/fiber/dist/declarations/src/core/utils").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">>> & {
13
+ color?: ColorRepresentation;
14
+ }, "ref"> & import("react").RefAttributes<Group<import("three").Object3DEventMap>>>;
@@ -0,0 +1,38 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { PrototypeMaterial } from '@pmndrs/viverse';
3
+ import { extend } from '@react-three/fiber';
4
+ import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react';
5
+ import { PlaneGeometry } from 'three';
6
+ import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
7
+ extend({ PrototypeMaterial });
8
+ const plane1 = new PlaneGeometry();
9
+ plane1.translate(0, 0, 0.5);
10
+ const plane2 = new PlaneGeometry();
11
+ plane2.rotateY(Math.PI);
12
+ plane2.translate(0, 0, -0.5);
13
+ const twoPlanes = mergeGeometries([plane1, plane2]);
14
+ /**
15
+ * component for rendering a simple placeholder prototype box using a the Prototype Material with a prototype texture from kenney.nl
16
+ */
17
+ export const PrototypeBox = forwardRef((props, ref) => {
18
+ const internalRef = useRef(null);
19
+ const mat1Ref = useRef(null);
20
+ const mat2Ref = useRef(null);
21
+ const mat3Ref = useRef(null);
22
+ useEffect(() => {
23
+ if (mat1Ref.current == null ||
24
+ mat2Ref.current == null ||
25
+ mat3Ref.current == null ||
26
+ internalRef.current == null) {
27
+ return;
28
+ }
29
+ const scaleX = Math.max(0.5, Math.round(internalRef.current.scale.x) / 2);
30
+ const scaleY = Math.max(0.5, Math.round(internalRef.current.scale.y) / 2);
31
+ const scaleZ = Math.max(0.5, Math.round(internalRef.current.scale.z) / 2);
32
+ mat1Ref.current.repeat.set(scaleX, scaleZ);
33
+ mat2Ref.current.repeat.set(scaleZ, scaleY);
34
+ mat3Ref.current.repeat.set(scaleX, scaleY);
35
+ });
36
+ useImperativeHandle(ref, () => internalRef.current, []);
37
+ return (_jsxs("group", { ...props, ref: internalRef, children: [_jsx("mesh", { "rotation-x": Math.PI / 2, geometry: twoPlanes, castShadow: true, receiveShadow: true, children: _jsx("prototypeMaterial", { ref: mat1Ref, color: props.color }) }), _jsx("mesh", { "rotation-y": Math.PI / 2, geometry: twoPlanes, castShadow: true, receiveShadow: true, children: _jsx("prototypeMaterial", { ref: mat2Ref, color: props.color }) }), _jsx("mesh", { geometry: twoPlanes, castShadow: true, receiveShadow: true, children: _jsx("prototypeMaterial", { ref: mat3Ref, color: props.color }) })] }));
38
+ });
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@react-three/viverse",
3
+ "description": "Toolkit for building and publishing React Three Fiber Apps to Viverse.",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "author": "Bela Bohlender",
7
+ "license": "SEE LICENSE IN LICENSE",
8
+ "homepage": "https://github.com/pmndrs/viverse",
9
+ "keywords": [
10
+ "r3f",
11
+ "viverse",
12
+ "character controller",
13
+ "three.js",
14
+ "react",
15
+ "typescript",
16
+ "web game"
17
+ ],
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git@github.com:pmndrs/viverse.git"
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "dependencies": {
26
+ "@viverse/sdk": "^1.2.10-alpha.0",
27
+ "suspend-react": "^0.1.3",
28
+ "@react-three/xr": "^6.6.20",
29
+ "@pixiv/three-vrm": "^3.4.2",
30
+ "zustand": "^5.0.6",
31
+ "@pmndrs/viverse": "^0.1.2"
32
+ },
33
+ "peerDependencies": {
34
+ "@react-three/fiber": "*"
35
+ },
36
+ "devDependencies": {
37
+ "@react-three/fiber": "^9.2.0",
38
+ "@types/react": "^19.1.8",
39
+ "react": "^19.1.0"
40
+ },
41
+ "version": "0.1.2",
42
+ "scripts": {
43
+ "build": "tsc",
44
+ "check:prettier": "prettier --check src",
45
+ "check:eslint": "eslint \"src/**/*.{ts,tsx}\"",
46
+ "fix:prettier": "prettier --write src",
47
+ "fix:eslint": "eslint \"src/**/*.{ts,tsx}\" --fix"
48
+ }
49
+ }