Skip to content

Commit

Permalink
Merge pull request #114 from musehq/dev
Browse files Browse the repository at this point in the history
v2.3.2
  • Loading branch information
alex-shortt authored Sep 7, 2022
2 parents d00e3bd + e95b0c7 commit 4dafab6
Show file tree
Hide file tree
Showing 10 changed files with 213 additions and 130 deletions.
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

1 comment on commit 4dafab6

@vercel
Copy link

@vercel vercel bot commented on 4dafab6 Sep 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

spacesvr – ./

spacesvr.vercel.app
spacesvr-git-master-muse-hq.vercel.app
spacesvr-muse-hq.vercel.app

Please sign in to comment.