Skip to content
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

Packaging (#126) #127

Merged
merged 3 commits into from
Apr 26, 2024
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
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v18.16.0
v20.11.1
80 changes: 47 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,41 +1,51 @@
# (MML) 3D Web Experience

This repository contains packages used to run a web-based, multi-user 3D web experience that
supports [MML (Metaverse Markup Language)](https://mml.io/). This repository includes two published
packages:

- [`@mml-io/3d-web-client-core`](./packages/3d-web-client-core) - A package that implements the main
components of a 3D web experience.
- [`@mml-io/3d-web-user-networking`](./packages/3d-web-user-networking) - A package that contains
WebSocket server and client implementations that synchronize user positions.

There is an example implementation of a 3D web experience in the `examples` directory. This example
contains:

- `web-client`
- A THREE.js 3D experience utilizing the `@mml-io/3d-web-client-core` and
`@mml-io/3d-web-user-networking` packages to create a multi-user 3D web client that connects to
the server.
- `server`
- A server which serves the `web-client` and handles user networking WebSocket connections with
`@mml-io/3d-web-user-networking`
- Additionally, the server runs MML documents in the `mml-documents` directory which are then
connected to by the `web-client`.
- A simple session-based [auth system](#auth) that can be used as a reference to implement arbitrary http-based auth.

It can be easily deployed to environments that support Node.js and expose ports to the internet.
supports [MML (Metaverse Markup Language)](https://mml.io/).

It can be easily deployed to environments that support Node.js and can expose ports to the internet.

<img src="./playground.jpg">

## Packages

This repository includes the following published packages:

- [`@mml-io/3d-web-experience-client`](./packages/3d-web-experience-client)
- Client for a 3D web experience that includes user position networking, MML content composition,
MML-based avatars, and text chat.
- [`@mml-io/3d-web-experience-server`](./packages/3d-web-experience-server)
- Server for a 3D web experience that includes user position networking, MML hosting, and text chat.
- [`@mml-io/3d-web-client-core`](./packages/3d-web-client-core)
- The main components of a 3D web experience (controls, rendering, MML composition etc.) that can be
extended with other packages to create a full 3D web experience.
- [`@mml-io/3d-web-user-networking`](./packages/3d-web-user-networking)
- WebSocket server and client implementations that synchronize user positions.
- [`@mml-io/3d-web-avatar`](./packages/3d-web-avatar)
- Creates and parses MML documents for avatars (using `m-character`).
- [`@mml-io/3d-web-avatar-editor-ui`](./packages/3d-web-avatar-editor-ui)
- UI components (e.g. parts pickers) for creating avatars.
- [`@mml-io/3d-web-standalone-avatar-editor`](./packages/3d-web-standalone-avatar-editor)
- An MML avatar editor (using `m-character`).
- [`@mml-io/3d-web-text-chat`](./packages/3d-web-text-chat)
- Contains WebSocket server and client implementations for text chat.
- [`@mml-io/3d-web-voice-chat`](./packages/3d-web-voice-chat)
- Client implementation for spatial voice chat.

## Main features

- Multiple users can connect to the experience using just a web browser.

- Users can interact simultaneously with the stateful MML documents.

- Easy to deploy and extend with interactive MML content.

## Running locally
### Auth Flow
- When the client page is rendered by the server, the server uses a UserAuthenticator implementation to determine if a session should be generated for the incoming http request and if so includes that session token on the client page.
- The client then sends the session token in the first message to the server when it connects via websocket.
- The server can use the session token to authenticate the user and determine what identity (username, avatar etc) the user should have.
- An example implementation of this is provided in the example server, but the interface is extensible enough that a more complex user authenticator can limit which avatar components should be permitted based on external systems.


## Running Examples & Iterating Locally

Making sure you have Node.js installed, run the following from the root of the repository:

Expand All @@ -44,10 +54,14 @@ npm install
npm run iterate
```

Once the example server is running, open `http://localhost:8080` in your browser.

## Auth
- When the client page is rendered by the server the server uses a UserAuthenticator implementation to determine if a session should be generated for the incoming http request and if so includes that session token on the client page.
- The client then sends the session token in the first message to the server when it connects via websocket.
- The server can use the session token to authenticate the user and determine what identity (username, avatar etc) the user should have.
- An example implementation of this is provided in the example server, but the interface is extensible enough that a more complex user authenticator can limit which avatar components should be permitted based on external systems.
## Examples

- [`example/multi-user-3d-web-experience`](./example/multi-user-3d-web-experience)
- Once the server is running (see [above](#running-examples--iterating-locally)), open `http://localhost:8080`.
- A client and server pair of packages that uses the `@mml-io/3d-web-experience-client` and `@mml-io/3d-web-experience-server` packages to create a multi-user 3d web experience that includes MML hosting and text chat.
- [`example/local-only-multi-user-3d-web-experience`](./example/local-only-multi-user-3d-web-experience)
- Once the server is running (see [above](#running-examples--iterating-locally)), open `http://localhost:8081`.
- A client that uses the various packages to create a 3d web experience that only works locally. No server is needed, but there is a server to serve the client.
- [`example/web-avatar-editor`](./example/web-avatar-editor)
- Once the server is running (see [above](#running-examples--iterating-locally)), open `http://localhost:8082`.
- An avatar editor that uses the `@mml-io/3d-web-standalone-avatar-editor` to create and edit MML avatars and a simple Express server that hosts the editor.
Binary file added example/assets/hdr/puresky_2k.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions example/assets/models/anim_air.glb
Git LFS file not shown
4 changes: 2 additions & 2 deletions example/assets/models/anim_idle.glb
Git LFS file not shown
4 changes: 2 additions & 2 deletions example/assets/models/anim_jog.glb
Git LFS file not shown
4 changes: 2 additions & 2 deletions example/assets/models/anim_run.glb
Git LFS file not shown
4 changes: 2 additions & 2 deletions example/assets/models/bot.glb
Git LFS file not shown
Binary file removed example/assets/textures/checker.png
Binary file not shown.
48 changes: 0 additions & 48 deletions example/local-multi-web-client/src/Room.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ const buildOptions: esbuild.BuildOptions = {
".glb": "file",
".hdr": "file",
},
outbase: "../",
outbase: "../../", // This is targeting the parent of the "assets" directory to avoid generated paths including a traversal
sourceRoot: "./src",
publicPath: "/local-multi-web-client/",
publicPath: "/",
plugins: [
copy({
resolveFrom: "cwd",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@example/web-client",
"name": "@example/local-only-multi-user-3d-web-experience-client",
"private": true,
"version": "0.15.0",
"files": [
Expand All @@ -18,9 +18,10 @@
"@mml-io/3d-web-text-chat": "^0.15.0",
"@mml-io/3d-web-user-networking": "^0.15.0",
"@mml-io/3d-web-voice-chat": "^0.15.0",
"three": "0.153.0"
"mml-web-runner": "0.14.0",
"three": "0.163.0"
},
"devDependencies": {
"@types/three": "0.153.0"
"@types/three": "0.163.0"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg" href="/local-multi-web-client/favicon.svg" />
<link rel="icon" type="image/svg" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>MML 3D Web Experience</title>
<link rel="stylesheet" href="/local-multi-web-client/style.css" />
<link rel="stylesheet" href="/style.css" />
</head>
<body>
<div id="app"></div>
<div id="text-chat-ui"></div>
<div id="voice-chat-ui"></div>
<script type="application/javascript" src="/local-multi-web-client/index.js"></script>
<script type="application/javascript" src="/index.js"></script>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
CharacterState,
CollisionsManager,
Composer,
GroundPlane,
KeyInputManager,
MMLCompositionScene,
TimeManager,
Expand All @@ -15,15 +16,14 @@ import { EditableNetworkedDOM, NetworkedDOM } from "@mml-io/networked-dom-docume
import { MMLWebRunnerClient } from "mml-web-runner";
import { AudioListener, Euler, Scene, Vector3 } from "three";

import hdrUrl from "../../assets/hdr/puresky_2k.hdr";
import airAnimationFileUrl from "../../assets/models/anim_air.glb";
import idleAnimationFileUrl from "../../assets/models/anim_idle.glb";
import jogAnimationFileUrl from "../../assets/models/anim_jog.glb";
import sprintAnimationFileUrl from "../../assets/models/anim_run.glb";
import defaultAvatarMeshFileUrl from "../../assets/models/bot.glb";
import hdrJpgUrl from "../../../assets/hdr/puresky_2k.jpg";
import airAnimationFileUrl from "../../../assets/models/anim_air.glb";
import idleAnimationFileUrl from "../../../assets/models/anim_idle.glb";
import jogAnimationFileUrl from "../../../assets/models/anim_jog.glb";
import sprintAnimationFileUrl from "../../../assets/models/anim_run.glb";
import defaultAvatarMeshFileUrl from "../../../assets/models/bot.glb";

import { LocalAvatarServer } from "./LocalAvatarServer";
import { Room } from "./Room";

const animationConfig: AnimationConfig = {
airAnimationFileUrl,
Expand Down Expand Up @@ -90,7 +90,7 @@ export class LocalAvatarClient {
this.cameraManager.camera.add(this.audioListener);

this.composer = new Composer(this.scene, this.cameraManager.camera, true);
this.composer.useHDRI(hdrUrl);
this.composer.useHDRJPG(hdrJpgUrl);
this.element.appendChild(this.composer.renderer.domElement);

this.resizeObserver = new ResizeObserver(() => {
Expand All @@ -109,40 +109,40 @@ export class LocalAvatarClient {
},
);

this.characterManager = new CharacterManager(
this.composer,
this.characterModelLoader,
this.collisionsManager,
this.cameraManager,
this.timeManager,
this.keyInputManager,
this.remoteUserStates,
(characterState: CharacterState) => {
this.characterManager = new CharacterManager({
composer: this.composer,
characterModelLoader: this.characterModelLoader,
collisionsManager: this.collisionsManager,
cameraManager: this.cameraManager,
timeManager: this.timeManager,
keyInputManager: this.keyInputManager,
remoteUserStates: this.remoteUserStates,
sendUpdate: (characterState: CharacterState) => {
localAvatarServer.send(localClientId, characterState);
},
animationConfig,
() => {
characterResolve: () => {
return { username: "User", characterDescription };
},
);
});
this.scene.add(this.characterManager.group);

this.mmlComposition = new MMLCompositionScene(
this.element,
this.composer.renderer,
this.scene,
this.cameraManager.camera,
this.audioListener,
this.collisionsManager,
() => {
this.mmlComposition = new MMLCompositionScene({
targetElement: this.element,
renderer: this.composer.renderer,
scene: this.scene,
camera: this.cameraManager.camera,
audioListener: this.audioListener,
collisionsManager: this.collisionsManager,
getUserPositionAndRotation: () => {
return this.characterManager.getLocalCharacterPositionAndRotation();
},
);
});
this.scene.add(this.mmlComposition.group);

const room = new Room();
this.collisionsManager.addMeshesGroup(room);
this.scene.add(room);
const groundPlane = new GroundPlane();
this.collisionsManager.addMeshesGroup(groundPlane);
this.scene.add(groundPlane);

this.characterManager.spawnLocalCharacter(
localClientId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<m-light type="point" y="50" intensity="0.5"></m-light>
<m-light type="point" y="10" intensity="100"></m-light>
<m-cube color="red" x="-3" y="1.5" z="-5">
<m-attr-anim attr="ry" start="0" end="360" duration="5000"></m-attr-anim>
</m-cube>
Expand Down Expand Up @@ -41,4 +41,4 @@
});
stairs.append(stair);
}
</script>
</script>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"extends": "../../tsconfig.json",
"extends": "../../../tsconfig.json",
"compilerOptions": {
"noImplicitAny": true,
"module": "esnext",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as esbuild from "esbuild";

import { rebuildOnDependencyChangesPlugin } from "../../utils/rebuildOnDependencyChangesPlugin";
import { rebuildOnDependencyChangesPlugin } from "../../../utils/rebuildOnDependencyChangesPlugin";

const buildMode = "--build";
const watchMode = "--watch";
Expand Down
Loading
Loading