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

Updatable client config (#167) #171

Merged
merged 2 commits into from
Aug 5, 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
9 changes: 6 additions & 3 deletions example/multi-user-3d-web-experience/client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@ const app = new Networked3dWebExperienceClient(holder, {
sprintAnimationFileUrl,
doubleJumpAnimationFileUrl,
},
skyboxHdrJpgUrl: hdrJpgUrl,
mmlDocuments: [{ url: `${protocol}//${host}/mml-documents/example-mml.html` }],
environmentConfiguration: {},
mmlDocuments: { example: { url: `${protocol}//${host}/mml-documents/example-mml.html` } },
environmentConfiguration: {
skybox: {
hdrJpgUrl: hdrJpgUrl,
},
},
avatarConfiguration: {
availableAvatars: [],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { createRef, forwardRef } from "react";
import { flushSync } from "react-dom";
import { createRoot, Root } from "react-dom/client";

import { AvatarType } from "./AvatarType";
import { AvatarConfiguration, AvatarType } from "./AvatarType";
import { AvatarSelectionUIComponent } from "./components/AvatarPanel/AvatarSectionUIComponent";

const ForwardedAvatarSelectionUIComponent = forwardRef(AvatarSelectionUIComponent);
Expand All @@ -14,12 +14,9 @@ export type CustomAvatar = AvatarType & {

export type AvatarSelectionUIProps = {
holderElement: HTMLElement;
clientId: number;
visibleByDefault?: boolean;
availableAvatars: Array<AvatarType>;
sendMessageToServerMethod: (avatar: CustomAvatar) => void;
enableCustomAvatar?: boolean;
};
} & AvatarConfiguration;

export class AvatarSelectionUI {
private root: Root;
Expand All @@ -36,15 +33,23 @@ export class AvatarSelectionUI {
this.config.sendMessageToServerMethod(avatar);
};

public updateAvatarConfig(avatarConfig: AvatarConfiguration) {
this.config = {
...this.config,
...avatarConfig,
};
this.init();
}

init() {
flushSync(() =>
this.root.render(
<ForwardedAvatarSelectionUIComponent
ref={this.appRef}
onUpdateUserAvatar={this.onUpdateUserAvatar}
visibleByDefault={false}
visibleByDefault={this.config.visibleByDefault}
availableAvatars={this.config.availableAvatars}
enableCustomAvatar={this.config.enableCustomAvatar}
allowCustomAvatars={this.config.allowCustomAvatars}
/>,
),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,8 @@ export type AvatarType = {
mmlCharacterUrl: string;
}
);

export type AvatarConfiguration = {
availableAvatars: Array<AvatarType>;
allowCustomAvatars?: boolean;
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type AvatarSelectionUIProps = {
onUpdateUserAvatar: (avatar: AvatarType) => void;
visibleByDefault?: boolean;
availableAvatars: AvatarType[];
enableCustomAvatar?: boolean;
allowCustomAvatars?: boolean;
};

enum CustomAvatarType {
Expand Down Expand Up @@ -92,6 +92,10 @@ export const AvatarSelectionUIComponent: ForwardRefRenderFunction<any, AvatarSel
}
};

if (!props.availableAvatars.length && !props.allowCustomAvatars) {
return null;
}

return (
<>
<div className={styles.menuButton} onClick={handleRootClick}>
Expand Down Expand Up @@ -146,7 +150,7 @@ export const AvatarSelectionUIComponent: ForwardRefRenderFunction<any, AvatarSel
</div>
</div>
)}
{props.enableCustomAvatar && (
{props.allowCustomAvatars && (
<div className={styles.customAvatarSection}>
{!!props.availableAvatars.length && <hr />}
<h2>Custom Avatar Section</h2>
Expand Down
2 changes: 1 addition & 1 deletion packages/3d-web-avatar-selection-ui/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from "./avatar-selection-ui/AvatarSelectionUI";
export { AvatarType } from "./avatar-selection-ui/AvatarType";
export * from "./avatar-selection-ui/AvatarType";
3 changes: 3 additions & 0 deletions packages/3d-web-client-core/src/mml/MMLCompositionScene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
ChatProbe,
LoadingProgressManager,
LinkProps,
MMLDocumentTimeManager,
} from "mml-web";
import { AudioListener, Group, Object3D, PerspectiveCamera, Scene, WebGLRenderer } from "three";

Expand All @@ -30,6 +31,7 @@ export class MMLCompositionScene {
public group: Group;

public readonly mmlScene: IMMLScene;
public readonly documentTimeManager: MMLDocumentTimeManager;
private readonly promptManager: PromptManager;
private readonly interactionManager: InteractionManager;
private readonly interactionListener: InteractionListener;
Expand All @@ -49,6 +51,7 @@ export class MMLCompositionScene {
this.interactionManager = interactionManager;
this.interactionListener = interactionListener;
this.loadingProgressManager = new LoadingProgressManager();
this.documentTimeManager = new MMLDocumentTimeManager();

this.mmlScene = {
getAudioListener: () => this.config.audioListener,
Expand Down
182 changes: 120 additions & 62 deletions packages/3d-web-client-core/src/rendering/composer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
Scene,
ShadowMapType,
SRGBColorSpace,
Texture,
ToneMapping,
Vector2,
WebGLRenderer,
Expand Down Expand Up @@ -65,7 +66,14 @@ export type EnvironmentConfiguration = {
blurriness?: number;
azimuthalAngle?: number;
polarAngle?: number;
};
} & (
| {
hdrJpgUrl: string;
}
| {
hdrUrl: string;
}
);
envMap?: {
intensity?: number;
};
Expand All @@ -88,8 +96,6 @@ export class Composer {
private resizeListener: () => void;
public resolution: Vector2 = new Vector2(this.width, this.height);

private isEnvHDRI: boolean = false;

private readonly scene: Scene;
public postPostScene: Scene;
private readonly camera: PerspectiveCamera;
Expand Down Expand Up @@ -123,6 +129,14 @@ export class Composer {
private ambientLight: AmbientLight | null = null;
private environmentConfiguration?: EnvironmentConfiguration;

private skyboxState: {
src: {
hdrJpgUrl?: string;
hdrUrl?: string;
};
latestPromise: Promise<unknown> | null;
} = { src: {}, latestPromise: null };

public sun: Sun | null = null;
public spawnSun: boolean;

Expand Down Expand Up @@ -264,6 +278,14 @@ export class Composer {
this.scene.add(this.sun);
}

if (this.environmentConfiguration?.skybox) {
if ("hdrJpgUrl" in this.environmentConfiguration.skybox) {
this.useHDRJPG(this.environmentConfiguration.skybox.hdrJpgUrl);
} else if ("hdrUrl" in this.environmentConfiguration.skybox) {
this.useHDRI(this.environmentConfiguration.skybox.hdrUrl);
}
}

this.updateSunValues();

this.resizeListener = () => {
Expand All @@ -273,6 +295,22 @@ export class Composer {
this.fitContainer();
}

public updateEnvironmentConfiguration(environmentConfiguration: EnvironmentConfiguration) {
this.environmentConfiguration = environmentConfiguration;

if (environmentConfiguration.skybox) {
if ("hdrJpgUrl" in environmentConfiguration.skybox) {
this.useHDRJPG(environmentConfiguration.skybox.hdrJpgUrl);
} else if ("hdrUrl" in environmentConfiguration.skybox) {
this.useHDRI(environmentConfiguration.skybox.hdrUrl);
}
}

this.updateSkyboxAndEnvValues();
this.updateAmbientLightValues();
this.updateSunValues();
}

public setupTweakPane(tweakPane: TweakPane) {
tweakPane.setupRenderPane(
this.effectComposer,
Expand Down Expand Up @@ -368,74 +406,76 @@ export class Composer {
);
}

public useHDRJPG(url: string, fromFile: boolean = false): void {
const pmremGenerator = new PMREMGenerator(this.renderer);
const hdrJpg = new HDRJPGLoader(this.renderer).load(url, () => {
const hdrJpgEquirectangularMap = hdrJpg.renderTarget.texture;

hdrJpgEquirectangularMap.mapping = EquirectangularReflectionMapping;
hdrJpgEquirectangularMap.needsUpdate = true;

const envMap = pmremGenerator!.fromEquirectangular(hdrJpgEquirectangularMap).texture;
if (envMap) {
envMap.colorSpace = LinearSRGBColorSpace;
envMap.needsUpdate = true;
this.scene.environment = envMap;
this.scene.environmentIntensity = envValues.envMapIntensity;
this.scene.environmentRotation = new Euler(
MathUtils.degToRad(envValues.skyboxPolarAngle),
MathUtils.degToRad(envValues.skyboxAzimuthalAngle),
0,
);
this.scene.background = envMap;
this.scene.backgroundIntensity = envValues.skyboxIntensity;
this.scene.backgroundBlurriness = envValues.skyboxBlurriness;
this.scene.backgroundRotation = new Euler(
MathUtils.degToRad(envValues.skyboxPolarAngle),
MathUtils.degToRad(envValues.skyboxAzimuthalAngle),
0,
);
this.isEnvHDRI = true;
private async loadHDRJPG(url: string): Promise<Texture> {
return new Promise((resolve, reject) => {
const pmremGenerator = new PMREMGenerator(this.renderer);
const hdrJpg = new HDRJPGLoader(this.renderer).load(url, () => {
const hdrJpgEquirectangularMap = hdrJpg.renderTarget.texture;
hdrJpgEquirectangularMap.mapping = EquirectangularReflectionMapping;
hdrJpgEquirectangularMap.needsUpdate = true;

const envMap = pmremGenerator!.fromEquirectangular(hdrJpgEquirectangularMap).texture;
hdrJpgEquirectangularMap.dispose();
pmremGenerator!.dispose();
}

hdrJpg.dispose();
hdrJpg.dispose();
if (envMap) {
envMap.colorSpace = LinearSRGBColorSpace;
envMap.needsUpdate = true;
resolve(envMap);
} else {
reject("Failed to generate environment map");
}
});
});
}

public useHDRI(url: string, fromFile: boolean = false): void {
if ((this.isEnvHDRI && fromFile === false) || !this.renderer) {
return;
}
const pmremGenerator = new PMREMGenerator(this.renderer);
new RGBELoader(new LoadingManager()).load(
url,
(texture) => {
private async loadHDRi(url: string): Promise<Texture> {
return new Promise((resolve, reject) => {
const pmremGenerator = new PMREMGenerator(this.renderer);
new RGBELoader(new LoadingManager()).load(url, (texture) => {
const envMap = pmremGenerator!.fromEquirectangular(texture).texture;
texture.dispose();
pmremGenerator!.dispose();
if (envMap) {
envMap.colorSpace = LinearSRGBColorSpace;
envMap.needsUpdate = true;
this.scene.environment = envMap;
this.scene.environmentIntensity = envValues.envMapIntensity;
this.scene.environmentRotation = new Euler(
MathUtils.degToRad(envValues.skyboxPolarAngle),
MathUtils.degToRad(envValues.skyboxAzimuthalAngle),
0,
);
this.scene.background = envMap;
this.scene.backgroundIntensity = envValues.skyboxIntensity;
this.scene.backgroundBlurriness = envValues.skyboxBlurriness;
this.isEnvHDRI = true;
texture.dispose();
pmremGenerator!.dispose();
resolve(envMap);
} else {
reject("Failed to generate environment map");
}
},
() => {},
(error: ErrorEvent) => {
console.error(`Can't load ${url}: ${JSON.stringify(error)}`);
},
);
});
});
}

public useHDRJPG(url: string, fromFile: boolean = false): void {
if (this.skyboxState.src.hdrJpgUrl === url) {
return;
}

const hdrJPGPromise = this.loadHDRJPG(url);
this.skyboxState.src = { hdrJpgUrl: url };
this.skyboxState.latestPromise = hdrJPGPromise;
hdrJPGPromise.then((envMap) => {
if (this.skyboxState.latestPromise !== hdrJPGPromise) {
return;
}
this.applyEnvMap(envMap);
});
}

public useHDRI(url: string): void {
if (this.skyboxState.src.hdrUrl === url) {
return;
}
const hdrPromise = this.loadHDRi(url);
this.skyboxState.src = { hdrUrl: url };
this.skyboxState.latestPromise = hdrPromise;
hdrPromise.then((envMap) => {
if (this.skyboxState.latestPromise !== hdrPromise) {
return;
}
this.applyEnvMap(envMap);
});
}

public setHDRIFromFile(): void {
Expand All @@ -453,7 +493,7 @@ export class Composer {
const fileURL = URL.createObjectURL(file);
if (fileURL) {
if (extension === "hdr") {
this.useHDRI(fileURL, true);
this.useHDRI(fileURL);
} else if (extension === "jpg") {
this.useHDRJPG(fileURL);
} else {
Expand Down Expand Up @@ -542,4 +582,22 @@ export class Composer {
}
this.setAmbientLight();
}

private applyEnvMap(envMap: Texture) {
this.scene.environment = envMap;
this.scene.environmentIntensity = envValues.envMapIntensity;
this.scene.environmentRotation = new Euler(
MathUtils.degToRad(envValues.skyboxPolarAngle),
MathUtils.degToRad(envValues.skyboxAzimuthalAngle),
0,
);
this.scene.background = envMap;
this.scene.backgroundIntensity = envValues.skyboxIntensity;
this.scene.backgroundBlurriness = envValues.skyboxBlurriness;
this.scene.backgroundRotation = new Euler(
MathUtils.degToRad(envValues.skyboxPolarAngle),
MathUtils.degToRad(envValues.skyboxAzimuthalAngle),
0,
);
}
}
Loading
Loading