mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 07:42:23 +00:00
feat(vessels, transit, rail): draw fleet models under the 3D toggle
Vessels, Transit and Rail turn nearby icons into fleet models at the aircraft layers' distances. A vessel is drawn at its AIS length and beam (static dimensions now reach vessel rows), standing at the sea surface; a transit vehicle or train stands on its road or rail, raised by its type's elevation, and is painted in its mode or category colour where the model marks it.
This commit is contained in:
1 parent
fcc44d0eaf
commit
7b93045895
18 files changed
+984
-15
No files matched your search
@@ -26,6 +26,22 @@ import {
|
||||
UNSELECTED_TRAIN_ALPHA,
|
||||
} from './policy.js';
|
||||
import { placeOnPath } from './pathBinding.js';
|
||||
import {
|
||||
fleetModelSet,
|
||||
loadFleetModelSet,
|
||||
transitModelSpec,
|
||||
} from '../../data/fleetModels.js';
|
||||
import { createFleetModelLayer } from '../../data/fleetModelInstances.js';
|
||||
|
||||
/** How often the trains near enough for a model are gathered again (wall ms). */
|
||||
const MODEL_CANDIDATES_MS = 500;
|
||||
/** Each category's colour, painted where a train model marks it. */
|
||||
const CATEGORY_TINTS = Object.fromEntries(
|
||||
Object.entries(RAIL_CATEGORY_COLORS).map(([category, css]) => [
|
||||
category,
|
||||
Cesium.Color.fromCssColorString(css),
|
||||
]),
|
||||
);
|
||||
|
||||
const SPRITE_PX = 64;
|
||||
/** Billboard tints; the setter copies, so sharing one instance is safe. */
|
||||
@@ -507,6 +523,11 @@ export function createRendering({ state, services, parts }) {
|
||||
);
|
||||
}
|
||||
|
||||
/** Whether a train's icon is drawn: shown, and not drawn by its model. */
|
||||
function markerShown(entry) {
|
||||
return categoryShown(entry) && !entry.modelOwnsVisual;
|
||||
}
|
||||
|
||||
/**
|
||||
* A train bound to its railway line is drawn on the line, at the arc its
|
||||
* playback has reached; any other train (or a bound train whose reports do
|
||||
@@ -536,7 +557,7 @@ export function createRendering({ state, services, parts }) {
|
||||
undefined,
|
||||
scratchPosition,
|
||||
);
|
||||
entry.marker.show = categoryShown(entry);
|
||||
entry.marker.show = markerShown(entry);
|
||||
}
|
||||
|
||||
/** While a train is selected, every other train is drawn see-through. */
|
||||
@@ -554,7 +575,7 @@ export function createRendering({ state, services, parts }) {
|
||||
entry.marker.width = size;
|
||||
entry.marker.height = size;
|
||||
entry.marker.color = tintFor(entry);
|
||||
entry.marker.show = categoryShown(entry);
|
||||
entry.marker.show = markerShown(entry);
|
||||
}
|
||||
|
||||
/** Re-tint every train after the selection appeared, moved or cleared. */
|
||||
@@ -658,7 +679,7 @@ export function createRendering({ state, services, parts }) {
|
||||
function cullSelected() {
|
||||
const entry = state._trains.get(state._selectedKey);
|
||||
if (!entry?.marker) return;
|
||||
const shown = categoryShown(entry) && facesCamera(entry.marker.position);
|
||||
const shown = markerShown(entry) && facesCamera(entry.marker.position);
|
||||
if (entry.marker.show !== shown) entry.marker.show = shown;
|
||||
parts.routes.cullStops(facesCamera);
|
||||
}
|
||||
@@ -666,6 +687,121 @@ export function createRendering({ state, services, parts }) {
|
||||
function onPreRender() {
|
||||
advanceMoving();
|
||||
if (state._enabled && state._selectedKey !== null) cullSelected();
|
||||
if (state._enabled) updateModels();
|
||||
}
|
||||
|
||||
// Under the DISPLAY rail's 3D toggle, trains near the camera are drawn as
|
||||
// the project's procedural fleet models, chosen by category and mode,
|
||||
// painted in their category colour where the model marks it, on the
|
||||
// aircraft layers' icon-to-model rules (services.fleet). The fleet set
|
||||
// loads once, up front, when the layer starts.
|
||||
let fleetModels = null;
|
||||
let modelItems = new Map();
|
||||
let modelCandidatesAtMs = -Infinity;
|
||||
|
||||
/** Load the fleet set and create the layer's models (on enable). */
|
||||
function startModels() {
|
||||
const fleet = services.fleet;
|
||||
if (!fleet?.policy || !state._viewer) return;
|
||||
loadFleetModelSet({ resolveAsset: fleet.resolveAsset }).catch(() => {
|
||||
/* models stay off; trains keep their icons */
|
||||
});
|
||||
fleetModels ||= createFleetModelLayer({
|
||||
viewer: state._viewer,
|
||||
display: fleet.display,
|
||||
policy: fleet.policy,
|
||||
resolveAsset: fleet.resolveAsset,
|
||||
});
|
||||
}
|
||||
|
||||
/** Hand a train's visual back to its icon. */
|
||||
function handBack(entry) {
|
||||
if (!entry.modelOwnsVisual) return;
|
||||
entry.modelOwnsVisual = false;
|
||||
if (entry.marker) entry.marker.show = markerShown(entry);
|
||||
}
|
||||
|
||||
function modelItem(entry, set) {
|
||||
let item = modelItems.get(entry.key);
|
||||
if (!item || item.entry !== entry)
|
||||
item = { entry, pickId: entry.key, modelOwnsVisual: false };
|
||||
const specKey = `${entry.category}|${entry.railMode}`;
|
||||
if (item.specKey !== specKey) {
|
||||
item.specKey = specKey;
|
||||
item.spec = transitModelSpec(set, {
|
||||
railCategory: entry.category,
|
||||
railMode: entry.railMode,
|
||||
});
|
||||
item.tint = CATEGORY_TINTS[entry.category] || CATEGORY_TINTS.local;
|
||||
}
|
||||
item.position = entry.marker.position;
|
||||
item.headingDeg = Number.isFinite(entry.courseDeg) ? entry.courseDeg : 0;
|
||||
item.show = categoryShown(entry);
|
||||
return item;
|
||||
}
|
||||
|
||||
function updateModels() {
|
||||
if (!fleetModels) return;
|
||||
if (!fleetModels.active() || state._trains.size === 0) {
|
||||
if (modelItems.size) {
|
||||
fleetModels.frame(new Map(), performance.now());
|
||||
for (const item of modelItems.values()) handBack(item.entry);
|
||||
modelItems = new Map();
|
||||
}
|
||||
return;
|
||||
}
|
||||
const set = fleetModelSet();
|
||||
const now = performance.now();
|
||||
const next = new Map();
|
||||
if (now - modelCandidatesAtMs >= MODEL_CANDIDATES_MS) {
|
||||
modelCandidatesAtMs = now;
|
||||
const camera = state._viewer?.camera;
|
||||
const policy = services.fleet.policy;
|
||||
const keepM =
|
||||
services.fleet.display?.()?.models3dMode === 'all'
|
||||
? policy.keepAllM
|
||||
: policy.keepM;
|
||||
const limitSq = keepM * keepM * 1.44;
|
||||
if (camera?.positionWC)
|
||||
for (const entry of state._trains.values()) {
|
||||
if (!entry.marker || !categoryShown(entry)) continue;
|
||||
if (
|
||||
Cesium.Cartesian3.distanceSquared(
|
||||
camera.positionWC,
|
||||
entry.marker.position,
|
||||
) > limitSq
|
||||
)
|
||||
continue;
|
||||
next.set(entry.key, modelItem(entry, set));
|
||||
}
|
||||
} else {
|
||||
for (const [key, item] of modelItems)
|
||||
if (state._trains.get(key) === item.entry && item.entry.marker)
|
||||
next.set(key, modelItem(item.entry, set));
|
||||
}
|
||||
for (const [key, item] of modelItems)
|
||||
if (next.get(key) !== item) handBack(item.entry);
|
||||
modelItems = next;
|
||||
fleetModels.frame(modelItems, now);
|
||||
for (const item of modelItems.values()) {
|
||||
const entry = item.entry;
|
||||
if (item.modelOwnsVisual) {
|
||||
entry.modelOwnsVisual = true;
|
||||
entry.marker.show = false;
|
||||
} else handBack(entry);
|
||||
}
|
||||
if (fleetModels.settling) governorRequestRender('rail-models');
|
||||
}
|
||||
|
||||
/** Drop every model (layer disabled); `destroy` also removes the collection. */
|
||||
function stopModels(destroy = false) {
|
||||
for (const item of modelItems.values()) item.entry.modelOwnsVisual = false;
|
||||
modelItems = new Map();
|
||||
modelCandidatesAtMs = -Infinity;
|
||||
if (destroy) {
|
||||
fleetModels?.destroy();
|
||||
fleetModels = null;
|
||||
} else fleetModels?.clear();
|
||||
}
|
||||
|
||||
function advanceMoving() {
|
||||
@@ -697,7 +833,7 @@ export function createRendering({ state, services, parts }) {
|
||||
function applyCategoryVisibility() {
|
||||
for (const entry of state._trains.values()) {
|
||||
if (!entry.marker) continue;
|
||||
entry.marker.show = categoryShown(entry);
|
||||
entry.marker.show = markerShown(entry);
|
||||
schedulePlayback(entry);
|
||||
}
|
||||
syncRenderHold();
|
||||
@@ -720,5 +856,7 @@ export function createRendering({ state, services, parts }) {
|
||||
applyCategoryVisibility,
|
||||
categoryShown,
|
||||
reset,
|
||||
startModels,
|
||||
stopModels,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user