spacesvr
A standardized reality for the future of the 3D Web.
www.muse.place · demo · discord
About
The mission of spacesvr is to organize and implement the standards for experiencing 3D content on the web in the same way that there exists standards for experiencing 2D content with HTML/CSS/JS.
spacesvr is designed to empower the artist. Instead of worrying about file structures or basic functionality like cross-device compatability, artists should spend their time telling their story. As such, consumption is optimized for simplicity, and the organization provides a framework to tell stories.
spacesvr is actively maintained by Muse, a YC-backed startup that provides tooling for visually building worlds. Muse's mission is to accelerate the adoption of 3D websites by increasing their accessibility, both for the end user and for the creator. Muse is completely built on spacesvr.
Quick Start
Develop
You could set up the framework in your sleep. Just import the package
yarn add spacesvr
and copy/paste 9 lines of code
import { StandardReality, LostWorld } from "spacesvr";
function World() {
return (
<StandardReality>
<LostWorld /> // an example world with a floor, skybox, and fog
</StandardReality>
);
}
this is the starting point for this demo
From this point, your creations will be built by directly using the following technologies:
Under the hood and mostly abstracted away are the following technologies:
Folder Structure
the following are each a fundamental unit and their own folder at the top level of spacesvr, with the pattern recursively re-appearing throughout the repository
ideas are the fundamental building blocks of your world. They are the 3D the equivalent of HTML Elements. They are implemented as React components.
layers (of reality) offer new functionality to the world. They are implemented using a React container component, a context, and a corresponding hook.
logic offers functions to be used when composing your world. They are implemented as individually exported functions and hooks.
realities define how the player experiences your world. They are comparable in function to a browser. They are implemented as a React container component and composed of an ordering of layers.
tools offer the player affordances in your world. They are the 3D equivalent of a browser toolbar. They are implemented using a Layer for fundamental state a modifier for registry.
worlds are sets of ideas. They are the actual content of your site. They are implemented as compositions of ideas.
Realities
Standard Reality
The Standard Reality defines the standard experiencing the 3D web. The layers provided are, in order: Environment, Physics, Player, Toolbelt, Network, Visual. Additionally, it provides an infinite ground to walk on that can be disabled.
<StandardReality
environmentProps={{...}}
physicsProps={{...}}
playerProps={{...}}
networkProps={{...}}
disableGround={true}
/>
Layers
Environment Layer
The base layer that abstracts away the DOM, allowing you to think only in 3D
type EnvironmentProps = {
name?: string;
pauseMenu?: ReactNode;
loadingScreen?: ReactNode;
dev?: boolean;
canvasProps?: Partial<ContainerProps>;
};
const environmentState = useEnvironment();
type EnvironmentState = {
name: string;
paused: boolean;
setPaused: (p: boolean) => void;
device: DeviceState;
containerRef: MutableRefObject<HTMLDivElement | null>;
};
Physics Layer
Provides a default physics configuration
import { PhysicsProps as ProviderProps } from "@react-three/cannon";
type PhysicsProps = ProviderProps;
Player Layer
Provides a user-controlled entity with a standardized set of controls that work cross-platform (VR/Mobile/Desktop)
type PlayerProps = {
pos?: number[];
rot?: number;
speed?: number;
controls?: {
disableGyro?: boolean;
};
};
const playerState = usePlayer();
type PlayerState = {
position: PlayerVec;
velocity: PlayerVec;
controls: PlayerControls;
raycaster: Raycaster;
};
Toolbelt Layer
Provides a layer of UX to offer user interaction with the world.
type ToolbeltProps = {
showOnSpawn?: boolean;
};
const toolbelt = useToolbelt();
type ToolbeltState = {
tools: Tool[];
activeTool?: Tool;
hide: () => void;
next: () => void;
prev: () => void;
show: () => void;
activeIndex: number | undefined;
setActiveIndex: (i: number) => void;
direction: Direction;
};
Network Layer
Provides multiplayer out-of-the-box. Muse provides signalling servers and STUN/TURN servers for everyone :).
type NetworkProps = {
autoconnect?: boolean;
disableEntities?: boolean;
iceServers?: RTCIceServer[];
host?: string;
sessionId?: string;
worldName?: string;
voice?: boolean;
};
const networkState = useNetwork();
type NetworkState = {
connected: boolean;
connect: (config?: ConnectionConfig) => Promise<void>;
connections: Map<string, DataConnection>;
disconnect: () => void;
voice: boolean;
setVoice: (v: boolean) => void;
mediaConnections: Map<string, MediaConnection>;
useChannel: <Data = any, State = any>(
id: string,
type: ChannelType,
reducer: Reducer<Data, State>
) => Channel<Data, State>;
};
Tools
Camera Tool
A tool that gives the user a camera to take pictures with. To add to your toolbelt simply add it into the World.
<StandardReality>
<Camera />
</StandardReality>
Walkie Talkie Tool
A tool to configure your microphone settings. Automatically added if voice chat is enabled in the network layer.
Ideas
types of ideas
- basis/ for visualizations of fundamental metaphysics
- environment/ for setting up the environment
- media/ for importing common media types
- mediated/ for some basic art assets
- modifiers/ for modifying other ideas. they don't render anything themselves
- ui/ for guiding and interacting with the user
basis/
VisualIdea
Visualize an Idea
<VisualIdea idea={new Idea()} />
VisualSite
Visualize a Site
<VisualSite idea={new Site()} />
VisualWorld
Visualize a World
<VisualWorld idea={new World()} />
environment/
Background
Set the background color of your space
<Background color="blue" />
Fog
Add fog to your scene.
<Fog color="white" near={10} far={100} />
InfinitePlane
Adds an infinite plane to walk on (added by default with the Environment Layer)
<InfinitePlane
height={-0.0001}
size={[100, 100]}
visible={false}
/>
media/
Audio
A positional audio component that will play the passed in audio url. Handles media playback rules for Safari, iOS, etc.
<Audio
url="https://link-to-your-audio.mp3"
position={[0, 4, 0]}
volume={1}
rollOff={1}
dCone={new Vector3(coneInnerAngle, coneOuterAngle, coneOuterGain)}
fftSize={128}
/>
HDRI
Set the scene background to an hdr file. You can find free hdr files here: https://hdrihaven.com/
<HDRI
src="https://link-to-your-hdri.hdr"
disableBackground={false}
disableEnvironment={false}
/>
Image
Quickly add an image to your scene
<Image
src="https://link-to-your-image.png"
size={1}
framed
/>
Model
Quickly add a GLTF/GLB model to your scene. Will handle Suspense, KTX2, Draco, Meshopt. Clones the gltf scene so the same file can be re-used.
<Model
src="https://link-to-your-model.glb"
center
normalize
/>
Video
Add a video file to your space with positional audio. Handles media playback rules for Safari, iOS, etc.
<Video
src="https://link-to-your-video.mp4"
size={1}
volume={1}
muted
framed
/>
mediated/
Frame
Builds a frame to showcase media, especially images.
width: number;
height: number;
thickness?: number;
material?: Material;
innerFrameMaterial?: Material;
<Frame
width={1}
height={1}
thickness={0.1}
material={new MeshBasicMaterial({ color: "red" })}
innerFrameMaterial={new MeshBasicMaterial({ color: "blue" })}
/>
LostFloor
An infinite floor styled to the Lost World.
<LostFloor />
modifiers/
Collidable
Enables colliders for its children either by a named collider mesh or using all meshes and capping collective triangle count to triLimit prop.
<Collidable
triLimit={1000}
enabled={true}
hideCollisionMeshes={false}
/>
Interactable
Makes its children react to onclick and on hover methods
<Interactable
onClick={() => console.log("Ive been clicked!")}
onHovered={() => console.log("Ive been hovered!")}
onUnHovered={() => console.log("Ive been unhovered?")}
>
<Stuff />
</Interactable>
Tool
Turns its children into a tool, automatically registers it with the Tool Layer.
<Tool
name="My Tool"
pos={[0, 0]}
face={true}
pinY={false}
range={0}
orderIndex={0}
onSwitch={(enabled: boolean) => {}}
>
<Stuff />
</Tool>
Anchor
Makes its children link out to when clicked. handles leaving vr session.
<Anchor
href="https://link-to-your-website.com"
target="_blank"
>
<Stuff />
</Anchor>
FacePlayer
Turns its children into a billboard, always facing the camera.
<FacePlayer enabled={true} lockX={false} lockY={false} lockZ={false} />
Floating
Lazily floats its children.
<Floating height={0.2} speed={1} />
LookAtPlayer
Makes its children face the player, but with easing.
<LookAtPlayer enabled={true} />
Spinning
Makes its children spin
<Spinning xSpeed={0} ySpeed={1} zSpeed={0} />
Visual Effect
Adds a render pass to the Visual Layer's render pipeline. Use to add postprocessing.
<VisualEffect
index={1}
>
<unrealBloomPass args={[new Vector2(256, 256), 0.1, 0.01, 0.95]} />
</VisualEffect>
ui/
TextInput
A text input component made to mimic an HTML input element. Supports all shortcuts, drag to select, shift click, double/triple click.
const [text, setText] = useState("");
<TextInput
type="text" // text | password | number, default is text
value={text} // control the input value
onChange={setText} // optional onChange function
onSubmit={(s: string) => console.log(s)} // optional onSubmit function, called when enter is pressed
onFocus={() => console.log("focused")} // optional onFocus function
onBlur={() => console.log("blurred")} // optional onBlur function
font={"https://link-to-your-font.ttf"} // optional font
fontSize={0.1} // font size, default 0.1
width={1} // width, default 1
placeholder="Enter your name" // optional placeholder text
/>;
Arrow
An arrow icon
<Arrow dark={false} />
Button
A simple button
<Button
onClick={() => console.log("Ive been clicked!")}
font="https://link-to-your-font.ttf"
fontSize={0.1}
maxWidth={1}
textColor="red"
color="green"
outline={false}
outlineColor="#9f9f9f"
>
Click me!
</Button>
Key
A Keyboard Key that responds to the corresponding key press. Useful for tutorials.
<Key
keyCode="a"
keyPress={["a, A"]}
onPressed={(evt) => console.log("Ive been pressed!")}
/>
Switch
A boolean switch
const [value, setValue] = useState(false);
<Switch
value={value} // control the switch value
onChange={setValue} // optional onChange function
/>;