1
0

feat: add playable flight and office screen sharing

This commit is contained in:
2026-08-11 19:46:47 -07:00
parent c0c7fcf974
commit 16dc85a6f8
13 changed files with 1355 additions and 45 deletions
+42
View File
@@ -76,6 +76,10 @@ export interface SceneActor {
switchActor(kind: ActorKind, identity?: ActorIdentity, mode?: ActorMode): ActorControllerSnapshot;
/** Rebuild the visible skin for a new serializable identity, preserving motion and kind. */
setIdentity(identity: ActorIdentity): ActorControllerSnapshot;
/** Attach a caller-owned live/static face texture. False when the current actor is not humanoid. */
attachFaceTexture(texture: THREE.Texture): boolean;
/** Detach and release the adapter's reference without disposing the caller texture. */
clearFaceTexture(): void;
dispose(): void;
}
@@ -137,6 +141,23 @@ function disposeRig(actor: RiggedActor): void {
else disposeCrow(actor.rig);
}
const DEFAULT_FACE_COLOR = 0x18242b;
function faceMaterial(rig: HumanoidRig): THREE.MeshBasicMaterial {
const material = rig.face.material;
if (!(material instanceof THREE.MeshBasicMaterial)) {
throw new Error("scene actor: humanoid face must use one MeshBasicMaterial");
}
return material;
}
function applyFaceTexture(rig: HumanoidRig, texture: THREE.Texture | null): void {
const material = faceMaterial(rig);
material.map = texture;
material.color.set(texture ? 0xffffff : DEFAULT_FACE_COLOR);
material.needsUpdate = true;
}
function positive(value: number, name: string): number {
if (!(value > 0) || !Number.isFinite(value)) throw new RangeError(`${name} must be finite and positive`);
return value;
@@ -177,6 +198,7 @@ export function createSceneActor(options: SceneActorOptions): SceneActor {
root.add(actor.rig.root);
let enabled = options.active ?? false;
let desired: ActorActionSnapshot = { ...NEUTRAL_ACTOR_ACTIONS };
let faceTexture: THREE.Texture | null = null;
let disposed = false;
function sync(): void {
@@ -218,6 +240,11 @@ export function createSceneActor(options: SceneActorOptions): SceneActor {
const previous = actor;
actor = buildActor(kind, controller.state().identity);
root.add(actor.rig.root);
if (actor.kind === "humanoid" && faceTexture) applyFaceTexture(actor.rig, faceTexture);
if (previous.kind === "humanoid") applyFaceTexture(previous.rig, null);
// A face is a humanoid-only, potentially sensitive live reference. Do not
// retain it invisibly while the player is represented as an animal.
if (actor.kind !== "humanoid") faceTexture = null;
disposeRig(previous);
sync();
}
@@ -299,10 +326,25 @@ export function createSceneActor(options: SceneActorOptions): SceneActor {
replaceActor(controller.state().kind);
return controller.snapshot();
},
attachFaceTexture(texture) {
if (disposed || actor.kind !== "humanoid") return false;
if (!texture || texture.isTexture !== true) {
throw new RangeError("scene actor: face texture must be a Three.js Texture");
}
faceTexture = texture;
applyFaceTexture(actor.rig, texture);
return true;
},
clearFaceTexture() {
faceTexture = null;
if (!disposed && actor.kind === "humanoid") applyFaceTexture(actor.rig, null);
},
dispose() {
if (disposed) return;
disposed = true;
root.removeFromParent();
if (actor.kind === "humanoid") applyFaceTexture(actor.rig, null);
faceTexture = null;
disposeRig(actor);
},
};