@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 +34 -0
- package/README.md +70 -0
- package/dist/character.d.ts +34 -0
- package/dist/character.js +127 -0
- package/dist/gamepad.d.ts +2 -0
- package/dist/gamepad.js +38 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.js +156 -0
- package/dist/material.d.ts +14 -0
- package/dist/material.js +38 -0
- package/package.json +49 -0
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. |  |
|
|
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
|
+
}
|
package/dist/gamepad.js
ADDED
|
@@ -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
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -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>>>;
|
package/dist/material.js
ADDED
|
@@ -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
|
+
}
|