Skip to content

v2.3.2 #114

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
spacesvr
</h3>
<h5 align="center">
A standardized reality for future of the 3D Web.
A standardized reality for the future of the 3D Web.
</h5>

<div align="center">
Expand Down Expand Up @@ -37,7 +37,7 @@

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 mediate along.
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](https://www.muse.place?utm_source=npmjs&utm_campaign=learn_more), 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.

Expand Down Expand Up @@ -212,6 +212,9 @@ type NetworkState = {
connect: (config?: ConnectionConfig) => Promise<void>; // when autoconnect is off, use this to manually connect
connections: Map<string, DataConnection>; // reference to active peer connections
disconnect: () => void;
voice: boolean; // whether voice is enabled
setVoice: (v: boolean) => void; // enable/disable voice
mediaConnections: Map<string, MediaConnection>; // reference to active media connections
useChannel: <Data = any, State = any>(
id: string,
type: ChannelType,
Expand Down Expand Up @@ -382,6 +385,17 @@ Adds an infinite plane to walk on (added by default with the Environment Layer)
/>
```

#### Model

Quickly add a GLTF/GLB model to your scene. Will handle Suspense, KTX2, Draco, Meshopt.

```tsx
<Model
src="https://link-to-your-model.glb"
center={false} // whether to center the model so its bounds are centered on its origin
/>
```

#### Video

Add a video file to your space with positional audio. Handles media playback rules for Safari, iOS, etc.
Expand Down
15 changes: 15 additions & 0 deletions examples/worlds/Multiplayer/ideas/PingPongMulti.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useNetwork } from "spacesvr";
import { useEffect } from "react";

export default function PingPongMulti() {
const { voice, setVoice } = useNetwork();

useEffect(() => {
setTimeout(() => {
console.log("setting to ", !voice);
setVoice(!voice);
}, 5000);
}, [setVoice, voice]);

return null;
}
2 changes: 2 additions & 0 deletions examples/worlds/Multiplayer/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { StandardReality, Background, Model } from "spacesvr";
import LightSwitch from "./ideas/LightSwitch";
import PingPongMulti from "./ideas/PingPongMulti";

export default function Multiplayer() {
return (
<StandardReality
playerProps={{ pos: [5, 1, 0], rot: Math.PI }}
networkProps={{ autoconnect: true, voice: true }}
>
{/*<PingPongMulti />*/}
<Background color={0xffffff} />
<fog attach="fog" args={[0xffffff, 10, 90]} />
<ambientLight />
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "spacesvr",
"version": "2.3.1",
"version": "2.3.2",
"private": true,
"description": "A standardized reality for future of the 3D Web",
"keywords": [
Expand Down
2 changes: 1 addition & 1 deletion src/layers/Environment/ui/PauseMenu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export default function PauseMenu(props: PauseMenuProps) {
const PAUSE_ITEMS: PauseItem[] = [
...pauseMenuItems,
{
text: "v2.3.1",
text: "v2.3.2",
link: "https://www.npmjs.com/package/spacesvr",
},
...menuItems,
Expand Down
42 changes: 24 additions & 18 deletions src/layers/Network/ideas/NetworkedEntities/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,11 @@ export default function NetworkedEntities() {
},
}));

const snapshot: Snapshot = {
SI.vault.add({
id: Math.random().toString(),
time: new Date().getTime(),
state,
};

SI.vault.add(snapshot);
});
});

// send own player data
Expand All @@ -77,23 +75,27 @@ export default function NetworkedEntities() {
let i = 0;
for (const entityState of snapshot.state) {
const { x, y, z, q } = entityState;
obj.position.x = x as number;
obj.position.y = y as number;
obj.position.z = z as number;
obj.position.set(x as number, y as number, z as number);
obj.position.y -= 0.2; // they were floating before, idk where the constant comes from really
const quat = q as Quat;
obj.quaternion.x = quat.x;
obj.quaternion.y = quat.y;
obj.quaternion.z = quat.z;
obj.quaternion.w = quat.w;
obj.quaternion.set(
quat.x as number,
quat.y as number,
quat.z as number,
quat.w as number
);
obj.updateMatrix();
mesh.current.setMatrixAt(i, obj.matrix);

const audio = entities[i]?.posAudio;
if (audio) {
obj.matrix.decompose(audio.position, audio.quaternion, audio.scale);
audio.updateMatrix();
audio.rotateY(Math.PI); // for some reason it's flipped
const posAudio = entities[i]?.posAudio;
if (posAudio) {
obj.matrix.decompose(
posAudio.position,
posAudio.quaternion,
posAudio.scale
);
posAudio.rotation.y += Math.PI; // for some reason it's flipped
posAudio.updateMatrix();
}

i++;
Expand All @@ -107,11 +109,15 @@ export default function NetworkedEntities() {
}

return (
<group>
<group name="spacesvr-entities">
{entities.map(
(entity) =>
entity.posAudio && (
<primitive key={entity.posAudio.uuid} object={entity.posAudio} />
<primitive
key={entity.posAudio.uuid}
object={entity.posAudio}
matrixAutoUpdate={false}
/>
)
)}
<instancedMesh
Expand Down
82 changes: 38 additions & 44 deletions src/layers/Network/ideas/NetworkedEntities/logic/entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type Entity = {
};

export const useEntities = (): Entity[] => {
const { connections, connected, voiceStreams } = useNetwork();
const { connections, connected, mediaConnections } = useNetwork();
const { paused } = useEnvironment();

const listener = useListener();
Expand All @@ -23,80 +23,74 @@ export const useEntities = (): Entity[] => {

const entities = useMemo<Entity[]>(() => [], []);

const sameIds = (ids1: string[], ids2: string[]) =>
ids1.sort().join(",") === ids2.sort().join(",");
const needsAudio = (e: Entity) => mediaConnections.has(e.id) && !e.posAudio;

// check for a change in player list, re-render if there is a change
const connectionIds = useRef<string[]>([]);
const voiceIds = useRef<string[]>([]);
useLimitedFrame(6, () => {
useLimitedFrame(5, () => {
if (!connected) return;

// check for changes in connections
if (!sameIds(connectionIds.current, Array.from(connections.keys()))) {
connectionIds.current = Array.from(connections.keys());
// changed flag to trigger re-render at the end
let changed = false;

// remove entities that are no longer connected
entities.map((e) => {
if (!connectionIds.current.includes(e.id)) {
entities.splice(entities.indexOf(e), 1);
}
});

// add in new entities
for (const id of connectionIds.current) {
if (!entities.some((e) => e.id === id)) {
entities.push({ id, posAudio: undefined });
// remove old entities
entities.map((e) => {
if (!connections.has(e.id)) {
if (e.posAudio) {
e.posAudio.remove();
e.posAudio = undefined;
}
entities.splice(entities.indexOf(e), 1);
changed = true;
}
});

rerender();
// add in new entities
for (const id of Array.from(connections.keys())) {
if (!entities.some((e) => e.id === id)) {
entities.push({ id, posAudio: undefined });
changed = true;
}
}

// dont run until first time unpaused to make sure audio context is running from first press
if (
!firstPaused &&
!sameIds(voiceIds.current, Array.from(voiceStreams.keys()))
) {
voiceIds.current = Array.from(voiceStreams.keys());

// remove voice streams that are no longer connected
if (!firstPaused) {
// remove media connections streams that are no longer connected
entities.map((e) => {
if (!voiceIds.current.includes(e.id)) {
if (!mediaConnections.has(e.id)) {
e.posAudio?.remove();
e.posAudio = undefined;
changed = true;
}
});

// add in new voice streams
for (const id of voiceIds.current) {
const entity = entities.find((e) => e.id === id);
if (!entity) continue;

const stream = voiceStreams.get(id)!;
if (!stream) continue;
entities.filter(needsAudio).map((e) => {
// add in new media connections if the stream is active
const mediaConn = mediaConnections.get(e.id);
if (!mediaConn) return;
if (!mediaConn.remoteStream) return;

const audioElem = document.createElement("audio");
audioElem.srcObject = stream;
audioElem.srcObject = mediaConn.remoteStream; // remote is incoming, local is own voice
audioElem.muted = true;
audioElem.autoplay = true;
audioElem.loop = true;
//@ts-ignore
audioElem.playsInline = true;

const posAudio = new PositionalAudio(listener);
posAudio.userData.peerId = id;
posAudio.setMediaStreamSource(stream);
posAudio.userData.peerId = e.id;
posAudio.setMediaStreamSource(audioElem.srcObject);
posAudio.setRefDistance(2);
posAudio.setDirectionalCone(200, 290, 0.2);
posAudio.setVolume(0.6);
posAudio.setDirectionalCone(200, 290, 0.35);

// posAudio.add(new PositionalAudioHelper(posAudio, 1));
entity.posAudio = posAudio;
}
e.posAudio = posAudio;

rerender();
changed = true;
});
}

if (changed) rerender();
});

return entities;
Expand Down
14 changes: 9 additions & 5 deletions src/layers/Network/logic/connection.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { useEffect, useMemo, useState } from "react";
import { DataConnection, Peer } from "peerjs";
import { DataConnection, MediaConnection, Peer } from "peerjs";
import { isLocalNetwork } from "./local";
import { LocalSignaller } from "./signallers/LocalSignaller";
import { MuseSignaller } from "./signallers/MuseSignaller";
import { useWaving } from "./wave";
import { Signaller, SignallerConfig } from "./signallers";
import { Channels, useChannels } from "./channels";
import { useVoice } from "./voice";
import { useVoiceConnections } from "./voice";
import { getMuseIceServers } from "./ice";

export type ConnectionState = {
connected: boolean;
connect: (config?: ConnectionConfig) => Promise<void>;
connections: Map<string, DataConnection>;
voiceStreams: Map<string, MediaStream>;
mediaConnections: Map<string, MediaConnection>;
disconnect: () => void;
voice: boolean;
setVoice: (v: boolean) => void;
Expand Down Expand Up @@ -43,6 +43,10 @@ export const useConnection = (
console.log("connection closed with peer");
connections.delete(conn.peer);
});
conn.on("error", () => {
console.log("connection closed with peer");
connections.delete(conn.peer);
});
channels.greet(conn);
connections.set(conn.peer, conn);
});
Expand Down Expand Up @@ -125,16 +129,16 @@ export const useConnection = (

const [voice, setVoice] = useState(!!externalConfig.voice);
useEffect(() => setVoice(!!externalConfig.voice), [externalConfig.voice]);
const voiceStreams = useVoice(voice, peer, connections);
const mediaConnections = useVoiceConnections(voice, peer, connections);

return {
connected,
connect,
disconnect,
connections,
voiceStreams,
useChannel: channels.useChannel,
voice,
setVoice,
mediaConnections,
};
};
Loading