Skip to content

Commit

Permalink
Refactor mml character loading (#104)
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcusLongmuir authored Feb 1, 2024
1 parent 4fd4a46 commit c33d9eb
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 127 deletions.
21 changes: 16 additions & 5 deletions example/local-multi-web-client/src/LocalAvatarClient.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
AnimationConfig,
CameraManager,
CharacterDescription,
CharacterManager,
Expand All @@ -19,18 +20,26 @@ 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 meshFileUrl from "../../assets/models/bot.glb";
import defaultAvatarMeshFileUrl from "../../assets/models/bot.glb";

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

const characterDescription: CharacterDescription = {
const animationConfig: AnimationConfig = {
airAnimationFileUrl,
idleAnimationFileUrl,
jogAnimationFileUrl,
meshFileUrl,
sprintAnimationFileUrl,
modelScale: 1,
};

// Specify the avatar to use here:
const characterDescription: CharacterDescription = {
// Option 1 (Default) - Use a GLB file directly
meshFileUrl: defaultAvatarMeshFileUrl, // This is just an address of a GLB file
// Option 2 - Use an MML Character from a URL
// mmlCharacterUrl: "https://...",
// Option 3 - Use an MML Character from a string
// mmlCharacterString: `<m-character src="https://..."></m-character>`,
};

export class LocalAvatarClient {
Expand Down Expand Up @@ -111,6 +120,8 @@ export class LocalAvatarClient {
(characterState: CharacterState) => {
localAvatarServer.send(localClientId, characterState);
},
animationConfig,
characterDescription,
);
this.scene.add(this.characterManager.group);

Expand All @@ -132,7 +143,7 @@ export class LocalAvatarClient {
this.scene.add(room);

this.characterManager.spawnLocalCharacter(
characterDescription!,
characterDescription,
localClientId,
spawnPosition,
spawnRotation,
Expand Down
32 changes: 15 additions & 17 deletions example/web-client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
MMLCompositionScene,
TimeManager,
TweakPane,
AnimationConfig,
} from "@mml-io/3d-web-client-core";
import { ChatNetworkingClient, FromClientChatMessage, TextChatUI } from "@mml-io/3d-web-text-chat";
import {
Expand All @@ -33,18 +34,26 @@ 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 meshFileUrl from "../../assets/models/bot.glb";
import defaultAvatarMeshFileUrl from "../../assets/models/bot.glb";

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

const characterDescription: CharacterDescription = {
const animationConfig: AnimationConfig = {
airAnimationFileUrl,
idleAnimationFileUrl,
jogAnimationFileUrl,
meshFileUrl,
sprintAnimationFileUrl,
modelScale: 1,
};

// Specify the avatar to use here:
const characterDescription: CharacterDescription = {
// Option 1 (Default) - Use a GLB file directly
meshFileUrl: defaultAvatarMeshFileUrl, // This is just an address of a GLB file
// Option 2 - Use an MML Character from a URL
// mmlCharacterUrl: "https://...",
// Option 3 - Use an MML Character from a string
// mmlCharacterString: `<m-character src="https://..."></m-character>`,
};

const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
Expand Down Expand Up @@ -157,19 +166,8 @@ export class App {
this.latestCharacterObject.characterState = characterState;
this.networkClient.sendUpdate(characterState);
},
/*
If you want to use an MML Character in the experience, this is where you should
pass it as an argument.
MML Characters can be loaded by passing a URL that can be fetched and serve your
<m-character> tag, or just by passing your MML Character description in a string
like in the example below:
`
<m-character src="../../assets/models/bot.glb">
</m-character>
`,
*/
animationConfig,
characterDescription,
);
this.scene.add(this.characterManager.group);

Expand Down
18 changes: 13 additions & 5 deletions packages/3d-web-client-core/src/character/Character.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,17 @@ import { CharacterSpeakingIndicator } from "./CharacterSpeakingIndicator";
import { AnimationState } from "./CharacterState";
import { CharacterTooltip } from "./CharacterTooltip";

export type CharacterDescription = {
meshFileUrl: string;
export type AnimationConfig = {
idleAnimationFileUrl: string;
jogAnimationFileUrl: string;
sprintAnimationFileUrl: string;
airAnimationFileUrl: string;
modelScale: number;
meshModel?: Object3D;
};

export type CharacterDescription = {
meshFileUrl?: string;
mmlCharacterUrl?: string;
mmlCharacterString?: string;
};

export class Character extends Group {
Expand All @@ -27,6 +30,7 @@ export class Character extends Group {

constructor(
private readonly characterDescription: CharacterDescription,
private readonly animationConfig: AnimationConfig,
private readonly characterModelLoader: CharacterModelLoader,
private readonly characterId: number,
private readonly modelLoadedCallback: () => void,
Expand All @@ -40,7 +44,11 @@ export class Character extends Group {
}

private async load(): Promise<void> {
this.model = new CharacterModel(this.characterDescription, this.characterModelLoader);
this.model = new CharacterModel(
this.characterDescription,
this.animationConfig,
this.characterModelLoader,
);
await this.model.init();
this.add(this.model.mesh!);
if (this.speakingIndicator === null) {
Expand Down
75 changes: 8 additions & 67 deletions packages/3d-web-client-core/src/character/CharacterManager.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
import {
ModelLoader,
Character as MMLCharacter,
parseMMLDescription,
type MMLCharacterDescription,
} from "@mml-io/3d-web-avatar";
import { PositionAndRotation } from "mml-web";
import { Euler, Group, Object3D, Quaternion, Vector3 } from "three";
import { Euler, Group, Quaternion, Vector3 } from "three";

import { CameraManager } from "../camera/CameraManager";
import { CollisionsManager } from "../collisions/CollisionsManager";
Expand All @@ -14,7 +8,7 @@ import { KeyInputManager } from "../input/KeyInputManager";
import { Composer } from "../rendering/composer";
import { TimeManager } from "../time/TimeManager";

import { Character, CharacterDescription } from "./Character";
import { AnimationConfig, Character, CharacterDescription } from "./Character";
import { CharacterModelLoader } from "./CharacterModelLoader";
import { AnimationState, CharacterState } from "./CharacterState";
import { LocalController } from "./LocalController";
Expand All @@ -32,7 +26,6 @@ export class CharacterManager {
public remoteCharacterControllers: Map<number, RemoteController> = new Map();

private localCharacterSpawned: boolean = false;
private characterDescription: CharacterDescription | null = null;
private localController: LocalController;
public localCharacter: Character | null = null;

Expand All @@ -52,74 +45,21 @@ export class CharacterManager {
private readonly keyInputManager: KeyInputManager,
private readonly clientStates: Map<number, CharacterState>,
private readonly sendUpdate: (update: CharacterState) => void,
private readonly mmlCharacterDescriptionURL?: string,
private readonly animationConfig: AnimationConfig,
private readonly characterDescription: CharacterDescription,
) {
this.group = new Group();
}

private async fetchDescription(
characterDescription: string,
): Promise<Partial<MMLCharacterDescription>> {
let parsedDescrioption: Partial<MMLCharacterDescription> = {};
await fetch(characterDescription)
.then(async (response: Response) => {
const characterMMLDescriptionURL = await response.text();
const parsedMMLDescriptionURL = parseMMLDescription(characterMMLDescriptionURL);
parsedDescrioption = parsedMMLDescriptionURL[0];
})
.catch((_err) => {
const parsedMMLDescription = parseMMLDescription(characterDescription);
parsedDescrioption = parsedMMLDescription[0];
});
return parsedDescrioption;
}

private async composeMMLCharacter(descriptionURL: string): Promise<Object3D | undefined> {
const mmlCharacterDescription: Partial<MMLCharacterDescription> =
await this.fetchDescription(descriptionURL);

if (mmlCharacterDescription.base?.url.length === 0) {
throw new Error(
"ERROR: An MML Character Description was provided but it's not a valid <m-character> string, or a valid URL",
);
}

let mergedCharacter: Object3D | null = null;
if (mmlCharacterDescription) {
const characterBase = mmlCharacterDescription.base?.url || null;
const parts: string[] = [];

mmlCharacterDescription.parts?.forEach((part) => {
if (part.url) parts.push(part.url);
});

if (characterBase) {
const mmlCharacter = new MMLCharacter(new ModelLoader());
mergedCharacter = await mmlCharacter.mergeBodyParts(characterBase, parts);
if (mergedCharacter) {
return mergedCharacter.children[0].children[0];
}
}
}
}

public async spawnLocalCharacter(
public spawnLocalCharacter(
characterDescription: CharacterDescription,
id: number,
spawnPosition: Vector3 = new Vector3(),
spawnRotation: Euler = new Euler(),
): Promise<void> {
if (this.mmlCharacterDescriptionURL) {
const mmlCharacterBody = await this.composeMMLCharacter(this.mmlCharacterDescriptionURL);
if (mmlCharacterBody && mmlCharacterBody instanceof Object3D) {
characterDescription.meshModel = mmlCharacterBody;
}
}

this.characterDescription = characterDescription;

) {
const character = new Character(
characterDescription,
this.animationConfig,
this.characterModelLoader,
id,
() => {
Expand Down Expand Up @@ -164,6 +104,7 @@ export class CharacterManager {
) {
const character = new Character(
characterDescription,
this.animationConfig,
this.characterModelLoader,
id,
() => {
Expand Down
Loading

0 comments on commit c33d9eb

Please sign in to comment.