This document describes the runtime architecture of the worgen-rs Bevy application: plugin order, system scheduling, asset/data loading, UI integration, camera control, and core abstractions. All details reflect the code under src/.
worgen-rs is a desktop 3D asset viewer that:
- Reads configuration (
assets/settings.json) before constructing the BevyApp(static singleton initialization). - Scans a
Datadirectory for archive files and builds:- A global lowercase file → archive path map (fast resolution of virtual paths).
- Per‑archive categorized file lists (textures, models, world models, world maps).
- Exposes archives and categorized contents in a left egui panel with per‑file load state icons.
- Loads at most one selected root asset at a time through the
AssetServerusing custom loaders for images, models, world models, and world maps. - Computes a root bounding box (
RootAabb) and focuses a pan‑orbit camera automatically when a new scene root appears. - Shows a right details panel for the currently selected asset (images, meshes, materials, terrains, embedded models/world models, alpha maps, etc.).
- Provides runtime inspection (types, materials, entities) via
bevy_inspector_eguiintegrated inside the egui pass.
All file accesses use a virtual path scheme: archive://relative/path. A custom asset source resolves these paths by locating and reading files inside archives.
ArchiveAssetReaderPlugin– Registers thearchive://virtual asset source backed by a custom synchronous reader.DefaultPlugins– Standard Bevy plugins with asset metadata checks disabled (AssetMetaCheck::Never).SettingsPlugin– ProvidesTerrainSettingsand a system that propagates layer visibility changes (bitmask) to all existing extended terrain materials each frame they change.WorgenAssetPlugin– Registers reflected components (RootAabb,Model,WorldModel,WorldMap), initializes the custom asset types/loaders (ModelAsset,WorldModelAsset,WorldMapAsset, extended terrain material), and builds the global file → archive map inPreStartup.FrameTimeDiagnosticsPlugin– Frame timing metrics.EguiPlugin– egui integration (adds theEguiPrimaryContextPassschedule and user texture management).DefaultInspectorConfigPlugin– Integratesbevy_inspector_eguiallowing inspection of registered reflected types and components inside the egui pass.UiPlugin– Sets up the isolated UI camera & panels, registers / emitsFileSelectedevents, dynamic viewport adjustment.DataPlugin– Asynchronous archive scanning tasks, categorized file collection, selection & root scene entity lifecycle.PanOrbitCameraPlugin– Directional light + camera spawn, automatic focus on new root AABBs, pan / orbit / zoom input handling.
Settings::init() runs once before plugin registration to populate a static Settings singleton (game path, optional default model path, test image path).
PreStartup:
FileArchiveMap::init– Builds a global lowercase file path → archive path map by scanning all archives.
Startup:
archive::start_loading– Spawns async tasks (one per archive) to extract categorized file lists.ui::select_default_model– Emits aFileSelectedevent if a default model path is configured.camera::setup_camera– Spawns directional light + pan‑orbit camera entity.ui::setup_ui– Creates a dedicated UI 2D camera (isolated render layers) and disables automatic primary egui context creation.
PreUpdate:
camera::on_world_map_loaded– Focus when a world mapRootAabbappears.camera::on_world_model_loaded,camera::on_model_loaded– Focus when a world model or modelRootAabbappears (only run while no world map is present to avoid double focusing).
Update:
archive::check_archive_loading(conditional whileLoadArchiveTasksexists) – Polls archive categorization tasks; populatesArchiveInfoMapor triggers an error exit on failure.data::load_selected_file– Responds to the newestFileSelectedevent, despawns & unloads the priorCurrentFile, loads the newly selected asset (root label), and spawns an entity withCurrentFile+SceneRoot.camera::pan_orbit_camera– Processes accumulated mouse motion & scroll (pan/orbit/zoom) unless pointer is captured by egui.settings::apply_terrain_settings– PropagatesTerrainSettingschanges (recomputes a 4‑bitlevel_mask).
Egui (EguiPrimaryContextPass):
ui::data_info– Renders left archive browser + right current file details panels and adjusts world camera viewport to exclude panel widths.
Resources:
ArchiveInfoMap– Archive path → categorized lists (texture, model, world model, world map paths).FileInfoMap– Lowercase file path →FileInfo(original path, owning archive, inferredDataType, load/unload helpers, recursive load state lookup).LoadArchiveTasks– In‑flight asynchronous archive categorization tasks.TerrainSettings– User flags controlling visibility of up to four terrain texture layers (bitmask mapped toTerrainMaterial.level_mask).
Global singletons:
Settings– Static configuration loaded from JSON (game root path, test image/model path overrides).FileArchiveMap– Static map from file path → archive path used by the custom asset reader (read‑only after init).
Events:
FileSelected { file_path }– Issued by the UI or startup logic to request a new root asset load (debounced to newest per frame).
Components:
CurrentFile { path }– Marks the entity holding the scene root for the currently selected asset (used by right panel & unload logic).PanOrbitState,PanOrbitSettings– Pan/orbit/zoom camera state & configuration (keys: Ctrl=pan, Alt=orbit, Shift=zoom, scroll=zoom).RootAabb– Axis‑aligned bounding box derived from meshes (or terrain chunks) after consistent reorientation.Model,WorldModel,WorldMap– Marker components identifying scene root types for focus logic & UI introspection.TerrainMaterial– Extension payload ofExtTerrainMaterialstoring layer textures, combined alpha map, layer count and bitmask.Geoset,GeosetCatalog,GeosetSelection– Appearance variant system (see "Appearance Variant (Geoset) System").
FileInfoMap infers a DataType from extension:
Texture– Texture file.Model– Standard 3D model.WorldModel– Large static multi‑group structure (root file only, group files are implicit).WorldMap– Terrain map with chunks, embedded models/world models, textures & alpha masks.Unknown– Any other file (ignored by selection logic).
Dependencies (textures, group files, embedded assets) are scheduled by loaders; the UI only triggers root asset loads.
The ArchiveAssetReaderPlugin registers the archive source. Resolution steps:
- Lowercase the requested relative path.
- Look up owning archive via
FileArchiveMap. - Open archive and read file bytes (synchronously per request; Bevy orchestrates async scheduling at a higher level).
Common pattern: parse bytes → enqueue/load dependent assets (images, group files) → create meshes & materials → build a Scene with a consistent reorientation (rotate −90° X then −90° Z) → compute RootAabb → label sub‑assets for partial handle loading.
Loaders:
- Image loader – Decodes image format into RGBA
Imageassets, applying per‑texture sampler descriptors derived from format flags. - Model loader – Parses model structure, resolves texture handles (fallback to configured test image when missing), builds per‑batch meshes & materials. Appearance variant (geoset) grouping ensures only one variant of mutually exclusive categories is visible at spawn.
- World model loader – Parses root file, loads all group files, builds meshes per render batch, applies material flags (alpha blending, two‑sided, unlit, sampler modes), constructs a scene with
WorldModelmarker and child mesh entities. - World map loader – Parses terrain definition, generates one mesh per chunk (145 vertices, 256 CCW triangles via 4‑triangle fan per quad), creates a combined RGBA alpha texture per chunk, builds extended terrain materials carrying up to 4 texture layers + alpha mask, requests referenced models & world models, places them with orientation & scale adjustments, and labels all sub‑assets (chunks, materials, combined alpha, models, world models, images).
ExtTerrainMaterial = StandardMaterial + TerrainMaterial extension (combined alpha map + up to three additional layer textures + counts + layer mask). TerrainSettings recomputes a 4‑bit level_mask (bit per layer) applied to every existing extended terrain material when changed.
Two dynamic side panels:
- Left (Archives) – Collapsible archive headers → categorized file groups. Each file row: icon by type (🖼 texture, 📦 model, 🏰 world model, 🗺 world map, ❓ unknown) + load state overlay (▶ not loaded, ⏳ loading, ✔ loaded, ✖ failed). Clicking (non‑tooltip) emits
FileSelected. - Right (Current) – When a file is selected, shows a scrollable inspector-driven entity view (root + sub‑entities) including image previews & sampler parameters for images and terrain alpha/layer textures.
Viewport management: The UI camera renders only egui (isolated render layers). After each frame the main 3D camera viewport is shrunk horizontally to exclude the occupied left/right panel widths minimizing wasted rendering under opaque UI.
- User clicks a file row →
FileSelectedevent. - Handler retains only the newest event per frame (debounce for rapid clicking).
- If the selected path differs: previous
CurrentFileentity is despawned & its asset handle unloaded; new root asset loaded via labeled path (ModelAssetLabel::Root,WorldModelAssetLabel::Root,WorldMapAssetLabel::Root). - An entity with
CurrentFile+SceneRoot(handle)spawns. - Loader completion spawns the scene (with root marker +
RootAabb), triggering camera focus inPreUpdate.
Pan‑orbit controller:
- Inputs: Control = pan, Alt = orbit, Shift = zoom, scroll wheel = zoom (all adjustable through
PanOrbitSettings). - Aggregates all mouse motion & scroll events; exponential zoom scaling; yaw/pitch wrap & upside‑down horizontal inversion.
Focus logic: sets center to AABB center and radius to max(length(half_extents) * comfort, minimum). Yaw/pitch preserved; only center, radius and camera translation update.
RootAabb utilities derive a combined AABB from one or many meshes (optionally transformed). For terrains a merged bounding box is computed from chunk meshes prior to focus. Reorientation occurs before measurement to ensure consistent camera framing across asset types.
Some character / equipment models expose multiple optional or stylistic mesh fragments encoded as numeric ids. The runtime groups those into high‑level categories (Hair, Cape, Helm, etc.) and exposes a simple selection model:
Key components:
Geoset– Attached to each child mesh fragment (raw id + derived category + variant index).GeosetCatalog– Per‑model discovered variant indices per category (built lazily once).GeosetSelection– Mutable state: exclusive category → single variant; additive category → enabled set.
Systems (via GeosetRuntimePlugin):
build_geoset_catalog_system– Collects child components, builds catalog, seeds selection.apply_geoset_selection_system– Applies selection (change‑driven visibility updates).debug_cycle_cape_system– PressCto cycle cape variants (debug/testing aid).
UI: Right panel section lists models with catalogs. Exclusive categories offer prev / next / reset / none buttons; additive categories expose toggle chips. Clearing an exclusive category hides its variants until reselected.
- Archive task errors log the cause and request application error exit.
- Asset load failures surface as ✖ with tooltips for messages; previous scene persists (no implicit retry).
- Load state progression is derived from recursive dependency states (root + sub‑assets) to reflect readiness for viewing.
- Archive categorization: one async IO task per archive, polled each frame (unfinished tasks re‑queued).
- Asset pipeline: standard Bevy asynchronous loading; only root selection & unloading logic is custom.
- Only the newest selection per frame is processed (debounce during rapid clicks).
- A uniform reorientation (−90° X, −90° Z) yields consistent forward/up for all asset categories.
- Terrain layer visibility toggles update material bitmasks in place (no asset reload).
- Model appearance variants: mutually exclusive categories ensure only a single variant renders for overlapping cosmetic groups.
flowchart TD
subgraph Init[Initialization]
SettingsInit[Load settings.json]
FileArchiveMapInit[Build file to archive map]
end
subgraph Startup
StartArchiveTasks[Spawn archive info tasks]
UiSetup[Setup UI camera]
CamSetup[Setup light and camera]
DefaultSelect[Emit default selection]
end
subgraph Background
ArchiveTasks[(Archive tasks)] --> ArchiveInfoMap[ArchiveInfoMap]
end
ArchiveInfoMap --> LeftPanel[Left UI Panel\narchives and categorized files]
LeftPanel -->|click| FileEvt[FileSelected]
FileEvt --> LoadSel[load_selected_file]
LoadSel --> Current[CurrentFile entity and SceneRoot]
Current --> AabbAdded[RootAabb added]
AabbAdded --> Focus[Focus camera]
Focus --> CamCtrl[PanOrbitCamera]
Input[[Mouse & Scroll]] --> CamCtrl
CamCtrl --> Viewport[3D View]
LeftPanel --> Viewport
Current --> RightPanel[Right UI Panel\ncurrent asset details]
TerrainSettings[TerrainSettings resource] -->|change| ApplyTerrain[apply terrain settings]
ApplyTerrain --> TerrainMats[Terrain Materials]
SettingsInit --> FileArchiveMapInit --> StartArchiveTasks
SettingsInit --> DefaultSelect
StartArchiveTasks --> ArchiveTasks
UiSetup --> LeftPanel
CamSetup --> CamCtrl
- Replace static
Settingswith a reloadable Bevy resource (hot‑reload, thread safety, dynamic path changes). - File watching & incremental refresh of
ArchiveInfoMap/FileInfoMapwhen archives are added/removed. - Progressive / streaming world map loading with frustum or distance prioritization.
- Cached or pooled archive reads to amortize open/seek cost; optional memory mapping.
- Asset caching / LRU eviction to bound memory usage during long sessions.
- Smooth camera focus tween or eased dolly instead of instantaneous reposition.
- User‑configurable input mapping & gamepad support for camera/navigation.
- Expanded diagnostics (counts, memory estimates, per‑category timings) in a dedicated panel beyond raw inspector data.
- Failure & retry panel for individual asset load errors (manual retry + logs consolidation).
- Terrain material/shader upgrades: normal mapping, triplanar blend, texture arrays to reduce bind group churn.
- Parallel world model group loading progress visualization (incremental readiness feedback).
- Headless validation mode for CI (batch load assets, report failures, produce summary JSON).
- Mesh & material merging or instancing passes to reduce draw calls after load.
- Spatial culling structures (chunk / cell / BVH) for large scenes & maps.
- Incremental loading of embedded model/world model placements (prioritize camera‑proximate instances first).
- Skip allocation of alpha textures that are fully uniform (black/transparent) and reuse a shared handle.
- Store parsed model/world model metadata directly on components (instead of relying only on handles) for faster UI queries & modification.
- Graceful handling & warning (not fatal exit) for missing or malformed archives; hot reload on reappearance.