feat: add playable flight and office screen sharing
This commit is contained in:
+48
-2
@@ -70,6 +70,14 @@ import type {
|
||||
ActorControllerSnapshot,
|
||||
ActorIdentity,
|
||||
} from "../actors/controller.ts";
|
||||
import {
|
||||
createSceneAircraft,
|
||||
type SceneAircraftOptions,
|
||||
} from "../aircraft/sceneAircraft.ts";
|
||||
import type {
|
||||
AircraftActionSnapshot,
|
||||
AircraftControllerSnapshot,
|
||||
} from "../aircraft/controller.ts";
|
||||
|
||||
export interface SceneOptions {
|
||||
city: City;
|
||||
@@ -89,6 +97,8 @@ export interface SceneOptions {
|
||||
actor?: SceneActorOptions;
|
||||
/** Geographic spawn anchor for that actor; defaults to the board centre. */
|
||||
actorAnchor?: { lat: number; lng: number };
|
||||
/** Optional possessed fixed-wing aircraft; the city supplies its projection and terrain. */
|
||||
aircraft?: Omit<SceneAircraftOptions, "project" | "groundAt">;
|
||||
flights?: FlightSource;
|
||||
/**
|
||||
* Element sets to propagate, if this deployment has any.
|
||||
@@ -191,6 +201,10 @@ export interface SceneHandle {
|
||||
setActorIdentity(identity: ActorIdentity): void;
|
||||
setActorActive(active: boolean): void;
|
||||
actorActive(): boolean;
|
||||
setAircraftActions(actions: Partial<AircraftActionSnapshot>): void;
|
||||
aircraftState(): Readonly<AircraftControllerSnapshot> | null;
|
||||
setAircraftActive(active: boolean): void;
|
||||
aircraftActive(): boolean;
|
||||
setMarkers(markers: Marker[]): void;
|
||||
/** Take this city off the stage and release everything it built. */
|
||||
dispose(): void;
|
||||
@@ -377,6 +391,16 @@ export async function createScene(
|
||||
})
|
||||
: null;
|
||||
if (sceneActor) scene.add(sceneActor.root);
|
||||
const sceneAircraft = options.aircraft
|
||||
? createSceneAircraft({
|
||||
...options.aircraft,
|
||||
project: (lat, lng) => world.project(lat, lng),
|
||||
groundAt: (lat, lng) => world.groundAt(lat, lng),
|
||||
altitudeSceneUnitsPerMetre: options.aircraft.altitudeSceneUnitsPerMetre ??
|
||||
1 / world.metresPerUnit,
|
||||
})
|
||||
: null;
|
||||
if (sceneAircraft) scene.add(sceneAircraft.root);
|
||||
|
||||
let flightLayer: FlightLayer | null = null;
|
||||
let flightTimer = 0;
|
||||
@@ -445,6 +469,7 @@ export async function createScene(
|
||||
// is an explicit UI action, so a chapter selection always hands the camera
|
||||
// back before it moves anywhere else.
|
||||
sceneActor?.setActive(false);
|
||||
sceneAircraft?.setActive(false);
|
||||
kit.controls.minDistance = orbitMinDistance;
|
||||
if (kit.camera.near !== 0.1) {
|
||||
kit.camera.near = 0.1;
|
||||
@@ -489,10 +514,13 @@ export async function createScene(
|
||||
onExit: () => kit.resetPick(),
|
||||
tick(dt) {
|
||||
const actorPlaying = sceneActor?.active() ?? false;
|
||||
kit.controls.enabled = !actorPlaying;
|
||||
const aircraftPlaying = sceneAircraft?.active() ?? false;
|
||||
kit.controls.enabled = !actorPlaying && !aircraftPlaying;
|
||||
kit.tick(dt);
|
||||
sceneActor?.tick(dt);
|
||||
if (actorPlaying && sceneActor) kit.setPose(sceneActor.followPose());
|
||||
sceneAircraft?.tick(dt);
|
||||
if (aircraftPlaying && sceneAircraft) kit.setPose(sceneAircraft.followPose());
|
||||
roadTraffic?.tick(dt);
|
||||
clouds.tick(dt);
|
||||
if (options.flights && flightLayer) {
|
||||
@@ -543,6 +571,7 @@ export async function createScene(
|
||||
nightLights.dispose();
|
||||
markerLayer.dispose();
|
||||
roadTraffic?.dispose();
|
||||
sceneAircraft?.dispose();
|
||||
kit.dispose();
|
||||
scene.traverse((obj) => {
|
||||
const mesh = obj as THREE.Mesh;
|
||||
@@ -589,7 +618,10 @@ export async function createScene(
|
||||
setActorIdentity: (identity) => { sceneActor?.setIdentity(identity); },
|
||||
setActorActive(active) {
|
||||
sceneActor?.setActive(active);
|
||||
if (active) roadTraffic?.setFollowing(false);
|
||||
if (active) {
|
||||
sceneAircraft?.setActive(false);
|
||||
roadTraffic?.setFollowing(false);
|
||||
}
|
||||
// State boards compress one real metre to a few hundredths of a scene
|
||||
// unit. Their possessed actor and chase camera are therefore closer than
|
||||
// the map camera's 0.1 near plane; lower it only for play mode so the
|
||||
@@ -599,11 +631,25 @@ export async function createScene(
|
||||
kit.camera.updateProjectionMatrix();
|
||||
},
|
||||
actorActive: () => sceneActor?.active() ?? false,
|
||||
setAircraftActions: (actions) => { sceneAircraft?.setActions(actions); },
|
||||
aircraftState: () => sceneAircraft?.state() ?? null,
|
||||
setAircraftActive(active) {
|
||||
sceneAircraft?.setActive(active);
|
||||
if (active) {
|
||||
sceneActor?.setActive(false);
|
||||
roadTraffic?.setFollowing(false);
|
||||
}
|
||||
kit.camera.near = active ? 0.01 : 0.1;
|
||||
kit.controls.minDistance = active ? 0.01 : orbitMinDistance;
|
||||
kit.camera.updateProjectionMatrix();
|
||||
},
|
||||
aircraftActive: () => sceneAircraft?.active() ?? false,
|
||||
setMarkers(markers) {
|
||||
markerLayer.setMarkers(markers);
|
||||
},
|
||||
dispose() {
|
||||
sceneActor?.dispose();
|
||||
sceneAircraft?.dispose();
|
||||
/**
|
||||
* Off the stage, then released — and the stage itself is left running.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user