feat: add playable actors and seamless journey state
This commit is contained in:
@@ -29,13 +29,12 @@
|
||||
* station said so is a nice idea and a bad dependency for a room that has to
|
||||
* render with no network at all.
|
||||
*
|
||||
* ### Orbit dollhouse is the only navigation mode
|
||||
* ### Orbit dollhouse remains the default navigation mode
|
||||
*
|
||||
* Walk mode is not built here. `Plan` already produces the collision segments it
|
||||
* will need, which is the point of doing the wall split once, but v1 orbits: the
|
||||
* ceilings come off, the walls between you and what you are looking at go
|
||||
* translucent, and the existing camera, flight and picking machinery is reused
|
||||
* verbatim.
|
||||
* An optional `OfficeWalker` can temporarily possess a local actor and publish a
|
||||
* chase-camera pose. It is inactive by default; without one, or while inactive,
|
||||
* the ceilings, occlusion fading, named views and orbit controls behave exactly
|
||||
* as before.
|
||||
*
|
||||
* ### Two depths, and the public one is the architecture without the people
|
||||
*
|
||||
@@ -83,6 +82,11 @@ import { Plan, type Depth, type PlanOptions } from "./plan.ts";
|
||||
import { createPresenceLayer, type PresenceLayer, type PresencePalette } from "./presence.ts";
|
||||
import { createShell, type Shell, type WallInfo } from "./shell.ts";
|
||||
import { createLuminaires, type Luminaires, type Walker } from "./luminaires.ts";
|
||||
import {
|
||||
createOfficeWalker,
|
||||
type OfficeWalker,
|
||||
type OfficeWalkerOptions,
|
||||
} from "./officeWalker.ts";
|
||||
import {
|
||||
createRobotLayer,
|
||||
type RobotLayer,
|
||||
@@ -210,6 +214,8 @@ export interface OfficeSceneOptions {
|
||||
* withhold, so a stranger gets them too.
|
||||
*/
|
||||
robots?: readonly RobotSpec[];
|
||||
/** Optional local walk actor. Constructed inactive unless `walker.active` says otherwise. */
|
||||
walker?: OfficeWalkerOptions;
|
||||
/** Defaults to false — the lid comes off, because that is the whole view. */
|
||||
showCeilings?: boolean;
|
||||
/** Fade the walls you are looking through. Defaults to true. */
|
||||
@@ -230,6 +236,8 @@ export interface OfficeScene extends StageScene {
|
||||
* print the "no presence" badge and whether to offer a sign-in.
|
||||
*/
|
||||
depth: Depth;
|
||||
/** Local walk-mode actor, or null when this scene was built as dollhouse-only. */
|
||||
walker: OfficeWalker | null;
|
||||
/** The pack's viewpoints, as the thing a legend prints and `flyTo` is keyed on. */
|
||||
views: View[];
|
||||
flyTo(viewId: string): void;
|
||||
@@ -486,6 +494,8 @@ export function createOfficeScene(office: Office, options: OfficeSceneOptions):
|
||||
options.robots && options.robots.length > 0
|
||||
? createRobotLayer(plan, { materials, robots: options.robots })
|
||||
: null;
|
||||
const officeWalker = options.walker ? createOfficeWalker(plan, options.walker) : null;
|
||||
if (officeWalker) scene.add(officeWalker.root);
|
||||
if (robots) {
|
||||
scene.add(robots.group);
|
||||
/**
|
||||
@@ -496,7 +506,12 @@ export function createOfficeScene(office: Office, options: OfficeSceneOptions):
|
||||
* through a reference taken at setup. Calling it every frame would allocate
|
||||
* nothing extra but would imply the array were a snapshot, which it is not.
|
||||
*/
|
||||
luminaires.setWalkers(robots.robots());
|
||||
}
|
||||
if (robots || officeWalker) {
|
||||
luminaires.setWalkers([
|
||||
...(robots?.robots() ?? NO_ROBOTS),
|
||||
...(officeWalker ? [officeWalker.view] : []),
|
||||
]);
|
||||
}
|
||||
// A public office has no presence layer, rather than an empty one. The
|
||||
// difference is not cosmetic: an empty `PresenceLayer` is a `THREE.Group`
|
||||
@@ -749,13 +764,18 @@ export function createOfficeScene(office: Office, options: OfficeSceneOptions):
|
||||
controls: kit.controls,
|
||||
plan,
|
||||
depth,
|
||||
walker: officeWalker,
|
||||
views,
|
||||
// A public office anchors nothing, because it has nobody to anchor. The
|
||||
// empty map is this scene's own rather than a shared module-level one: an
|
||||
// HTML overlay that writes into what it was handed should not be able to
|
||||
// reach across into another office.
|
||||
anchors: presence?.anchors ?? new Map<string, THREE.Vector3>(),
|
||||
flyTo,
|
||||
flyTo(viewId) {
|
||||
officeWalker?.setActive(false);
|
||||
kit.controls.enabled = true;
|
||||
flyTo(viewId);
|
||||
},
|
||||
current: () => currentView,
|
||||
onViewChange(fn) {
|
||||
viewListeners.push(fn);
|
||||
@@ -799,7 +819,11 @@ export function createOfficeScene(office: Office, options: OfficeSceneOptions):
|
||||
onExit: () => kit.resetPick(),
|
||||
tick(dt) {
|
||||
if (disposed) return;
|
||||
const walking = officeWalker?.active() ?? false;
|
||||
kit.controls.enabled = !walking;
|
||||
kit.tick(dt);
|
||||
officeWalker?.tick(dt);
|
||||
if (walking && officeWalker) kit.setPose(officeWalker.followPose());
|
||||
updateOcclusion();
|
||||
// Robots first: the lights above them should respond to where they are
|
||||
// *now*, not to where they were last frame.
|
||||
@@ -807,6 +831,7 @@ export function createOfficeScene(office: Office, options: OfficeSceneOptions):
|
||||
luminaires.tick(dt);
|
||||
},
|
||||
dispose() {
|
||||
officeWalker?.dispose();
|
||||
robots?.dispose();
|
||||
luminaires.dispose();
|
||||
if (horizonPlane) {
|
||||
|
||||
Reference in New Issue
Block a user