From 0798aa5d5724115b198d5ec2a2df57e051b49f60 Mon Sep 17 00:00:00 2001 From: Rbanh Date: Sun, 12 Jul 2026 00:24:06 -0400 Subject: [PATCH] Build renderer and material component foundations Add dedicated skinned rendering, pose restoration, shared Material and Material Instance slots, registry-driven components, Surface/Solari integration, transactional schema upgrades, navigation authoring, documentation, and evaluation evidence. --- .cargo/config.toml | 2 + .../navigation_authoring_2026-07-11.plan.md | 68 + ...al_component_foundation_2026-07-11.plan.md | 43 + .gitignore | 1 + Cargo.lock | 707 +++- Cargo.toml | 7 + README.md | 40 +- assets/.index/registry.ron | 230 ++ ...-e39c-41d4-9b5b-e48efe541f7f.animation.ron | 236 ++ ...-45eb-4cb2-8970-71921cbd7bd0.animation.ron | 236 ++ ...-3500-49e7-9935-f685fa9b2594.animation.ron | 20 + .../levels/audio_authoring_showcase.scn.ron | 46 +- assets/levels/editor_scene 2.scn.ron | 696 ++-- assets/levels/editor_scene 3.scn.ron | 760 ++-- assets/levels/editor_scene.scn.ron | 3481 ++++++++-------- .../navigation_authoring_showcase.scn.ron | 70 + assets/levels/rendering_showcase.scn.ron | 58 +- assets/materials/concrete.ron | 32 +- assets/materials/emissive_panel.ron | 31 +- assets/materials/surface_tint.ron | 71 + assets/materials/surface_tint_instance.ron | 12 + ...39c-41d4-9b5b-e48efe541f7f.static_mesh.ron | 360 ++ ...5eb-4cb2-8970-71921cbd7bd0.static_mesh.ron | 360 ++ ...500-49e7-9935-f685fa9b2594.static_mesh.ron | 5 +- assets/models/RobotExpressive.glb | 3 + .../navigation_showcase_humanoid.nav.ron | 963 +++++ assets/prefabs/example_base.scn.ron | 28 +- assets/prefabs/example_nested.scn.ron | 28 +- assets/prefabs/example_variant.scn.ron | 28 +- assets/shaders/blacksite_surface.wgsl | 42 + assets/shaders/standard_lit.shader.ron | 75 +- assets/shaders/surface_tint.shader.ron | 80 + assets/shaders/surface_tint.wgsl | 14 + assets/shaders/unlit.shader.ron | 34 +- crates/blacksite_surface/Cargo.toml | 13 + crates/blacksite_surface/src/lib.rs | 777 ++++ .../blacksite_surface/src/surface_footer.wgsl | 41 + .../blacksite_surface/src/surface_header.wgsl | 103 + crates/editor/Cargo.toml | 1 + crates/editor/src/assets/animation.rs | 67 +- crates/editor/src/assets/asset_db.rs | 10 + crates/editor/src/assets/catalog.rs | 370 +- crates/editor/src/assets/materials.rs | 110 +- crates/editor/src/assets/operators.rs | 52 +- crates/editor/src/assets/static_mesh.rs | 208 +- crates/editor/src/assets/thumbnails/studio.rs | 68 +- crates/editor/src/history/commands.rs | 57 +- crates/editor/src/history/mod.rs | 519 ++- crates/editor/src/play/session.rs | 17 +- crates/editor/src/project/settings_ui.rs | 6 +- crates/editor/src/scene/scene_io.rs | 160 +- crates/editor/src/ui/actor_inspector/mod.rs | 17 +- crates/editor/src/ui/animation_inspector.rs | 7 +- crates/editor/src/ui/asset_browser/panel.rs | 1172 +++++- crates/editor/src/ui/asset_browser/state.rs | 11 +- crates/editor/src/ui/component_registry.rs | 575 ++- crates/editor/src/ui/helpers.rs | 33 + crates/editor/src/ui/hierarchy_ops.rs | 13 +- crates/editor/src/ui/inspector.rs | 800 +++- crates/editor/src/ui/menu.rs | 38 + crates/editor/src/ui/mod.rs | 19 +- crates/editor/src/ui/navigation_inspector.rs | 765 ++++ crates/editor/src/ui/toolbar.rs | 19 + crates/editor/src/ui/viewport_chrome.rs | 4 +- crates/editor/src/viewport/actor_icons.rs | 22 +- crates/editor/src/viewport/brush_tool.rs | 11 + .../src/viewport/rendering_diagnostics.rs | 32 +- crates/editor/src/viewport/visualizers.rs | 137 +- crates/game/Cargo.toml | 2 + crates/game/src/animation.rs | 432 +- crates/game/src/audio.rs | 18 +- crates/game/src/lib.rs | 1 + crates/game/src/navigation.rs | 257 ++ crates/game_hot/Cargo.toml | 1 + crates/game_hot/src/rendering/solari.rs | 143 +- crates/game_hot/src/rendering/volumes.rs | 11 +- crates/game_hot/src/world/lighting.rs | 44 +- crates/scene/Cargo.toml | 3 + crates/scene/src/document.rs | 2 +- crates/scene/src/lib.rs | 21 +- crates/scene/src/migrate.rs | 88 + crates/scene/src/navigation.rs | 899 ++++ crates/scene/src/project_validation.rs | 703 +++- crates/scene/src/upgrade.rs | 450 ++ crates/shared/src/actor.rs | 287 +- crates/shared/src/animation.rs | 81 +- crates/shared/src/components.rs | 288 +- crates/shared/src/hydration/brushes.rs | 21 +- crates/shared/src/hydration/lights.rs | 43 +- crates/shared/src/hydration/materials.rs | 16 +- crates/shared/src/hydration/mod.rs | 141 +- crates/shared/src/hydration/physics.rs | 40 +- crates/shared/src/hydration/primitives.rs | 16 +- crates/shared/src/hydration/skinned_meshes.rs | 473 +++ crates/shared/src/hydration/static_meshes.rs | 271 +- crates/shared/src/lib.rs | 111 +- crates/shared/src/material_asset.rs | 215 +- crates/shared/src/navigation.rs | 181 + crates/shared/src/renderer_material.rs | 155 + docs/README.md | 11 + docs/adr/0009-authoring-vs-hydrated.md | 4 +- docs/adr/0010-actor-kind-and-inspector.md | 6 +- .../adr/0017-normalized-static-mesh-assets.md | 12 +- ...onentized-actor-inspector-and-materials.md | 9 +- docs/adr/0021-brush-authoring-schema.md | 4 +- ...31-animation-authoring-runtime-contract.md | 30 +- ...ioned-navigation-bake-and-runtime-query.md | 36 + .../0033-dedicated-skinned-mesh-renderer.md | 60 + ...34-registry-driven-authoring-components.md | 45 + ...ared-material-assets-and-renderer-slots.md | 60 + .../adr/0036-surface-abi-and-solari-parity.md | 72 + .../Bevy_Editor_Component_System_Spec.md | 32 + docs/editor/README.md | 22 +- docs/editor/animation-authoring.md | 42 +- docs/editor/architecture.md | 41 +- docs/editor/brp.md | 7 +- .../material-renderer-foundation/README.md | 96 + .../component-registry-add-menu.png | 3 + .../custom-surface-on-skinned-renderer.png | 3 + .../larger-material-thumbnail-previews.png | 3 + .../material-editor-create-instance.png | 3 + .../material-instance-sparse-overrides.png | 3 + .../mesh-light-component-composition.png | 3 + .../skinned-mesh-renderer-material-slots.png | 3 + docs/editor/extensibility.md | 49 + docs/editor/material-system.md | 149 + docs/editor/navigation-authoring.md | 66 + docs/editor/roadmap.md | 2 +- docs/mission.md | 4 +- third_party/bevy_solari/.cargo-ok | 1 + third_party/bevy_solari/.cargo_vcs_info.json | 6 + third_party/bevy_solari/BLACKSITE_PATCH.md | 24 + third_party/bevy_solari/Cargo.lock | 3602 +++++++++++++++++ third_party/bevy_solari/Cargo.toml | 159 + third_party/bevy_solari/Cargo.toml.orig | 55 + third_party/bevy_solari/LICENSE-APACHE | 176 + third_party/bevy_solari/LICENSE-MIT | 19 + third_party/bevy_solari/README.md | 20 + third_party/bevy_solari/src/lib.rs | 59 + .../bevy_solari/src/pathtracer/extract.rs | 36 + third_party/bevy_solari/src/pathtracer/mod.rs | 68 + .../bevy_solari/src/pathtracer/node.rs | 118 + .../src/pathtracer/pathtracer.wgsl | 118 + .../bevy_solari/src/pathtracer/prepare.rs | 47 + .../bevy_solari/src/realtime/extract.rs | 27 + .../src/realtime/gbuffer_utils.wgsl | 55 + third_party/bevy_solari/src/realtime/mod.rs | 112 + third_party/bevy_solari/src/realtime/node.rs | 597 +++ .../bevy_solari/src/realtime/prepare.rs | 324 ++ .../src/realtime/presample_light_tiles.wgsl | 43 + .../src/realtime/realtime_bindings.wgsl | 74 + .../realtime/resolve_dlss_rr_textures.wgsl | 59 + .../bevy_solari/src/realtime/restir_di.wgsl | 339 ++ .../bevy_solari/src/realtime/restir_gi.wgsl | 318 ++ .../bevy_solari/src/realtime/specular_gi.wgsl | 242 ++ .../src/realtime/world_cache_compact.wgsl | 86 + .../src/realtime/world_cache_query.wgsl | 148 + .../src/realtime/world_cache_update.wgsl | 129 + third_party/bevy_solari/src/scene/binder.rs | 589 +++ third_party/bevy_solari/src/scene/blas.rs | 186 + third_party/bevy_solari/src/scene/brdf.wgsl | 121 + third_party/bevy_solari/src/scene/extract.rs | 88 + third_party/bevy_solari/src/scene/mod.rs | 249 ++ .../src/scene/raytracing_scene_bindings.wgsl | 396 ++ .../bevy_solari/src/scene/sampling.wgsl | 251 ++ third_party/bevy_solari/src/scene/types.rs | 24 + xtask/Cargo.toml | 10 + xtask/src/bake_navigation.rs | 125 + xtask/src/upgrade_project.rs | 52 + 169 files changed, 27098 insertions(+), 3469 deletions(-) create mode 100644 .cursor/plans/navigation_authoring_2026-07-11.plan.md create mode 100644 .cursor/plans/renderer_material_component_foundation_2026-07-11.plan.md create mode 100644 assets/animations/generated/113f74df-e39c-41d4-9b5b-e48efe541f7f.animation.ron create mode 100644 assets/animations/generated/3f63f359-45eb-4cb2-8970-71921cbd7bd0.animation.ron create mode 100644 assets/animations/generated/b98ef565-3500-49e7-9935-f685fa9b2594.animation.ron create mode 100644 assets/levels/navigation_authoring_showcase.scn.ron create mode 100644 assets/materials/surface_tint.ron create mode 100644 assets/materials/surface_tint_instance.ron create mode 100644 assets/meshes/generated/113f74df-e39c-41d4-9b5b-e48efe541f7f.static_mesh.ron create mode 100644 assets/meshes/generated/3f63f359-45eb-4cb2-8970-71921cbd7bd0.static_mesh.ron create mode 100644 assets/models/RobotExpressive.glb create mode 100644 assets/navigation/generated/navigation_showcase_humanoid.nav.ron create mode 100644 assets/shaders/blacksite_surface.wgsl create mode 100644 assets/shaders/surface_tint.shader.ron create mode 100644 assets/shaders/surface_tint.wgsl create mode 100644 crates/blacksite_surface/Cargo.toml create mode 100644 crates/blacksite_surface/src/lib.rs create mode 100644 crates/blacksite_surface/src/surface_footer.wgsl create mode 100644 crates/blacksite_surface/src/surface_header.wgsl create mode 100644 crates/editor/src/ui/navigation_inspector.rs create mode 100644 crates/game/src/navigation.rs create mode 100644 crates/scene/src/navigation.rs create mode 100644 crates/scene/src/upgrade.rs create mode 100644 crates/shared/src/hydration/skinned_meshes.rs create mode 100644 crates/shared/src/navigation.rs create mode 100644 crates/shared/src/renderer_material.rs create mode 100644 docs/adr/0032-versioned-navigation-bake-and-runtime-query.md create mode 100644 docs/adr/0033-dedicated-skinned-mesh-renderer.md create mode 100644 docs/adr/0034-registry-driven-authoring-components.md create mode 100644 docs/adr/0035-shared-material-assets-and-renderer-slots.md create mode 100644 docs/adr/0036-surface-abi-and-solari-parity.md create mode 100644 docs/editor/evaluations/material-renderer-foundation/README.md create mode 100644 docs/editor/evaluations/material-renderer-foundation/component-registry-add-menu.png create mode 100644 docs/editor/evaluations/material-renderer-foundation/custom-surface-on-skinned-renderer.png create mode 100644 docs/editor/evaluations/material-renderer-foundation/larger-material-thumbnail-previews.png create mode 100644 docs/editor/evaluations/material-renderer-foundation/material-editor-create-instance.png create mode 100644 docs/editor/evaluations/material-renderer-foundation/material-instance-sparse-overrides.png create mode 100644 docs/editor/evaluations/material-renderer-foundation/mesh-light-component-composition.png create mode 100644 docs/editor/evaluations/material-renderer-foundation/skinned-mesh-renderer-material-slots.png create mode 100644 docs/editor/extensibility.md create mode 100644 docs/editor/material-system.md create mode 100644 docs/editor/navigation-authoring.md create mode 100644 third_party/bevy_solari/.cargo-ok create mode 100644 third_party/bevy_solari/.cargo_vcs_info.json create mode 100644 third_party/bevy_solari/BLACKSITE_PATCH.md create mode 100644 third_party/bevy_solari/Cargo.lock create mode 100644 third_party/bevy_solari/Cargo.toml create mode 100644 third_party/bevy_solari/Cargo.toml.orig create mode 100644 third_party/bevy_solari/LICENSE-APACHE create mode 100644 third_party/bevy_solari/LICENSE-MIT create mode 100644 third_party/bevy_solari/README.md create mode 100644 third_party/bevy_solari/src/lib.rs create mode 100644 third_party/bevy_solari/src/pathtracer/extract.rs create mode 100644 third_party/bevy_solari/src/pathtracer/mod.rs create mode 100644 third_party/bevy_solari/src/pathtracer/node.rs create mode 100644 third_party/bevy_solari/src/pathtracer/pathtracer.wgsl create mode 100644 third_party/bevy_solari/src/pathtracer/prepare.rs create mode 100644 third_party/bevy_solari/src/realtime/extract.rs create mode 100644 third_party/bevy_solari/src/realtime/gbuffer_utils.wgsl create mode 100644 third_party/bevy_solari/src/realtime/mod.rs create mode 100644 third_party/bevy_solari/src/realtime/node.rs create mode 100644 third_party/bevy_solari/src/realtime/prepare.rs create mode 100644 third_party/bevy_solari/src/realtime/presample_light_tiles.wgsl create mode 100644 third_party/bevy_solari/src/realtime/realtime_bindings.wgsl create mode 100644 third_party/bevy_solari/src/realtime/resolve_dlss_rr_textures.wgsl create mode 100644 third_party/bevy_solari/src/realtime/restir_di.wgsl create mode 100644 third_party/bevy_solari/src/realtime/restir_gi.wgsl create mode 100644 third_party/bevy_solari/src/realtime/specular_gi.wgsl create mode 100644 third_party/bevy_solari/src/realtime/world_cache_compact.wgsl create mode 100644 third_party/bevy_solari/src/realtime/world_cache_query.wgsl create mode 100644 third_party/bevy_solari/src/realtime/world_cache_update.wgsl create mode 100644 third_party/bevy_solari/src/scene/binder.rs create mode 100644 third_party/bevy_solari/src/scene/blas.rs create mode 100644 third_party/bevy_solari/src/scene/brdf.wgsl create mode 100644 third_party/bevy_solari/src/scene/extract.rs create mode 100644 third_party/bevy_solari/src/scene/mod.rs create mode 100644 third_party/bevy_solari/src/scene/raytracing_scene_bindings.wgsl create mode 100644 third_party/bevy_solari/src/scene/sampling.wgsl create mode 100644 third_party/bevy_solari/src/scene/types.rs create mode 100644 xtask/src/bake_navigation.rs create mode 100644 xtask/src/upgrade_project.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 100f9f5..d6822a4 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -7,4 +7,6 @@ rustflags = ["-C", "link-arg=-fuse-ld=mold"] [alias] clean-target = "run -p xtask --bin clean-target --" validate-levels = "run -p xtask --features validate-levels --bin validate-levels --" +bake-navigation = "run -p xtask --features validate-levels --bin bake-navigation --" package-project = "run -p xtask --features validate-levels --bin package-project --" +upgrade-project = "run -p xtask --features validate-levels --bin upgrade-project --" diff --git a/.cursor/plans/navigation_authoring_2026-07-11.plan.md b/.cursor/plans/navigation_authoring_2026-07-11.plan.md new file mode 100644 index 0000000..83807ab --- /dev/null +++ b/.cursor/plans/navigation_authoring_2026-07-11.plan.md @@ -0,0 +1,68 @@ +# Navigation Mesh Authoring, Bake Diagnostics, And Path Preview + +Working implementation plan for Gitea issue +[`#48`](https://git.spacetrainclubhouse.com/Falling-Metal-Interactive/Blacksite/issues/48). +This is the navigation production loop required by the M7 content-production milestone. + +## Status + +Architecture and dependency compatibility are established. Implementation and production +acceptance are in progress. + +## Outcome + +A level designer can author navigation bounds, obstacles, areas, links, and agent profiles; bake a +deterministic versioned artifact; inspect stale or invalid data; preview a path in the viewport; and +use the same artifact through a game-owned runtime query API and headless release validation. + +## Architecture + +- Shared reflected components own bounds, obstacles, area volumes, links, and scene bake settings. +- The scene crate owns deterministic source fingerprinting, bake artifact IO, and validation. +- Rerecast performs the 3D walkable-surface bake without coupling navigation to a Bevy plugin + release. Polyanya performs proven any-angle runtime and editor-preview path queries. +- Overlapping primitive and additive-brush triangles enter the deterministic bake source; distant + geometry and navigation authoring are excluded from each bounds fingerprint. +- The game crate owns loading and querying baked artifacts. Editor UI calls that public API for + preview rather than implementing a second pathfinder. +- Generated artifacts live under `assets/navigation/generated/`, are project-relative runtime + dependencies, and never serialize viewport helpers into authored scenes. + +## Implementation Sequence + +1. Add reflected authoring schema, versioned bake artifact, source fingerprint, validation, and + exact agent-profile settings. +2. Add deterministic Rerecast bake input extraction for authored bounds/obstacles and headless bake + command coverage. +3. Add game-owned artifact loading and Polyanya path-query API, including authored off-mesh links. +4. Add typed inspectors, create commands, bake/stale status, viewport overlay, and start/end path + preview. +5. Add owner-attributed project validation for invalid links, missing/stale artifacts, unreachable + preview samples, and bake failures. +6. Commit a small sample scene/artifact and run automated, headless, live editor, PIE, and packaged + runtime acceptance. + +## Acceptance Gates + +- A designer can create bounds and obstacles, bake, view the resulting mesh, and preview a valid + start-to-end path. +- Agent radius, height, climb, and slope settings deterministically affect the bake fingerprint. +- Relevant geometry or navigation-authoring changes mark the artifact stale; unrelated scene + metadata does not. +- Invalid or dangling links, samples outside the mesh, isolated regions, and bake failures identify + the owning actor or setting and provide a repair action. +- Runtime path queries and editor preview use the same game-owned API and versioned artifact. +- Helper meshes, lines, endpoints, and bake state are transient and never serialize. +- Headless bake and project validation pass for the committed sample fixture, and package dependency + collection includes the current artifact. + +## Deliberate Boundaries + +- V1 supports static baked navigation plus explicit links. Dynamic crowd avoidance and runtime tile + carving are separate work. +- Full partial-tile regeneration waits for upstream Rerecast tiling support. V1 records affected + source bounds and debounces a deterministic full bake while reporting the dirty region. +- Area costs are authored and validated in V1; path-cost weighting beyond walkable/blocked areas is + deferred until the runtime exposes per-polygon cost callbacks. +- Imported static meshes remain obstacle-authored in V1 because their normalized manifests do not + yet expose deterministic collision triangles to the headless scene crate. diff --git a/.cursor/plans/renderer_material_component_foundation_2026-07-11.plan.md b/.cursor/plans/renderer_material_component_foundation_2026-07-11.plan.md new file mode 100644 index 0000000..a16352b --- /dev/null +++ b/.cursor/plans/renderer_material_component_foundation_2026-07-11.plan.md @@ -0,0 +1,43 @@ +# Renderer, material, and component foundation + +## Goal + +Separate static and skinned renderer ownership, restore deterministic skinned edit poses, add shared +material/material-instance slots, and make authoring components registry-driven and composable. + +## Component foundation milestone + +- Give authoring components immutable IDs distinct from Rust type paths. +- Split persisted active state from inspector ordering with legacy-state migration. +- Route presence, persistence, add/remove/reset/copy/paste, and component history through Bevy + reflection and atomic transactions. +- Treat `ActorKind` as a derived compatibility/display hint; validate component data and explicit + requirements/conflicts. +- Provide one statically linked extension registration helper and reflection round-trip fixture. +- Verify registry integrity, transaction undo/redo, scene save inclusion, composition, and legacy + enable-state preservation. + +## Related renderer/material milestone + +- Keep `StaticMeshRenderer` and `SkinnedMeshRenderer` as separate authoring and hydration paths. +- Give both renderer types stable imported-source/explicit Material slots and preserve orphaned + assignments by ID across source changes. +- Add versioned Material and direct-base Material Instance assets, sparse editor overrides, shared + handle live refresh, and runtime-only property blocks. +- Capture and restore imported Transform/MorphWeights baselines; sample only the explicitly chosen + default edit clip and never reload a rig for a material-only change. +- Compose one validated Surface evaluator into raster and Solari, including normal/emissive/unlit + fields and exact cutout candidate acceptance. Safely omit skinned/morph geometry from Solari until + instance-owned deformed vertices and dynamic BLAS updates exist. +- Upgrade scenes/materials explicitly to schema v4 with dry-run, staged writes, backup, rollback, + stable slot normalization, and active-state separation. + +## Acceptance + +- Shared, scene, game, game-hot, editor, Surface, and maintained Solari-fork tests pass. +- `cargo --locked validate-levels` reports zero blocking findings. +- A second project-upgrade dry run reports no changes after apply. +- Editor evidence shows the stable component cards, static/skinned material slots, Material Instance + workflow, deterministic animated actor pose, and Surface/Solari diagnostics. +- Gitea implementation issue and related roadmap issues link the documentation and screenshot + evidence without claiming unsupported dynamic deformed BLAS behavior. diff --git a/.gitignore b/.gitignore index 83d8053..8a93262 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ # Editor/runtime generated session state /assets/levels/.pie_session.scn.ron /assets/.trash/ +/.blacksite/backups/ # Backup, temporary, and log files **/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock index 57c8a65..a9a9dd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -478,12 +478,12 @@ dependencies = [ "bevy_mesh", "bevy_mikktspace", "bevy_pbr", - "bevy_platform", - "bevy_reflect", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", "bevy_render", "bevy_state", "bevy_time", - "bevy_utils", + "bevy_utils 0.19.0", "bevy_window", "bytemuck", "disqualified", @@ -517,7 +517,7 @@ dependencies = [ "bevy_app", "bevy_derive", "bevy_ecs", - "bevy_reflect", + "bevy_reflect 0.19.0", "serde", ] @@ -544,11 +544,11 @@ dependencies = [ "bevy_ecs", "bevy_math", "bevy_mesh", - "bevy_platform", - "bevy_reflect", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", "bevy_time", "bevy_transform", - "bevy_utils", + "bevy_utils 0.19.0", "blake3", "derive_more 2.1.1", "downcast-rs 2.0.2", @@ -569,7 +569,7 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fa3d816d7788e27da936b9c7ec27f9828906d53cb1d06e26c58cac5c5ee799f" dependencies = [ - "bevy_macro_utils", + "bevy_macro_utils 0.19.0", "quote", "syn", ] @@ -589,10 +589,10 @@ dependencies = [ "bevy_ecs", "bevy_image", "bevy_math", - "bevy_reflect", + "bevy_reflect 0.19.0", "bevy_render", "bevy_shader", - "bevy_utils", + "bevy_utils 0.19.0", "tracing", ] @@ -604,10 +604,10 @@ checksum = "671ae6f3a8d84f9ed696c531a138558596e041231a8414b6efeafa8469e841f9" dependencies = [ "bevy_derive", "bevy_ecs", - "bevy_platform", - "bevy_reflect", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", "bevy_tasks", - "bevy_utils", + "bevy_utils 0.19.0", "console_error_panic_hook", "ctrlc", "downcast-rs 2.0.2", @@ -635,10 +635,10 @@ dependencies = [ "bevy_asset_macros", "bevy_diagnostic", "bevy_ecs", - "bevy_platform", - "bevy_reflect", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", "bevy_tasks", - "bevy_utils", + "bevy_utils 0.19.0", "bitflags 2.13.0", "blake3", "crossbeam-channel", @@ -667,7 +667,7 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9444998cc37b51dfd1572c23a501865733ea65619a7d88b47aa654ae2290a4f" dependencies = [ - "bevy_macro_utils", + "bevy_macro_utils 0.19.0", "proc-macro2", "quote", "syn", @@ -683,7 +683,7 @@ dependencies = [ "bevy_asset", "bevy_ecs", "bevy_math", - "bevy_reflect", + "bevy_reflect 0.19.0", "bevy_transform", "rodio", "tracing", @@ -703,16 +703,16 @@ dependencies = [ "bevy_image", "bevy_math", "bevy_mesh", - "bevy_reflect", + "bevy_reflect 0.19.0", "bevy_transform", - "bevy_utils", + "bevy_utils 0.19.0", "bevy_window", "derive_more 2.1.1", "downcast-rs 2.0.2", "serde", "smallvec", "thiserror 2.0.18", - "wgpu-types", + "wgpu-types 29.0.3", ] [[package]] @@ -724,7 +724,7 @@ dependencies = [ "bevy_app", "bevy_ecs", "bevy_log", - "bevy_platform", + "bevy_platform 0.19.0", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -737,13 +737,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e2dfc0091be6eb62fec9158b2d3e6f21b002be7bdb42dd3b8322e949c8426b0" dependencies = [ "bevy_math", - "bevy_reflect", + "bevy_reflect 0.19.0", "bytemuck", "derive_more 2.1.1", "encase", "serde", "thiserror 2.0.18", - "wgpu-types", + "wgpu-types 29.0.3", ] [[package]] @@ -763,12 +763,12 @@ dependencies = [ "bevy_light", "bevy_log", "bevy_math", - "bevy_platform", - "bevy_reflect", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", "bevy_render", "bevy_shader", "bevy_transform", - "bevy_utils", + "bevy_utils 0.19.0", "bevy_window", "bitflags 2.13.0", "indexmap", @@ -781,7 +781,7 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "293df2b2f7ed65ef2ce02b2e7359faac1a9a92a8cc96c182c9de77eb06049ae0" dependencies = [ - "bevy_macro_utils", + "bevy_macro_utils 0.19.0", "quote", "syn", ] @@ -805,8 +805,8 @@ dependencies = [ "bevy_math", "bevy_pbr", "bevy_picking", - "bevy_platform", - "bevy_reflect", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", "bevy_render", "bevy_shader", "bevy_state", @@ -815,7 +815,7 @@ dependencies = [ "bevy_transform", "bevy_ui", "bevy_ui_render", - "bevy_utils", + "bevy_utils 0.19.0", "bevy_window", "ron 0.12.1", "serde", @@ -832,7 +832,7 @@ dependencies = [ "atomic-waker", "bevy_app", "bevy_ecs", - "bevy_platform", + "bevy_platform 0.19.0", "bevy_tasks", "bevy_time", "const-fnv1a-hash", @@ -858,11 +858,11 @@ checksum = "4654b9b3535fa7813d2878a50e1b5ad80a361dc1c913b96ded57667f65d70a01" dependencies = [ "arrayvec", "bevy_ecs_macros", - "bevy_platform", - "bevy_ptr", - "bevy_reflect", + "bevy_platform 0.19.0", + "bevy_ptr 0.19.0", + "bevy_reflect 0.19.0", "bevy_tasks", - "bevy_utils", + "bevy_utils 0.19.0", "bitflags 2.13.0", "bumpalo", "concurrent-queue", @@ -884,7 +884,7 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee0b98a74141ce8dce4654c60020ebbaefab32646f42af6bbae55f282ae65ff7" dependencies = [ - "bevy_macro_utils", + "bevy_macro_utils 0.19.0", "proc-macro2", "quote", "syn", @@ -897,7 +897,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dc958a194d226d618404e0fea99a3c8b4146207a00a3ba07fa712bf71607240" dependencies = [ "bevy_ecs_macro_logic", - "bevy_macro_utils", + "bevy_macro_utils 0.19.0", "proc-macro2", "quote", "syn", @@ -923,14 +923,14 @@ dependencies = [ "bevy_math", "bevy_mesh", "bevy_picking", - "bevy_platform", - "bevy_reflect", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", "bevy_render", "bevy_shader", "bevy_time", "bevy_transform", "bevy_ui_render", - "bevy_utils", + "bevy_utils 0.19.0", "bevy_window", "bevy_winit", "bytemuck", @@ -947,7 +947,7 @@ dependencies = [ "wasm-bindgen-futures", "web-sys", "webbrowser", - "wgpu-types", + "wgpu-types 29.0.3", "winit", ] @@ -957,7 +957,7 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14dc5bf9e6cb46c4401a66625b5f7eb9654a7f3c8586423c428b79586a1c55cd" dependencies = [ - "bevy_macro_utils", + "bevy_macro_utils 0.19.0", "encase_derive_impl", ] @@ -980,8 +980,8 @@ dependencies = [ "bevy_log", "bevy_math", "bevy_picking", - "bevy_platform", - "bevy_reflect", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", "bevy_render", "bevy_scene", "bevy_shader", @@ -1002,7 +1002,7 @@ dependencies = [ "bevy_app", "bevy_ecs", "bevy_input", - "bevy_platform", + "bevy_platform 0.19.0", "bevy_time", "gilrs", "thiserror 2.0.18", @@ -1025,10 +1025,10 @@ dependencies = [ "bevy_log", "bevy_math", "bevy_mesh", - "bevy_reflect", + "bevy_reflect 0.19.0", "bevy_time", "bevy_transform", - "bevy_utils", + "bevy_utils 0.19.0", "bevy_window", ] @@ -1038,7 +1038,7 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9903f2614f53bacacb92bef1e407afe3a27a4abddb500bfd479909c5d7b254df" dependencies = [ - "bevy_macro_utils", + "bevy_macro_utils 0.19.0", "quote", "syn", ] @@ -1062,12 +1062,12 @@ dependencies = [ "bevy_math", "bevy_mesh", "bevy_pbr", - "bevy_reflect", + "bevy_reflect 0.19.0", "bevy_render", "bevy_shader", "bevy_sprite_render", "bevy_transform", - "bevy_utils", + "bevy_utils 0.19.0", "bytemuck", "tracing", ] @@ -1091,8 +1091,8 @@ dependencies = [ "bevy_material", "bevy_math", "bevy_mesh", - "bevy_platform", - "bevy_reflect", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", "bevy_tasks", "bevy_transform", "bevy_world_serialization", @@ -1105,7 +1105,7 @@ dependencies = [ "smallvec", "thiserror 2.0.18", "tracing", - "wgpu-types", + "wgpu-types 29.0.3", ] [[package]] @@ -1115,7 +1115,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afbd898e2c06dfba5854db187a3daece1eaf655c8fc385604bbe5b9304ed3f9b" dependencies = [ "bevy_math", - "bevy_reflect", + "bevy_reflect 0.19.0", "glam_matrix_extras", "serde", ] @@ -1131,9 +1131,9 @@ dependencies = [ "bevy_color", "bevy_ecs", "bevy_math", - "bevy_platform", - "bevy_reflect", - "bevy_utils", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", + "bevy_utils 0.19.0", "bitflags 2.13.0", "bytemuck", "futures-lite", @@ -1146,7 +1146,7 @@ dependencies = [ "serde", "thiserror 2.0.18", "tracing", - "wgpu-types", + "wgpu-types 29.0.3", ] [[package]] @@ -1158,8 +1158,8 @@ dependencies = [ "bevy_app", "bevy_ecs", "bevy_math", - "bevy_platform", - "bevy_reflect", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", "derive_more 2.1.1", "log", "serde", @@ -1178,7 +1178,7 @@ dependencies = [ "bevy_input", "bevy_math", "bevy_picking", - "bevy_reflect", + "bevy_reflect 0.19.0", "bevy_window", "log", "thiserror 2.0.18", @@ -1220,10 +1220,10 @@ dependencies = [ "bevy_mesh", "bevy_pbr", "bevy_picking", - "bevy_platform", + "bevy_platform 0.19.0", "bevy_post_process", - "bevy_ptr", - "bevy_reflect", + "bevy_ptr 0.19.0", + "bevy_reflect 0.19.0", "bevy_remote", "bevy_render", "bevy_scene", @@ -1238,7 +1238,7 @@ dependencies = [ "bevy_ui", "bevy_ui_render", "bevy_ui_widgets", - "bevy_utils", + "bevy_utils 0.19.0", "bevy_window", "bevy_winit", "bevy_world_serialization", @@ -1260,14 +1260,14 @@ dependencies = [ "bevy_log", "bevy_math", "bevy_mesh", - "bevy_platform", - "bevy_reflect", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", "bevy_transform", - "bevy_utils", + "bevy_utils 0.19.0", "half", "smallvec", "tracing", - "wgpu-types", + "wgpu-types 29.0.3", ] [[package]] @@ -1279,8 +1279,8 @@ dependencies = [ "android_log-sys", "bevy_app", "bevy_ecs", - "bevy_platform", - "bevy_utils", + "bevy_platform 0.19.0", + "bevy_utils 0.19.0", "tracing", "tracing-log", "tracing-oslog", @@ -1288,6 +1288,18 @@ dependencies = [ "tracing-wasm", ] +[[package]] +name = "bevy_macro_utils" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b147843b81a7ec548876ff97fa7bfdc646ef2567cb465566259237b39664438" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "toml_edit 0.23.10+spec-1.0.0", +] + [[package]] name = "bevy_macro_utils" version = "0.19.0" @@ -1297,7 +1309,7 @@ dependencies = [ "proc-macro2", "quote", "syn", - "toml_edit", + "toml_edit 0.25.12+spec-1.1.0", ] [[package]] @@ -1311,16 +1323,16 @@ dependencies = [ "bevy_ecs", "bevy_material_macros", "bevy_mesh", - "bevy_platform", - "bevy_reflect", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", "bevy_shader", - "bevy_utils", + "bevy_utils 0.19.0", "encase", "smallvec", "thiserror 2.0.18", "tracing", "variadics_please", - "wgpu-types", + "wgpu-types 29.0.3", ] [[package]] @@ -1329,7 +1341,7 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6928e167592472f6ee900f80de199cc2dafd31d4f082a86e8d594b81b973e3d" dependencies = [ - "bevy_macro_utils", + "bevy_macro_utils 0.19.0", "quote", "syn", ] @@ -1342,12 +1354,12 @@ checksum = "d7c280440d2a5bc07ea393b5346b5712eec73ea30f0133097b8b13dade385beb" dependencies = [ "approx", "arrayvec", - "bevy_reflect", + "bevy_reflect 0.19.0", "derive_more 2.1.1", "glam 0.32.1", "itertools 0.14.0", "libm", - "rand", + "rand 0.10.1", "rand_distr", "serde", "thiserror 2.0.18", @@ -1367,8 +1379,8 @@ dependencies = [ "bevy_encase_derive", "bevy_math", "bevy_mikktspace", - "bevy_platform", - "bevy_reflect", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", "bevy_transform", "bitflags 2.13.0", "bytemuck", @@ -1380,7 +1392,7 @@ dependencies = [ "serde", "thiserror 2.0.18", "tracing", - "wgpu-types", + "wgpu-types 29.0.3", ] [[package]] @@ -1411,13 +1423,13 @@ dependencies = [ "bevy_material", "bevy_math", "bevy_mesh", - "bevy_platform", - "bevy_reflect", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", "bevy_render", "bevy_shader", "bevy_tasks", "bevy_transform", - "bevy_utils", + "bevy_utils 0.19.0", "bitflags 2.13.0", "bytemuck", "derive_more 2.1.1", @@ -1429,7 +1441,7 @@ dependencies = [ "static_assertions", "thiserror 2.0.18", "tracing", - "wgpu-types", + "wgpu-types 29.0.3", ] [[package]] @@ -1446,8 +1458,8 @@ dependencies = [ "bevy_input", "bevy_math", "bevy_mesh", - "bevy_platform", - "bevy_reflect", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", "bevy_time", "bevy_transform", "bevy_window", @@ -1456,6 +1468,25 @@ dependencies = [ "uuid", ] +[[package]] +name = "bevy_platform" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec6b36504169b644acd26a5469fd8d371aa6f1d73ee5c01b1b1181ae1cefbf9b" +dependencies = [ + "critical-section", + "foldhash 0.2.0", + "futures-channel", + "hashbrown 0.16.1", + "js-sys", + "portable-atomic", + "portable-atomic-util", + "serde", + "spin", + "wasm-bindgen", + "wasm-bindgen-futures", +] + [[package]] name = "bevy_platform" version = "0.19.0" @@ -1494,22 +1525,53 @@ dependencies = [ "bevy_ecs", "bevy_image", "bevy_math", - "bevy_platform", - "bevy_reflect", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", "bevy_render", "bevy_shader", - "bevy_utils", + "bevy_utils 0.19.0", "smallvec", "thiserror 2.0.18", "tracing", ] +[[package]] +name = "bevy_ptr" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a9329e8dc4e01ced480eeec4902e6d7cb56e56ec37f6fbc4323e5c937290a7" + [[package]] name = "bevy_ptr" version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b511e89f7078fd21fdea77061a0ca3e737297c7011bb10c073e0aecb17c9fea6" +[[package]] +name = "bevy_reflect" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1dfeb67a9fe4f59003a84f5f99ba6302141c70e926601cbc6abfd4a1eea9ca9" +dependencies = [ + "assert_type_match", + "bevy_platform 0.18.1", + "bevy_ptr 0.18.1", + "bevy_reflect_derive 0.18.1", + "bevy_utils 0.18.1", + "derive_more 2.1.1", + "disqualified", + "downcast-rs 2.0.2", + "erased-serde", + "foldhash 0.2.0", + "glam 0.30.10", + "serde", + "smol_str", + "thiserror 2.0.18", + "uuid", + "variadics_please", + "wgpu-types 27.0.1", +] + [[package]] name = "bevy_reflect" version = "0.19.0" @@ -1517,10 +1579,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92abd59cbba1524c1847e9a50f74d94e6b7836c80a8edfa9c47e59f7fd81021d" dependencies = [ "assert_type_match", - "bevy_platform", - "bevy_ptr", - "bevy_reflect_derive", - "bevy_utils", + "bevy_platform 0.19.0", + "bevy_ptr 0.19.0", + "bevy_reflect_derive 0.19.0", + "bevy_utils 0.19.0", "derive_more 2.1.1", "disqualified", "downcast-rs 2.0.2", @@ -1536,7 +1598,21 @@ dependencies = [ "thiserror 2.0.18", "uuid", "variadics_please", - "wgpu-types", + "wgpu-types 29.0.3", +] + +[[package]] +name = "bevy_reflect_derive" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "475f68c93e9cd5f17e9167635c8533a4f388f12d38245a202359e4c2721d87ba" +dependencies = [ + "bevy_macro_utils 0.18.1", + "indexmap", + "proc-macro2", + "quote", + "syn", + "uuid", ] [[package]] @@ -1545,7 +1621,7 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90fa4e6b97fd2d582dd5ed96762a70d04505a477c833cf4f69d016dcd03d2d04" dependencies = [ - "bevy_macro_utils", + "bevy_macro_utils 0.19.0", "indexmap", "proc-macro2", "quote", @@ -1569,11 +1645,11 @@ dependencies = [ "bevy_dev_tools", "bevy_ecs", "bevy_log", - "bevy_platform", - "bevy_reflect", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", "bevy_render", "bevy_tasks", - "bevy_utils", + "bevy_utils 0.19.0", "http-body-util", "hyper", "serde", @@ -1602,14 +1678,14 @@ dependencies = [ "bevy_material_macros", "bevy_math", "bevy_mesh", - "bevy_platform", - "bevy_reflect", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", "bevy_render_macros", "bevy_shader", "bevy_tasks", "bevy_time", "bevy_transform", - "bevy_utils", + "bevy_utils 0.19.0", "bevy_window", "bitflags 2.13.0", "bytemuck", @@ -1632,7 +1708,7 @@ dependencies = [ "weak-table", "web-sys", "wgpu", - "wgpu-types", + "wgpu-types 29.0.3", ] [[package]] @@ -1641,7 +1717,7 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0eb5cffe5253a6ab4e4b84b2e40a6a2f4673deb16fd63ab9dfc4a015449873a" dependencies = [ - "bevy_macro_utils", + "bevy_macro_utils 0.19.0", "proc-macro2", "quote", "syn", @@ -1658,10 +1734,10 @@ dependencies = [ "bevy_derive", "bevy_ecs", "bevy_log", - "bevy_platform", - "bevy_reflect", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", "bevy_scene_macros", - "bevy_utils", + "bevy_utils 0.19.0", "smallvec", "thiserror 2.0.18", "tracing", @@ -1675,7 +1751,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4bc33a36a4e9e344985a96cd8b4abc816cf2c5d2fa002d72fd7bcd11396589c" dependencies = [ "bevy_ecs_macro_logic", - "bevy_macro_utils", + "bevy_macro_utils 0.19.0", "proc-macro2", "quote", "syn", @@ -1688,25 +1764,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa0ac51378bb6a021165ee334f846b515cd06082e24ce962adce58ad7df1c39a" dependencies = [ "bevy_asset", - "bevy_platform", - "bevy_reflect", - "bevy_utils", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", + "bevy_utils 0.19.0", "naga", "naga_oil", "serde", "thiserror 2.0.18", "tracing", "wgpu-naga-bridge", - "wgpu-types", + "wgpu-types 29.0.3", ] [[package]] name = "bevy_solari" version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2edac80e47ba799a747eb47c3dff6f9a46a85cf88bb0ceaad8ab5a5c6afdea" dependencies = [ - "bevy_anti_alias", "bevy_app", "bevy_asset", "bevy_camera", @@ -1716,15 +1789,17 @@ dependencies = [ "bevy_diagnostic", "bevy_ecs", "bevy_image", + "bevy_material", "bevy_math", "bevy_mesh", "bevy_pbr", - "bevy_platform", - "bevy_reflect", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", "bevy_render", "bevy_shader", "bevy_transform", - "bevy_utils", + "bevy_utils 0.19.0", + "blacksite_surface", "bytemuck", "derive_more 2.1.1", "tracing", @@ -1747,13 +1822,13 @@ dependencies = [ "bevy_math", "bevy_mesh", "bevy_picking", - "bevy_reflect", + "bevy_reflect 0.19.0", "bevy_text", "bevy_transform", "bevy_window", "radsort", "tracing", - "wgpu-types", + "wgpu-types 29.0.3", ] [[package]] @@ -1773,14 +1848,14 @@ dependencies = [ "bevy_material", "bevy_math", "bevy_mesh", - "bevy_platform", - "bevy_reflect", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", "bevy_render", "bevy_shader", "bevy_sprite", "bevy_text", "bevy_transform", - "bevy_utils", + "bevy_utils 0.19.0", "bitflags 2.13.0", "bytemuck", "derive_more 2.1.1", @@ -1797,10 +1872,10 @@ checksum = "8c22e694ea52e42994aea6ed2d0b378fa1e9d5a9b615db88626e33508bc03f35" dependencies = [ "bevy_app", "bevy_ecs", - "bevy_platform", - "bevy_reflect", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", "bevy_state_macros", - "bevy_utils", + "bevy_utils 0.19.0", "log", "variadics_please", ] @@ -1811,7 +1886,7 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835f45091fa0df1ff3cb10f4ca5a397d4e9249b550f1be6893b08a2a06d6eed7" dependencies = [ - "bevy_macro_utils", + "bevy_macro_utils 0.19.0", "quote", "syn", ] @@ -1826,7 +1901,7 @@ dependencies = [ "async-executor", "async-task", "atomic-waker", - "bevy_platform", + "bevy_platform 0.19.0", "concurrent-queue", "crossbeam-queue", "derive_more 2.1.1", @@ -1850,9 +1925,9 @@ dependencies = [ "bevy_image", "bevy_log", "bevy_math", - "bevy_platform", - "bevy_reflect", - "bevy_utils", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", + "bevy_utils 0.19.0", "parley", "serde", "smallvec", @@ -1861,7 +1936,7 @@ dependencies = [ "sys-locale", "thiserror 2.0.18", "tracing", - "wgpu-types", + "wgpu-types 29.0.3", ] [[package]] @@ -1872,8 +1947,8 @@ checksum = "c21e3be2aa4426ea1e0a7036d3d1dbbded84c319459d9f3e2a616b33563372f2" dependencies = [ "bevy_app", "bevy_ecs", - "bevy_platform", - "bevy_reflect", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", "crossbeam-channel", "log", "serde", @@ -1888,9 +1963,9 @@ dependencies = [ "bevy_app", "bevy_ecs", "bevy_math", - "bevy_reflect", + "bevy_reflect 0.19.0", "bevy_tasks", - "bevy_utils", + "bevy_utils 0.19.0", "derive_more 2.1.1", "serde", "thiserror 2.0.18", @@ -1938,13 +2013,13 @@ dependencies = [ "bevy_log", "bevy_math", "bevy_picking", - "bevy_platform", - "bevy_reflect", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", "bevy_sprite", "bevy_text", "bevy_time", "bevy_transform", - "bevy_utils", + "bevy_utils 0.19.0", "bevy_window", "derive_more 2.1.1", "parley", @@ -1974,8 +2049,8 @@ dependencies = [ "bevy_input_focus", "bevy_math", "bevy_mesh", - "bevy_platform", - "bevy_reflect", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", "bevy_render", "bevy_shader", "bevy_sprite", @@ -1983,7 +2058,7 @@ dependencies = [ "bevy_text", "bevy_transform", "bevy_ui", - "bevy_utils", + "bevy_utils 0.19.0", "bytemuck", "derive_more 2.1.1", "indexmap", @@ -2006,7 +2081,7 @@ dependencies = [ "bevy_log", "bevy_math", "bevy_picking", - "bevy_reflect", + "bevy_reflect 0.19.0", "bevy_text", "bevy_ui", "bevy_window", @@ -2014,6 +2089,16 @@ dependencies = [ "smol_str", ] +[[package]] +name = "bevy_utils" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2111910cd7a4b1e6ce07eaaeb6f68a2c0ea0ca609ed0d0d506e3eb161101435b" +dependencies = [ + "bevy_platform 0.18.1", + "disqualified", +] + [[package]] name = "bevy_utils" version = "0.19.0" @@ -2021,7 +2106,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3aceea6c7ffdd568f866d5ca4dfe50c33440c54f7bc230c13a9ba7ab0c91aed1" dependencies = [ "async-channel", - "bevy_platform", + "bevy_platform 0.19.0", "disqualified", "indexmap", "thread_local", @@ -2039,8 +2124,8 @@ dependencies = [ "bevy_image", "bevy_input", "bevy_math", - "bevy_platform", - "bevy_reflect", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", "log", "raw-window-handle", "serde", @@ -2066,8 +2151,8 @@ dependencies = [ "bevy_input_focus", "bevy_log", "bevy_math", - "bevy_platform", - "bevy_reflect", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", "bevy_tasks", "bevy_window", "bytemuck", @@ -2075,7 +2160,7 @@ dependencies = [ "tracing", "wasm-bindgen", "web-sys", - "wgpu-types", + "wgpu-types 29.0.3", "winit", ] @@ -2090,10 +2175,10 @@ dependencies = [ "bevy_camera", "bevy_derive", "bevy_ecs", - "bevy_platform", - "bevy_reflect", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", "bevy_transform", - "bevy_utils", + "bevy_utils 0.19.0", "derive_more 2.1.1", "ron 0.12.1", "serde", @@ -2132,6 +2217,17 @@ dependencies = [ "serde_core", ] +[[package]] +name = "blacksite_surface" +version = "0.1.0" +dependencies = [ + "bevy", + "naga", + "ron 0.8.1", + "shared", + "uuid", +] + [[package]] name = "blake3" version = "1.8.5" @@ -2204,6 +2300,16 @@ version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" +[[package]] +name = "bvh2d" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2a56b2e919a2fadaedb1fc194ca730d9adbf030e9e9f26bb9e7d3621c4f6622" +dependencies = [ + "glam 0.30.10", + "serde", +] + [[package]] name = "bytemuck" version = "1.25.0" @@ -2931,6 +3037,16 @@ dependencies = [ "proc-macro2-diagnostics", ] +[[package]] +name = "earcutr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79127ed59a85d7687c409e9978547cffb7dc79675355ed22da6b66fd5f6ead01" +dependencies = [ + "itertools 0.11.0", + "num-traits", +] + [[package]] name = "ecolor" version = "0.34.3" @@ -2960,6 +3076,7 @@ dependencies = [ "gltf", "hot-lib-reloader", "notify", + "polyanya", "protocol", "rfd", "ron 0.8.1", @@ -3288,6 +3405,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "float_next_after" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" + [[package]] name = "fnv" version = "1.0.7" @@ -3457,7 +3580,9 @@ dependencies = [ "bevy_ufbx", "cpal", "game_hot", + "glam 0.30.10", "hot-lib-reloader", + "polyanya", "protocol", "ron 0.12.1", "scene", @@ -3475,12 +3600,55 @@ dependencies = [ "bevy", "bevy_core_pipeline", "bevy_solari", + "blacksite_surface", "protocol", "settings", "shared", "sim", ] +[[package]] +name = "geo" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3901269ec6d4f6068d3f09e5f02f995bd076398dcd1dfec407cd230b02d11b" +dependencies = [ + "earcutr", + "float_next_after", + "geo-types", + "geographiclib-rs", + "i_overlay", + "log", + "num-traits", + "rand 0.8.7", + "robust", + "rstar", + "sif-itree", + "spade", +] + +[[package]] +name = "geo-types" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94776032c45f950d30a13af6113c2ad5625316c9abfbccee4dd5a6695f8fe0f5" +dependencies = [ + "approx", + "num-traits", + "rayon", + "rstar", + "serde", +] + +[[package]] +name = "geographiclib-rs" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5a7f08910fd98737a6eda7568e7c5e645093e073328eeef49758cfe8b0489c7" +dependencies = [ + "libm", +] + [[package]] name = "gethostname" version = "1.1.0" @@ -3491,6 +3659,17 @@ dependencies = [ "windows-link", ] +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "getrandom" version = "0.3.4" @@ -3561,6 +3740,16 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "glam" +version = "0.30.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19fc433e8437a212d1b6f1e68c7824af3aed907da60afa994e7f542d18d12aa9" +dependencies = [ + "approx", + "serde_core", +] + [[package]] name = "glam" version = "0.31.1" @@ -3581,7 +3770,7 @@ dependencies = [ "encase", "libm", "mint", - "rand", + "rand 0.10.1", "serde_core", ] @@ -3591,7 +3780,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db060a7f23a5666ccbe9c770cd0e0e419b2eb94c86a7c8bfd091bacf8b4926cd" dependencies = [ - "bevy_reflect", + "bevy_reflect 0.19.0", "glam 0.32.1", "serde", ] @@ -3933,6 +4122,49 @@ dependencies = [ "tokio", ] +[[package]] +name = "i_float" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "010025c2c532c8d82e42d0b8bb5184afa449fa6f06c709ea9adcb16c49ae405b" +dependencies = [ + "libm", +] + +[[package]] +name = "i_key_sort" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9190f86706ca38ac8add223b2aed8b1330002b5cdbbce28fb58b10914d38fc27" + +[[package]] +name = "i_overlay" +version = "4.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413183068e6e0289e18d7d0a1f661b81546e6918d5453a44570b9ab30cbed1b3" +dependencies = [ + "i_float", + "i_key_sort", + "i_shape", + "i_tree", + "rayon", +] + +[[package]] +name = "i_shape" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ea154b742f7d43dae2897fcd5ead86bc7b5eefcedd305a7ebf9f69d44d61082" +dependencies = [ + "i_float", +] + +[[package]] +name = "i_tree" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e6d558e6d4c7b82bc51d9c771e7a927862a161a7d87bf2b0541450e0e20915" + [[package]] name = "icu_collections" version = "2.2.0" @@ -4181,6 +4413,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.14.0" @@ -5546,6 +5787,24 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" +[[package]] +name = "polyanya" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a1cb0a84a11080ff93d943a023e9e386c34d3dcc3c988bc6785f990b1964277" +dependencies = [ + "bvh2d", + "geo", + "glam 0.30.10", + "hashbrown 0.16.1", + "log", + "rerecast", + "serde", + "smallvec", + "spade", + "thiserror 2.0.18", +] + [[package]] name = "polycool" version = "0.4.0" @@ -5590,6 +5849,15 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "presser" version = "0.3.1" @@ -5602,7 +5870,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit", + "toml_edit 0.25.12+spec-1.1.0", ] [[package]] @@ -5710,6 +5978,17 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "019b4b213425016d7d84a153c4c73afb0946fbb4840e4eece7ba8848b9d6da22" +[[package]] +name = "rand" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.4", +] + [[package]] name = "rand" version = "0.10.1" @@ -5717,7 +5996,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ "getrandom 0.4.3", - "rand_core", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", ] [[package]] @@ -5733,7 +6031,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d431c2703ccf129de4d45253c03f49ebb22b97d6ad79ee3ecfc7e3f4862c1d8" dependencies = [ "num-traits", - "rand", + "rand 0.10.1", ] [[package]] @@ -5882,6 +6180,22 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" +[[package]] +name = "rerecast" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd207dd0644bd506d8017195865d72274dae648923596438255a0da00836f548" +dependencies = [ + "anyhow", + "bevy_reflect 0.18.1", + "bitflags 2.13.0", + "glam 0.30.10", + "serde", + "slotmap", + "thiserror 2.0.18", + "tracing", +] + [[package]] name = "rfd" version = "0.17.2" @@ -6059,6 +6373,9 @@ name = "scene" version = "0.1.0" dependencies = [ "blake3", + "glam 0.30.10", + "polyanya", + "rerecast", "ron 0.8.1", "ron-edit", "serde", @@ -6196,6 +6513,12 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +[[package]] +name = "sif-itree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7f45b8998ced5134fb1d75732c77842a3e888f19c1ff98481822e8fbfbf930b" + [[package]] name = "sim" version = "0.1.0" @@ -6274,6 +6597,7 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" dependencies = [ + "serde", "version_check", ] @@ -6714,6 +7038,15 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_datetime" version = "1.1.1+spec-1.1.0" @@ -6723,6 +7056,18 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_edit" +version = "0.23.10+spec-1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +dependencies = [ + "indexmap", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "winnow 0.7.15", +] + [[package]] name = "toml_edit" version = "0.25.12+spec-1.1.0" @@ -6730,9 +7075,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" dependencies = [ "indexmap", - "toml_datetime", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow", + "winnow 1.0.3", ] [[package]] @@ -6741,7 +7086,7 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow", + "winnow 1.0.3", ] [[package]] @@ -6862,12 +7207,12 @@ dependencies = [ "bevy_mesh", "bevy_pbr", "bevy_picking", - "bevy_platform", - "bevy_reflect", + "bevy_platform 0.19.0", + "bevy_reflect 0.19.0", "bevy_render", "bevy_shader", "bevy_transform", - "bevy_utils", + "bevy_utils 0.19.0", "bevy_window", "bytemuck", "transform-gizmo", @@ -7331,7 +7676,7 @@ dependencies = [ "web-sys", "wgpu-core", "wgpu-hal", - "wgpu-types", + "wgpu-types 29.0.3", ] [[package]] @@ -7364,7 +7709,7 @@ dependencies = [ "wgpu-core-deps-windows-linux-android", "wgpu-hal", "wgpu-naga-bridge", - "wgpu-types", + "wgpu-types 29.0.3", ] [[package]] @@ -7442,7 +7787,7 @@ dependencies = [ "wayland-sys", "web-sys", "wgpu-naga-bridge", - "wgpu-types", + "wgpu-types 29.0.3", "windows", "windows-core", "windows-result", @@ -7455,7 +7800,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59c654c483f058800972c3645e95388a7eca31bf9fe1933bc20e036588a0be02" dependencies = [ "naga", - "wgpu-types", + "wgpu-types 29.0.3", +] + +[[package]] +name = "wgpu-types" +version = "27.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afdcf84c395990db737f2dd91628706cb31e86d72e53482320d368e52b5da5eb" +dependencies = [ + "bitflags 2.13.0", + "bytemuck", + "js-sys", + "log", + "serde", + "thiserror 2.0.18", + "web-sys", ] [[package]] @@ -7973,6 +8333,15 @@ dependencies = [ "xkbcommon-dl", ] +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + [[package]] name = "winnow" version = "1.0.3" diff --git a/Cargo.toml b/Cargo.toml index 72a7607..3012d30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "crates/blacksite_surface", "crates/game", "crates/game_hot", "crates/shared", @@ -36,6 +37,7 @@ bevy = { version = "0.19", features = [ ] } bevy_core_pipeline = "0.19" bevy_solari = "0.19" +blacksite_surface = { path = "crates/blacksite_surface" } bevy_ufbx = "0.18.1-rc.1" bevy_egui = "0.40" bevy-inspector-egui = "0.37" @@ -43,6 +45,9 @@ egui_dock = { version = "0.19.1", features = ["serde"] } egui_phosphor_icons = { version = "0.3.1", default-features = false } transform-gizmo-bevy = "0.9" serde = { version = "1", features = ["derive"] } +nav_glam = { package = "glam", version = "0.30.8", features = ["serde"] } +polyanya = { version = "0.16.1", default-features = false, features = ["recast", "serde"] } +rerecast = { version = "0.3.2", default-features = false, features = ["std", "serialize"] } shared = { path = "crates/shared" } game = { path = "crates/game" } game_hot = { path = "crates/game_hot" } @@ -52,6 +57,8 @@ settings = { path = "crates/settings" } scene = { path = "crates/scene" } [patch.crates-io] +# Blacksite Surface ABI and deformation-safety integration for Solari. +bevy_solari = { path = "third_party/bevy_solari" } # Local Bevy 0.19 compatibility patch until upstream publishes a matching FBX loader. bevy_ufbx = { path = "third_party/bevy_ufbx" } # Lossless RON editing fixes used by transactional prefab source edits. diff --git a/README.md b/README.md index 5b98f53..ecafa83 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ cargo clippy --workspace --all-targets -- -D warnings cargo clippy -p editor -p game --bins --features dev,hot-reload -- -D warnings cargo test --workspace cargo validate-levels +cargo bake-navigation --project . --check # Machine-readable project dependency and finding report cargo validate-levels --json cargo package-project --profile development @@ -149,6 +150,7 @@ deep-stale variants. | Asset Browser project/file views | Browse `assets/`, search/filter/sort models, textures, materials, audio clips, levels, and prefabs; switch grid/list; expand model subassets; inspect file details; audition audio; drag supported assets/submeshes into the viewport | | Drag audio clip into viewport | Create an authored audio source; when an audio source is selected, assign the clip instead | | Asset Browser context/details actions | Apply textures/materials, regenerate thumbnails, reimport models, place assets/submeshes, or move file assets to `assets/.trash/` | +| Static/Skinned Mesh Renderer material slots | Assign a shared Material/Material Instance per slot; Browse/Select/Locate the reference, or Clear the override to restore the imported source material | | `Ctrl+P` | Centered command palette; search human labels or stable command IDs, use arrow keys to select, Enter to run | | `F7` | While paused in Play: advance one sim tick | | Shift/Ctrl + click (Hierarchy) | Additive selection | @@ -230,10 +232,21 @@ Viewport shortcut keys require the pointer to be in the viewport and are suspend details when you need full `WorldAssetRoot` scene data instead of normalized static slots. Expanding a model in the Asset Browser exposes normalized mesh/material/texture plus generated skeleton/animation-clip subassets. Dragging a mesh places that part through the static mesh path; - dragging a clip creates an animated scene instance or assigns an exact-rig-compatible state to - the selected imported model. The Animation Controller inspector edits state IDs, clips, + dragging a clip creates a dedicated skinned renderer or assigns an exact-rig-compatible state to + the selected skinned actor. **Default animation** in Model Import Settings optionally selects the + exact clip sampled and paused as the edit-rest pose; **Imported rest pose** never guesses a clip. + Preview stop and PIE exit restore imported transforms and morph weights before resampling that + explicit default. The Animation Controller inspector edits state IDs, clips, range/loop/speed/default/crossfade and provides non-dirty play/pause/stop/scrub preview. See the [animation authoring guide](docs/editor/animation-authoring.md) and [ADR 0031](docs/adr/0031-animation-authoring-runtime-contract.md). +- Navigation bounds, obstacles, areas, and links are created from **Scene > Navigation** or the path + icon in the existing horizontal toolbar. Bounds bake versioned Rerecast artifacts under + `assets/navigation/generated/`; overlapping primitive and additive-brush triangles participate + in the bake fingerprint while distant authoring is excluded. The Inspector reports + stale/current state and provides a Polyanya-backed path test whose mesh, links, and route render + in the viewport. Use + `cargo bake-navigation --project . --check` in CI. See the + [navigation authoring guide](docs/editor/navigation-authoring.md) and [ADR 0032](docs/adr/0032-versioned-navigation-bake-and-runtime-query.md). - Brush actors are persisted as `ActorKind::Brush + BrushDesc`; valid convex faces hydrate into generated preview meshes. Vertex, edge, and face selections use the standard transform gizmo, face material/UV fields are undoable, and clip/intersect/merge/subtract provide conservative @@ -329,6 +342,7 @@ The `.vscode/` folder is preconfigured: - [ADR 0014: Unified Viewport Model](docs/adr/0014-unified-viewport-model.md) - [ADR 0016: Unified Rendering Contract](docs/adr/0016-unified-rendering-contract.md) - [ADR 0017: Normalized Static Mesh Assets](docs/adr/0017-normalized-static-mesh-assets.md) +- [ADR 0034: Registry-driven Authoring Components](docs/adr/0034-registry-driven-authoring-components.md) ## Project Layout @@ -375,12 +389,13 @@ crates/ - [x] Audio clip catalog/import foundation for Ogg, WAV, MP3, and FLAC with dedicated filtering, file details, and stable runtime-resolvable asset references - [x] Audio source/listener authoring, non-dirty spatial audition, viewport icons/range gizmos, stable buses, PIE/runtime parity, device diagnostics, and shared release validation ([ADR 0030](docs/adr/0030-audio-authoring-and-bus-schema.md); production acceptance completed in [Gitea #47](https://git.spacetrainclubhouse.com/Falling-Metal-Interactive/Blacksite/issues/47)) - [x] glTF/GLB skeletal animation manifests, stable controller states, non-dirty preview, PIE/runtime hydration, and exact-signature compatibility validation ([ADR 0031](docs/adr/0031-animation-authoring-runtime-contract.md); production acceptance completed in [Gitea #46](https://git.spacetrainclubhouse.com/Falling-Metal-Interactive/Blacksite/issues/46)) +- [ ] Navigation bounds/obstacles/areas/links, deterministic stale-checked bake artifacts, viewport path preview, headless bake, and game runtime query API ([ADR 0032](docs/adr/0032-versioned-navigation-bake-and-runtime-query.md); tracked in [Gitea #48](https://git.spacetrainclubhouse.com/Falling-Metal-Interactive/Blacksite/issues/48)) - [x] Prefab instances (`PrefabInstance`) + save-as-prefab + unpack - [x] Independent dirty-tab close confirmation and all-tab save guard when switching projects - [x] Transactional scene writes + bounded user-local recovery snapshots ([ADR 0023](docs/adr/0023-transactional-scene-persistence-and-recovery.md)) - [x] Versioned clean/crash session resume + privacy-bounded diagnostic bundle ([ADR 0024](docs/adr/0024-versioned-editor-session-state.md)) - [x] Hierarchy multi-select, reparent undo, multi-entity gizmo transform -- [x] Typed inspector undo (light, rigid body, collider, primitive, material, static mesh renderer) + registry-driven Add Component footer +- [x] Typed inspector undo (light, rigid body, collider, primitive, material, static mesh renderer) + registry-driven Add Component footer; skinned renderer sources are asset-owned and inspectable - [x] Gameplay authoring markers + visualizers (`WeaponSpawn`, `TriggerVolume`, etc.) - [x] Command palette execution; PIE sim step (F7); `xtask validate-levels` - [x] ADRs 0005–0012 (prefab/registry, scene schema, EditorPlugin, editor structure, authoring/hydration, ActorKind, sun policy, zero-debt) @@ -392,16 +407,19 @@ crates/ - [x] Project Browser UI, strict manifest validation, `--project` startup activation, recent filtering, sandbox scaffolding, clean process handoff, and desktop launcher action ([ADR 0025](docs/adr/0025-project-root-is-a-startup-boundary.md)) - [x] Independent scene tabs + stable subscene composition, recursive validation, ownership locks, active-world PIE consistency, and per-saved-tab recovery ([ADR 0026](docs/adr/0026-stable-scene-composition-and-active-document.md)) - [x] Editor lib/bin split + `EditorPluginGroup`; game EditorPlugin dogfood panel -- [x] FBX/glTF model import + normalized `StaticMeshRenderer` placement; explicit scene-instance load via `bevy_ufbx` / `ModelRef` +- [x] FBX/glTF model import + normalized static/skinned renderer routing; explicit generic scene-instance load via `bevy_ufbx` / `ModelRef` - [x] Asset browser model thumbnails (unified `assets/thumbnails/` pipeline; `ThumbnailState` cache; FBX via `FbxThumbnailSource`) -- [x] Material assets (`assets/materials/*.ron`, inspector picker, drag-drop to selection) +- [x] Shared Material/Material Instance assets, stable static/skinned renderer material slots, imported-source fallback, orphan preservation, and runtime-only property blocks ([ADR 0035](docs/adr/0035-shared-material-assets-and-renderer-slots.md), [material-system guide](docs/editor/material-system.md)) - [x] Prefab v2 core: shared stable nested override paths, property/component/structural scopes, recursive graph validation, linked-root variants, conflict recovery, transactional source Apply, and undoable unpack/convert ([ADR 0027](docs/adr/0027-stable-prefab-ownership-and-variants.md)) - [x] Prefab v2 production acceptance: committed base/nested/variant fixtures pass workspace tests, recursive headless validation, packaged release startup, and live editor placement/inspection regression coverage ([Gitea #43](https://git.spacetrainclubhouse.com/Falling-Metal-Interactive/Blacksite/issues/43)) - [x] Shared project validation: editor Diagnostics and `cargo validate-levels --json` use one owner-attributed dependency/finding report across project settings, registry/import artifacts, materials, shaders, scenes, prefabs, brushes, and colliders; valid/missing/cyclic/incompatible fixtures fail on blocking content errors ([ADR 0028](docs/adr/0028-authoritative-project-content-validation.md), [Gitea #45](https://git.spacetrainclubhouse.com/Falling-Metal-Interactive/Blacksite/issues/45)) - [x] Validation-gated package foundation: versioned development/QA/release profiles, deterministic runtime exclusions, stable input snapshots, transactional staged publication, Cargo-reported artifacts, BLAKE3 metadata, authored default-scene startup, and a non-blocking editor Build panel with live logs/cancel/run/reveal ([ADR 0029](docs/adr/0029-validation-gated-build-profiles-and-packaging.md), [build guide](docs/editor/build-and-package.md), [Gitea #44](https://git.spacetrainclubhouse.com/Falling-Metal-Interactive/Blacksite/issues/44)) - [x] Advanced rendering: `GiMode`, post-process volumes, Rendering panel, requested/effective render stack, Solari integration, emissive materials, post FX assets ([ADR 0013](docs/adr/0013-rendering-tiers-and-post-process-volumes.md), [ADR 0016](docs/adr/0016-unified-rendering-contract.md), [rendering guide](docs/editor/rendering.md)) +- [x] Surface ABI v1 with shared raster/Solari evaluator dispatch, packed typed parameters/textures, exact cutout ray-candidate evaluation, last-good shader fallback, and explicit exclusion of skinned/morph-deformed Solari geometry ([ADR 0036](docs/adr/0036-surface-abi-and-solari-parity.md), [material-system guide](docs/editor/material-system.md)) - [x] Static mesh renderer component, generated normalized mesh artifacts, source/one-actor hierarchy placement, and inspector renderer slots ([ADR 0017](docs/adr/0017-normalized-static-mesh-assets.md)) +- [x] Dedicated `SkinnedMeshRenderer`/`SkinnedMesh` actor path, skinned-part exclusion from static slots, dedicated runtime hierarchy hydration, and v2-to-v3 animated-scene migration ([ADR 0033](docs/adr/0033-dedicated-skinned-mesh-renderer.md)) - [x] Componentized actor inspector, unified component cards, thumbnail static mesh slots, imported asset Browse/Locate/Clear refs with inherited source defaults, collider split, shader-aware actor material data, and texture picker/drop refs ([ADR 0018](docs/adr/0018-componentized-actor-inspector-and-materials.md)) +- [x] Stable authoring component IDs, registry-built save filtering, reflected atomic add/remove/reset/copy/paste history, independent active state, static extension registration, and derived composable `ActorKind` hints ([ADR 0034](docs/adr/0034-registry-driven-authoring-components.md), [extensibility guide](docs/editor/extensibility.md)) - [x] Brush authoring schema MVP with `ActorKind::Brush`, cube `BrushDesc`, generated mesh hydration, scene migration, and inspector Add Component support ([ADR 0021](docs/adr/0021-brush-authoring-schema.md)) - [x] Brush draw, vertex/edge/face gizmo editing, face material/UV authoring, clip, and bounds-based CSG preview/commit workflow ([brush guide](docs/editor/brushes.md)) - [x] Searchable command palette with human labels/stable IDs and a status bar that exposes scene I/O, tool, history, mode, and selection feedback @@ -414,10 +432,12 @@ crates/ untextured models), material sphere thumbnails, search/filter/sort controls, expandable model subasset shelves, and a staged details pane. **File → Import Assets** accepts glTF/GLB and **FBX** (binary; copies sibling `.fbm` texture folders when present). - Model assets generate normalized static mesh manifests under `assets/meshes/generated/`; drag/drop - uses `StaticMeshRenderer` by default with imported asset refs and optional separate static mesh - collider components. Expanded mesh subassets generate independent thumbnails and can be placed independently. Asset details can switch - placement to **Scene Instance** for `ModelRef`/`WorldAssetRoot` playback, shared material assets can be + Model assets generate normalized model manifests under `assets/meshes/generated/`; drag/drop uses + **Renderable Asset (Auto)**. Unrigged sources use `StaticMeshRenderer` with imported asset refs + and optional separate static mesh colliders. Skin-bound or animated sources and their subasset placement use + `SkinnedMeshRenderer`, preserve the source joint hierarchy, and never create static slots or + automatic triangle colliders. Expanded mesh subassets generate independent thumbnails. Asset details can switch + placement to **Scene Instance** for generic `ModelRef`/`WorldAssetRoot` scenes, shared material assets can be edited from the browser, and delete actions move files to `assets/.trash/`. Animation authoring supports glTF/GLB rig and clip manifests, stable controller refs, clip drag assignment/placement, inspector preview, PIE/runtime playback, and headless compatibility diagnostics. Animated or @@ -432,7 +452,7 @@ crates/ **Unpack Layer** preserves nested links; **Convert to Local** recursively removes them. Current authoring UI coverage and production-acceptance gaps are tracked in [prefab-authoring.md](docs/editor/prefab-authoring.md). -- Material assets are RON files under `assets/materials/`; shader schemas live under `assets/shaders/`. `MaterialDesc` stores shader kind, typed parameters, texture bindings, and StandardMaterial fields. The Asset Browser material details editor can apply shader schemas, edit typed parameters/textures, and regenerate sphere thumbnails. Actor inspector material edits live on the actor `MaterialDesc`; static mesh source-material refs remain imported defaults and shared material assets are edited from the Asset Browser. +- Material and direct-base Material Instance assets live under `assets/materials/`; shader schemas live under `assets/shaders/`. In the Asset Browser, select a Material and use **Create Instance** for sparse inherited variants. Static and skinned renderers own stable per-draw material slots, with explicit assignments taking precedence over imported source defaults; shared edits propagate through live-updated handles without reloading skinned geometry. Custom Surface evaluators share one constrained ABI between raster and Solari-eligible non-deformed geometry. Dynamic skinned/morph Solari geometry and a persisted property-block authoring workflow remain future work; see the [material-system guide](docs/editor/material-system.md). - Per-field reflect undo for all components remains future work; typed `shared` inspectors cover the common authoring path. - The authoring/hydration layer is intentionally small so richer asset workflows (terrain, material graphs, lighting profiles) can be added without changing the scene format foundation. diff --git a/assets/.index/registry.ron b/assets/.index/registry.ron index 0b2bfe2..cf83459 100644 --- a/assets/.index/registry.ron +++ b/assets/.index/registry.ron @@ -12,6 +12,8 @@ hierarchy_mode: SingleActor, material_policy: SourceMaterials, static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, ), dependencies: [], ), @@ -28,6 +30,8 @@ hierarchy_mode: SingleActor, material_policy: SourceMaterials, static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, ), dependencies: [], ), @@ -44,6 +48,8 @@ hierarchy_mode: SingleActor, material_policy: SourceMaterials, static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, ), dependencies: [], ), @@ -60,6 +66,8 @@ hierarchy_mode: SingleActor, material_policy: SourceMaterials, static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, ), dependencies: [], ), @@ -76,6 +84,44 @@ hierarchy_mode: SingleActor, material_policy: SourceMaterials, static_mesh_manifest_path: Some("assets/meshes/generated/b98ef565-3500-49e7-9935-f685fa9b2594.static_mesh.ron"), + animation_manifest_path: Some("assets/animations/generated/b98ef565-3500-49e7-9935-f685fa9b2594.animation.ron"), + default_animation_clip_id: None, + ), + dependencies: [], + ), + ( + id: ("113f74df-e39c-41d4-9b5b-e48efe541f7f"), + path: "assets/models/RobotExpressive.glb", + label: "RobotExpressive", + kind_tag: "Model", + import_settings: ( + scale: 1.0, + generate_collider: true, + lod0_only: true, + placement_mode: SceneInstance, + hierarchy_mode: SourceHierarchy, + material_policy: SourceMaterials, + static_mesh_manifest_path: Some("assets/meshes/generated/113f74df-e39c-41d4-9b5b-e48efe541f7f.static_mesh.ron"), + animation_manifest_path: Some("assets/animations/generated/113f74df-e39c-41d4-9b5b-e48efe541f7f.animation.ron"), + default_animation_clip_id: Some("animation:clip:8:standing"), + ), + dependencies: [], + ), + ( + id: ("3f63f359-45eb-4cb2-8970-71921cbd7bd0"), + path: "assets/models/robot_expressive.glb", + label: "robot_expressive", + kind_tag: "Model", + import_settings: ( + scale: 1.0, + generate_collider: true, + lod0_only: true, + placement_mode: StaticAsset, + hierarchy_mode: SingleActor, + material_policy: SourceMaterials, + static_mesh_manifest_path: Some("assets/meshes/generated/3f63f359-45eb-4cb2-8970-71921cbd7bd0.static_mesh.ron"), + animation_manifest_path: Some("assets/animations/generated/3f63f359-45eb-4cb2-8970-71921cbd7bd0.animation.ron"), + default_animation_clip_id: Some("animation:clip:8:standing"), ), dependencies: [], ), @@ -92,6 +138,8 @@ hierarchy_mode: SingleActor, material_policy: SourceMaterials, static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, ), dependencies: [], ), @@ -108,6 +156,8 @@ hierarchy_mode: SingleActor, material_policy: SourceMaterials, static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, ), dependencies: [], ), @@ -124,6 +174,8 @@ hierarchy_mode: SingleActor, material_policy: SourceMaterials, static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, ), dependencies: [], ), @@ -140,6 +192,26 @@ hierarchy_mode: SingleActor, material_policy: SourceMaterials, static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, + ), + dependencies: [], + ), + ( + id: ("706e604e-1a63-4e97-b35d-eeb1d88fe3a9"), + path: "assets/levels/navigation_authoring_showcase.scn.ron", + label: "navigation_authoring_showcase.scn", + kind_tag: "Level", + import_settings: ( + scale: 1.0, + generate_collider: true, + lod0_only: true, + placement_mode: StaticAsset, + hierarchy_mode: SingleActor, + material_policy: SourceMaterials, + static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, ), dependencies: [], ), @@ -156,6 +228,8 @@ hierarchy_mode: SingleActor, material_policy: SourceMaterials, static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, ), dependencies: [], ), @@ -172,6 +246,8 @@ hierarchy_mode: SingleActor, material_policy: SourceMaterials, static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, ), dependencies: [], ), @@ -188,6 +264,8 @@ hierarchy_mode: SingleActor, material_policy: SourceMaterials, static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, ), dependencies: [], ), @@ -204,6 +282,8 @@ hierarchy_mode: SingleActor, material_policy: SourceMaterials, static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, ), dependencies: [], ), @@ -220,6 +300,8 @@ hierarchy_mode: SingleActor, material_policy: SourceMaterials, static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, ), dependencies: [], ), @@ -236,6 +318,8 @@ hierarchy_mode: SingleActor, material_policy: SourceMaterials, static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, ), dependencies: [], ), @@ -252,6 +336,62 @@ hierarchy_mode: SingleActor, material_policy: SourceMaterials, static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, + ), + dependencies: [], + ), + ( + id: ("04a4e00e-4732-43db-8e99-9cd99b94667b"), + path: "assets/animations/generated/113f74df-e39c-41d4-9b5b-e48efe541f7f.animation.ron", + label: "113f74df-e39c-41d4-9b5b-e48efe541f7f.animation", + kind_tag: "Level", + import_settings: ( + scale: 1.0, + generate_collider: true, + lod0_only: true, + placement_mode: StaticAsset, + hierarchy_mode: SingleActor, + material_policy: SourceMaterials, + static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, + ), + dependencies: [], + ), + ( + id: ("4937e63f-923b-4270-bdfc-298d63bcb814"), + path: "assets/animations/generated/3f63f359-45eb-4cb2-8970-71921cbd7bd0.animation.ron", + label: "3f63f359-45eb-4cb2-8970-71921cbd7bd0.animation", + kind_tag: "Level", + import_settings: ( + scale: 1.0, + generate_collider: true, + lod0_only: true, + placement_mode: StaticAsset, + hierarchy_mode: SingleActor, + material_policy: SourceMaterials, + static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, + ), + dependencies: [], + ), + ( + id: ("28f1df44-6d24-4722-bf75-6969879995a4"), + path: "assets/animations/generated/b98ef565-3500-49e7-9935-f685fa9b2594.animation.ron", + label: "b98ef565-3500-49e7-9935-f685fa9b2594.animation", + kind_tag: "Level", + import_settings: ( + scale: 1.0, + generate_collider: true, + lod0_only: true, + placement_mode: StaticAsset, + hierarchy_mode: SingleActor, + material_policy: SourceMaterials, + static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, ), dependencies: [], ), @@ -268,6 +408,8 @@ hierarchy_mode: SingleActor, material_policy: SourceMaterials, static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, ), dependencies: [], ), @@ -284,6 +426,26 @@ hierarchy_mode: SingleActor, material_policy: SourceMaterials, static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, + ), + dependencies: [], + ), + ( + id: ("264e2010-0693-4d74-8440-99373c2cedb3"), + path: "assets/navigation/generated/navigation_showcase_humanoid.nav.ron", + label: "navigation_showcase_humanoid.nav", + kind_tag: "Level", + import_settings: ( + scale: 1.0, + generate_collider: true, + lod0_only: true, + placement_mode: StaticAsset, + hierarchy_mode: SingleActor, + material_policy: SourceMaterials, + static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, ), dependencies: [], ), @@ -300,6 +462,26 @@ hierarchy_mode: SingleActor, material_policy: SourceMaterials, static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, + ), + dependencies: [], + ), + ( + id: ("cc2769da-c9a5-4cb3-866a-cc81d0688ee2"), + path: "assets/materials/surface_tint_instance.ron", + label: "surface_tint_instance", + kind_tag: "Material", + import_settings: ( + scale: 1.0, + generate_collider: true, + lod0_only: true, + placement_mode: StaticAsset, + hierarchy_mode: SingleActor, + material_policy: SourceMaterials, + static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, ), dependencies: [], ), @@ -316,6 +498,26 @@ hierarchy_mode: SingleActor, material_policy: SourceMaterials, static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, + ), + dependencies: [], + ), + ( + id: ("d6cb4151-7124-4237-aaf9-f7f8abd5fb76"), + path: "assets/materials/surface_tint.ron", + label: "surface_tint", + kind_tag: "Material", + import_settings: ( + scale: 1.0, + generate_collider: true, + lod0_only: true, + placement_mode: StaticAsset, + hierarchy_mode: SingleActor, + material_policy: SourceMaterials, + static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, ), dependencies: [], ), @@ -332,6 +534,8 @@ hierarchy_mode: SingleActor, material_policy: SourceMaterials, static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, ), dependencies: [], ), @@ -348,6 +552,8 @@ hierarchy_mode: SingleActor, material_policy: SourceMaterials, static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, ), dependencies: [], ), @@ -364,6 +570,8 @@ hierarchy_mode: SingleActor, material_policy: SourceMaterials, static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, ), dependencies: [], ), @@ -380,6 +588,8 @@ hierarchy_mode: SingleActor, material_policy: SourceMaterials, static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, ), dependencies: [], ), @@ -396,6 +606,26 @@ hierarchy_mode: SingleActor, material_policy: SourceMaterials, static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, + ), + dependencies: [], + ), + ( + id: ("a72943d6-3e9d-4629-8887-5c02d99d4f92"), + path: "assets/shaders/surface_tint.shader.ron", + label: "surface_tint.shader", + kind_tag: "ShaderSchema", + import_settings: ( + scale: 1.0, + generate_collider: true, + lod0_only: true, + placement_mode: StaticAsset, + hierarchy_mode: SingleActor, + material_policy: SourceMaterials, + static_mesh_manifest_path: None, + animation_manifest_path: None, + default_animation_clip_id: None, ), dependencies: [], ), diff --git a/assets/animations/generated/113f74df-e39c-41d4-9b5b-e48efe541f7f.animation.ron b/assets/animations/generated/113f74df-e39c-41d4-9b5b-e48efe541f7f.animation.ron new file mode 100644 index 0000000..0c9f6ca --- /dev/null +++ b/assets/animations/generated/113f74df-e39c-41d4-9b5b-e48efe541f7f.animation.ron @@ -0,0 +1,236 @@ +( + schema_version: 3, + asset_id: "113f74df-e39c-41d4-9b5b-e48efe541f7f", + label: "RobotExpressive", + default_animation_clip_id: Some("animation:clip:8:standing"), + source: ( + path: "assets/models/RobotExpressive.glb", + format: "glb", + fingerprint: ( + byte_len: 463988, + modified_unix_secs: 1783799272, + content_hash: "5869cb813e6a6093eda7af2824a9c13a582750e0443e88ce7c991a1f83415521", + ), + dependencies: [], + ), + runtime_supported: true, + skeletons: [ + ( + id: "animation:skeleton:0:skeleton_0", + label: "Skeleton 0", + source_index: 0, + signature: ("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b"), + joint_paths: [ + "RootNode/RobotArmature/Bone", + "RootNode/RobotArmature/Bone/Foot.L", + "RootNode/RobotArmature/Bone/Body", + "RootNode/RobotArmature/Bone/Body/Hips", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Neck", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Neck/Head", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm2.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm2.L/Middle1.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm2.L/Middle1.L/Middle2.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Thumb.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Thumb.L/Thumb2.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm1.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm1.L/Index.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm1.L/Index.L/Index2.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm3.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm3.L/Ring1.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm3.L/Ring1.L/Ring2.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm2.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm2.R/Middle1.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm2.R/Middle1.R/Middle2.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Thumb.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Thumb.R/Thumb2.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm1.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm1.R/Index.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm1.R/Index.R/Index2.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm3.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm3.R/Ring1.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm3.R/Ring1.R/Ring2.R", + "RootNode/RobotArmature/Bone/Body/UpperLeg.L", + "RootNode/RobotArmature/Bone/Body/UpperLeg.L/LowerLeg.L", + "RootNode/RobotArmature/Bone/Body/UpperLeg.R", + "RootNode/RobotArmature/Bone/Body/UpperLeg.R/LowerLeg.R", + "RootNode/RobotArmature/Bone/PoleTarget.L", + "RootNode/RobotArmature/Bone/Foot.R", + "RootNode/RobotArmature/Bone/PoleTarget.R", + ], + ), + ( + id: "animation:skeleton:1:skeleton_1", + label: "Skeleton 1", + source_index: 1, + signature: ("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b"), + joint_paths: [ + "RootNode/RobotArmature/Bone", + "RootNode/RobotArmature/Bone/Foot.L", + "RootNode/RobotArmature/Bone/Body", + "RootNode/RobotArmature/Bone/Body/Hips", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Neck", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Neck/Head", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm2.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm2.L/Middle1.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm2.L/Middle1.L/Middle2.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Thumb.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Thumb.L/Thumb2.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm1.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm1.L/Index.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm1.L/Index.L/Index2.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm3.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm3.L/Ring1.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm3.L/Ring1.L/Ring2.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm2.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm2.R/Middle1.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm2.R/Middle1.R/Middle2.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Thumb.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Thumb.R/Thumb2.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm1.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm1.R/Index.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm1.R/Index.R/Index2.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm3.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm3.R/Ring1.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm3.R/Ring1.R/Ring2.R", + "RootNode/RobotArmature/Bone/Body/UpperLeg.L", + "RootNode/RobotArmature/Bone/Body/UpperLeg.L/LowerLeg.L", + "RootNode/RobotArmature/Bone/Body/UpperLeg.R", + "RootNode/RobotArmature/Bone/Body/UpperLeg.R/LowerLeg.R", + "RootNode/RobotArmature/Bone/PoleTarget.L", + "RootNode/RobotArmature/Bone/Foot.R", + "RootNode/RobotArmature/Bone/PoleTarget.R", + ], + ), + ], + clips: [ + ( + id: "animation:clip:0:dance", + label: "Dance", + source_index: 0, + duration_seconds: 3.3333333, + target_skeleton_signature: Some(("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b")), + events: [], + ), + ( + id: "animation:clip:1:death", + label: "Death", + source_index: 1, + duration_seconds: 0.9583333, + target_skeleton_signature: Some(("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b")), + events: [], + ), + ( + id: "animation:clip:2:idle", + label: "Idle", + source_index: 2, + duration_seconds: 3.3333333, + target_skeleton_signature: Some(("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b")), + events: [], + ), + ( + id: "animation:clip:3:jump", + label: "Jump", + source_index: 3, + duration_seconds: 0.7083333, + target_skeleton_signature: Some(("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b")), + events: [], + ), + ( + id: "animation:clip:4:no", + label: "No", + source_index: 4, + duration_seconds: 1.6666666, + target_skeleton_signature: Some(("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b")), + events: [], + ), + ( + id: "animation:clip:5:punch", + label: "Punch", + source_index: 5, + duration_seconds: 0.8333333, + target_skeleton_signature: Some(("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b")), + events: [], + ), + ( + id: "animation:clip:6:running", + label: "Running", + source_index: 6, + duration_seconds: 0.9583333, + target_skeleton_signature: Some(("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b")), + events: [], + ), + ( + id: "animation:clip:7:sitting", + label: "Sitting", + source_index: 7, + duration_seconds: 0.41666666, + target_skeleton_signature: Some(("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b")), + events: [], + ), + ( + id: "animation:clip:8:standing", + label: "Standing", + source_index: 8, + duration_seconds: 0.41666666, + target_skeleton_signature: Some(("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b")), + events: [], + ), + ( + id: "animation:clip:9:thumbsup", + label: "ThumbsUp", + source_index: 9, + duration_seconds: 1.5833334, + target_skeleton_signature: Some(("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b")), + events: [], + ), + ( + id: "animation:clip:10:walking", + label: "Walking", + source_index: 10, + duration_seconds: 0.9583333, + target_skeleton_signature: Some(("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b")), + events: [], + ), + ( + id: "animation:clip:11:walkjump", + label: "WalkJump", + source_index: 11, + duration_seconds: 0.8333333, + target_skeleton_signature: Some(("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b")), + events: [], + ), + ( + id: "animation:clip:12:wave", + label: "Wave", + source_index: 12, + duration_seconds: 1.8333334, + target_skeleton_signature: Some(("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b")), + events: [], + ), + ( + id: "animation:clip:13:yes", + label: "Yes", + source_index: 13, + duration_seconds: 1.6666666, + target_skeleton_signature: Some(("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b")), + events: [], + ), + ], + diagnostics: [], +) \ No newline at end of file diff --git a/assets/animations/generated/3f63f359-45eb-4cb2-8970-71921cbd7bd0.animation.ron b/assets/animations/generated/3f63f359-45eb-4cb2-8970-71921cbd7bd0.animation.ron new file mode 100644 index 0000000..8ae208c --- /dev/null +++ b/assets/animations/generated/3f63f359-45eb-4cb2-8970-71921cbd7bd0.animation.ron @@ -0,0 +1,236 @@ +( + schema_version: 3, + asset_id: "3f63f359-45eb-4cb2-8970-71921cbd7bd0", + label: "robot_expressive", + default_animation_clip_id: Some("animation:clip:8:standing"), + source: ( + path: "assets/models/robot_expressive.glb", + format: "glb", + fingerprint: ( + byte_len: 463988, + modified_unix_secs: 1783750957, + content_hash: "5869cb813e6a6093eda7af2824a9c13a582750e0443e88ce7c991a1f83415521", + ), + dependencies: [], + ), + runtime_supported: true, + skeletons: [ + ( + id: "animation:skeleton:0:skeleton_0", + label: "Skeleton 0", + source_index: 0, + signature: ("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b"), + joint_paths: [ + "RootNode/RobotArmature/Bone", + "RootNode/RobotArmature/Bone/Foot.L", + "RootNode/RobotArmature/Bone/Body", + "RootNode/RobotArmature/Bone/Body/Hips", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Neck", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Neck/Head", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm2.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm2.L/Middle1.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm2.L/Middle1.L/Middle2.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Thumb.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Thumb.L/Thumb2.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm1.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm1.L/Index.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm1.L/Index.L/Index2.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm3.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm3.L/Ring1.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm3.L/Ring1.L/Ring2.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm2.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm2.R/Middle1.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm2.R/Middle1.R/Middle2.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Thumb.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Thumb.R/Thumb2.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm1.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm1.R/Index.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm1.R/Index.R/Index2.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm3.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm3.R/Ring1.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm3.R/Ring1.R/Ring2.R", + "RootNode/RobotArmature/Bone/Body/UpperLeg.L", + "RootNode/RobotArmature/Bone/Body/UpperLeg.L/LowerLeg.L", + "RootNode/RobotArmature/Bone/Body/UpperLeg.R", + "RootNode/RobotArmature/Bone/Body/UpperLeg.R/LowerLeg.R", + "RootNode/RobotArmature/Bone/PoleTarget.L", + "RootNode/RobotArmature/Bone/Foot.R", + "RootNode/RobotArmature/Bone/PoleTarget.R", + ], + ), + ( + id: "animation:skeleton:1:skeleton_1", + label: "Skeleton 1", + source_index: 1, + signature: ("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b"), + joint_paths: [ + "RootNode/RobotArmature/Bone", + "RootNode/RobotArmature/Bone/Foot.L", + "RootNode/RobotArmature/Bone/Body", + "RootNode/RobotArmature/Bone/Body/Hips", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Neck", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Neck/Head", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm2.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm2.L/Middle1.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm2.L/Middle1.L/Middle2.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Thumb.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Thumb.L/Thumb2.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm1.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm1.L/Index.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm1.L/Index.L/Index2.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm3.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm3.L/Ring1.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/LowerArm.L/Palm3.L/Ring1.L/Ring2.L", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm2.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm2.R/Middle1.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm2.R/Middle1.R/Middle2.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Thumb.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Thumb.R/Thumb2.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm1.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm1.R/Index.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm1.R/Index.R/Index2.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm3.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm3.R/Ring1.R", + "RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/LowerArm.R/Palm3.R/Ring1.R/Ring2.R", + "RootNode/RobotArmature/Bone/Body/UpperLeg.L", + "RootNode/RobotArmature/Bone/Body/UpperLeg.L/LowerLeg.L", + "RootNode/RobotArmature/Bone/Body/UpperLeg.R", + "RootNode/RobotArmature/Bone/Body/UpperLeg.R/LowerLeg.R", + "RootNode/RobotArmature/Bone/PoleTarget.L", + "RootNode/RobotArmature/Bone/Foot.R", + "RootNode/RobotArmature/Bone/PoleTarget.R", + ], + ), + ], + clips: [ + ( + id: "animation:clip:0:dance", + label: "Dance", + source_index: 0, + duration_seconds: 3.3333333, + target_skeleton_signature: Some(("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b")), + events: [], + ), + ( + id: "animation:clip:1:death", + label: "Death", + source_index: 1, + duration_seconds: 0.9583333, + target_skeleton_signature: Some(("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b")), + events: [], + ), + ( + id: "animation:clip:2:idle", + label: "Idle", + source_index: 2, + duration_seconds: 3.3333333, + target_skeleton_signature: Some(("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b")), + events: [], + ), + ( + id: "animation:clip:3:jump", + label: "Jump", + source_index: 3, + duration_seconds: 0.7083333, + target_skeleton_signature: Some(("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b")), + events: [], + ), + ( + id: "animation:clip:4:no", + label: "No", + source_index: 4, + duration_seconds: 1.6666666, + target_skeleton_signature: Some(("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b")), + events: [], + ), + ( + id: "animation:clip:5:punch", + label: "Punch", + source_index: 5, + duration_seconds: 0.8333333, + target_skeleton_signature: Some(("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b")), + events: [], + ), + ( + id: "animation:clip:6:running", + label: "Running", + source_index: 6, + duration_seconds: 0.9583333, + target_skeleton_signature: Some(("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b")), + events: [], + ), + ( + id: "animation:clip:7:sitting", + label: "Sitting", + source_index: 7, + duration_seconds: 0.41666666, + target_skeleton_signature: Some(("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b")), + events: [], + ), + ( + id: "animation:clip:8:standing", + label: "Standing", + source_index: 8, + duration_seconds: 0.41666666, + target_skeleton_signature: Some(("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b")), + events: [], + ), + ( + id: "animation:clip:9:thumbsup", + label: "ThumbsUp", + source_index: 9, + duration_seconds: 1.5833334, + target_skeleton_signature: Some(("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b")), + events: [], + ), + ( + id: "animation:clip:10:walking", + label: "Walking", + source_index: 10, + duration_seconds: 0.9583333, + target_skeleton_signature: Some(("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b")), + events: [], + ), + ( + id: "animation:clip:11:walkjump", + label: "WalkJump", + source_index: 11, + duration_seconds: 0.8333333, + target_skeleton_signature: Some(("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b")), + events: [], + ), + ( + id: "animation:clip:12:wave", + label: "Wave", + source_index: 12, + duration_seconds: 1.8333334, + target_skeleton_signature: Some(("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b")), + events: [], + ), + ( + id: "animation:clip:13:yes", + label: "Yes", + source_index: 13, + duration_seconds: 1.6666666, + target_skeleton_signature: Some(("e9761f2a198d6a280afd0fc2aca6e8783dc6237bd85c90b8bc07a2e11271dc2b")), + events: [], + ), + ], + diagnostics: [], +) \ No newline at end of file diff --git a/assets/animations/generated/b98ef565-3500-49e7-9935-f685fa9b2594.animation.ron b/assets/animations/generated/b98ef565-3500-49e7-9935-f685fa9b2594.animation.ron new file mode 100644 index 0000000..6259f6e --- /dev/null +++ b/assets/animations/generated/b98ef565-3500-49e7-9935-f685fa9b2594.animation.ron @@ -0,0 +1,20 @@ +( + schema_version: 3, + asset_id: "b98ef565-3500-49e7-9935-f685fa9b2594", + label: "painted_wooden_chair_02_2k", + default_animation_clip_id: None, + source: ( + path: "assets/models/painted_wooden_chair_02_2k.fbx", + format: "fbx", + fingerprint: ( + byte_len: 59964, + modified_unix_secs: 1780713434, + content_hash: "b12973a62dcb44589e380ea833eade726ae98a86c81084c842ee3801866d6a46", + ), + dependencies: [], + ), + runtime_supported: false, + skeletons: [], + clips: [], + diagnostics: [], +) \ No newline at end of file diff --git a/assets/levels/audio_authoring_showcase.scn.ron b/assets/levels/audio_authoring_showcase.scn.ron index d0d48dc..65ae56a 100644 --- a/assets/levels/audio_authoring_showcase.scn.ron +++ b/assets/levels/audio_authoring_showcase.scn.ron @@ -1,18 +1,16 @@ -(schema_version: 2, - resources: {}, - entities: { - 4294969001: ( - components: { - "bevy_ecs::name::Name": "Audition Tone Source", - "bevy_transform::components::transform::Transform": ( +(schema_version: 4,resources: { + }, + entities: { + 4294969001: (components: { + "bevy_ecs::name::Name": "Audition Tone Source", + "bevy_transform::components::transform::Transform": ( translation: (0.0, 1.5, -4.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::LevelObject": (), - "shared::components::ActorId": ("audio-showcase-source"), - "shared::components::ActorKind": AudioSource, - "shared::components::AudioSourceDesc": ( + "shared::components::ActorId": ("audio-showcase-source"), + "shared::components::ActorKind": AudioSource, + "shared::components::AudioSourceDesc": ( clip: Some(( asset_id: "c0a7d10e-53c1-4de0-b0dc-34a66f44ba77", sub_asset_id: "audio:source", @@ -32,25 +30,23 @@ ), bus: "sfx", ), - }, - ), - 4294969002: ( - components: { - "bevy_ecs::name::Name": "Showcase Listener", - "bevy_transform::components::transform::Transform": ( + "shared::components::LevelObject": (), + }), + 4294969002: (components: { + "bevy_ecs::name::Name": "Showcase Listener", + "bevy_transform::components::transform::Transform": ( translation: (0.0, 1.7, 0.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::LevelObject": (), - "shared::components::ActorId": ("audio-showcase-listener"), - "shared::components::ActorKind": AudioListener, - "shared::components::AudioListenerDesc": ( + "shared::components::ActorId": ("audio-showcase-listener"), + "shared::components::ActorKind": AudioListener, + "shared::components::AudioListenerDesc": ( enabled: true, priority: 100, ear_gap: 0.2, ), - }, - ), - }, -) + "shared::components::LevelObject": (), + }), + }, +) \ No newline at end of file diff --git a/assets/levels/editor_scene 2.scn.ron b/assets/levels/editor_scene 2.scn.ron index 67270d2..53c1d0e 100644 --- a/assets/levels/editor_scene 2.scn.ron +++ b/assets/levels/editor_scene 2.scn.ron @@ -1,21 +1,20 @@ -(schema_version: 2, - resources: {}, - entities: { - 4294967126: ( - components: { - "bevy_ecs::name::Name": "Pillar Copy", - "bevy_transform::components::transform::Transform": ( +(schema_version: 4,resources: { + }, + entities: { + 4294967126: (components: { + "bevy_ecs::name::Name": "Pillar Copy", + "bevy_transform::components::transform::Transform": ( translation: (3.7178926, 3.5, -11.932255), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (23), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (23), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.85, g: 0.85, @@ -29,7 +28,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 1.2, @@ -37,27 +36,25 @@ z_length: 1.2, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (1.2, 7.0, 1.2), ), - }, - ), - 4294967158: ( - components: { - "bevy_ecs::name::Name": "Scene Sun", - "bevy_transform::components::transform::Transform": ( + }), + 4294967158: (components: { + "bevy_ecs::name::Name": "Scene Sun", + "bevy_transform::components::transform::Transform": ( translation: (0.0, 5.316672, 0.0), rotation: (-0.21401538, 0.31161797, 0.67592126, 0.6326311), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": Light, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": Light, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (0), - "shared::components::LevelObject": (), - "shared::components::LightDesc": ( + "shared::components::HierarchySiblingIndex": (0), + "shared::components::LevelObject": (), + "shared::components::LightDesc": ( kind: Directional, color: ( r: 1.0, @@ -71,23 +68,21 @@ inner_angle_deg: 25.0, outer_angle_deg: 35.0, ), - }, - ), - 4294967159: ( - components: { - "bevy_ecs::name::Name": "Ground", - "bevy_transform::components::transform::Transform": ( + }), + 4294967159: (components: { + "bevy_ecs::name::Name": "Ground", + "bevy_transform::components::transform::Transform": ( translation: (0.0, -0.5, 0.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (1), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (1), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.42, g: 0.45, @@ -101,7 +96,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 200.0, @@ -109,27 +104,25 @@ z_length: 200.0, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (200.0, 1.0, 200.0), ), - }, - ), - 8589934420: ( - components: { - "bevy_ecs::name::Name": "Pillar Copy", - "bevy_transform::components::transform::Transform": ( + }), + 8589934420: (components: { + "bevy_ecs::name::Name": "Pillar Copy", + "bevy_transform::components::transform::Transform": ( translation: (-12.079595, 3.5, -13.098544), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (25), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (25), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.85, g: 0.85, @@ -143,7 +136,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 1.2, @@ -151,44 +144,40 @@ z_length: 1.2, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (1.2, 7.0, 1.2), ), - }, - ), - 8589934456: ( - components: { - "bevy_ecs::name::Name": "Player Start", - "bevy_transform::components::transform::Transform": ( + }), + 8589934456: (components: { + "bevy_ecs::name::Name": "Player Start", + "bevy_transform::components::transform::Transform": ( translation: (0.0, 0.9900649, 12.46175), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": PlayerSpawn, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": PlayerSpawn, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (2), - "shared::components::LevelObject": (), - "shared::components::PlayerSpawn": (), - }, - ), - 8589934457: ( - components: { - "bevy_ecs::name::Name": "ShowcaseSphere", - "bevy_transform::components::transform::Transform": ( + "shared::components::HierarchySiblingIndex": (2), + "shared::components::LevelObject": (), + "shared::components::PlayerSpawn": (), + }), + 8589934457: (components: { + "bevy_ecs::name::Name": "ShowcaseSphere", + "bevy_transform::components::transform::Transform": ( translation: (-1.3999996, 0.6, 4.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (3), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (3), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.9, g: 0.9, @@ -202,33 +191,31 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Sphere( radius: 0.6, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Sphere, size: (1.2, 1.2, 1.2), ), - }, - ), - 8589934458: ( - components: { - "bevy_ecs::name::Name": "ShowcaseSphere", - "bevy_transform::components::transform::Transform": ( + }), + 8589934458: (components: { + "bevy_ecs::name::Name": "ShowcaseSphere", + "bevy_transform::components::transform::Transform": ( translation: (-3.6, 0.6, 4.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (4), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (4), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.9, g: 0.9, @@ -242,33 +229,31 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Sphere( radius: 0.6, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Sphere, size: (1.2, 1.2, 1.2), ), - }, - ), - 8589934459: ( - components: { - "bevy_ecs::name::Name": "ShowcaseSphere", - "bevy_transform::components::transform::Transform": ( + }), + 8589934459: (components: { + "bevy_ecs::name::Name": "ShowcaseSphere", + "bevy_transform::components::transform::Transform": ( translation: (-5.8, 0.6, 4.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (5), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (5), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.9, g: 0.9, @@ -282,33 +267,31 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Sphere( radius: 0.6, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Sphere, size: (1.2, 1.2, 1.2), ), - }, - ), - 8589934460: ( - components: { - "bevy_ecs::name::Name": "ShowcaseSphere", - "bevy_transform::components::transform::Transform": ( + }), + 8589934460: (components: { + "bevy_ecs::name::Name": "ShowcaseSphere", + "bevy_transform::components::transform::Transform": ( translation: (-8.0, 0.6, 4.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (6), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (6), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.9, g: 0.9, @@ -322,33 +305,31 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Sphere( radius: 0.6, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Sphere, size: (1.2, 1.2, 1.2), ), - }, - ), - 8589934461: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( + }), + 8589934461: (components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( translation: (12.0, 0.90000004, 9.5), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (7), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (7), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.55, g: 0.52, @@ -362,7 +343,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 5.0, @@ -370,27 +351,25 @@ z_length: 0.5, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (5.0, 1.8000001, 0.5), ), - }, - ), - 8589934462: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( + }), + 8589934462: (components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( translation: (12.0, 0.81000006, 10.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (8), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (8), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.55, g: 0.52, @@ -404,7 +383,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 5.0, @@ -412,27 +391,25 @@ z_length: 0.5, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (5.0, 1.6200001, 0.5), ), - }, - ), - 8589934463: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( + }), + 8589934463: (components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( translation: (12.0, 0.72, 10.5), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (9), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (9), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.55, g: 0.52, @@ -446,7 +423,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 5.0, @@ -454,27 +431,25 @@ z_length: 0.5, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (5.0, 1.44, 0.5), ), - }, - ), - 8589934464: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( + }), + 8589934464: (components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( translation: (12.0, 0.63, 11.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (10), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (10), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.55, g: 0.52, @@ -488,7 +463,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 5.0, @@ -496,27 +471,25 @@ z_length: 0.5, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (5.0, 1.26, 0.5), ), - }, - ), - 8589934465: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( + }), + 8589934465: (components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( translation: (12.0, 0.54, 11.5), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (11), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (11), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.55, g: 0.52, @@ -530,7 +503,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 5.0, @@ -538,27 +511,25 @@ z_length: 0.5, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (5.0, 1.08, 0.5), ), - }, - ), - 8589934466: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( + }), + 8589934466: (components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( translation: (12.0, 0.45000002, 12.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (12), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (12), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.55, g: 0.52, @@ -572,7 +543,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 5.0, @@ -580,27 +551,25 @@ z_length: 0.5, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (5.0, 0.90000004, 0.5), ), - }, - ), - 8589934467: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( + }), + 8589934467: (components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( translation: (12.0, 0.36, 12.5), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (13), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (13), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.55, g: 0.52, @@ -614,7 +583,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 5.0, @@ -622,27 +591,25 @@ z_length: 0.5, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (5.0, 0.72, 0.5), ), - }, - ), - 8589934468: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( + }), + 8589934468: (components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( translation: (12.0, 0.27, 13.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (14), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (14), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.55, g: 0.52, @@ -656,7 +623,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 5.0, @@ -664,27 +631,25 @@ z_length: 0.5, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (5.0, 0.54, 0.5), ), - }, - ), - 8589934469: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( + }), + 8589934469: (components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( translation: (12.0, 0.18, 13.5), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (15), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (15), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.55, g: 0.52, @@ -698,7 +663,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 5.0, @@ -706,27 +671,25 @@ z_length: 0.5, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (5.0, 0.36, 0.5), ), - }, - ), - 8589934470: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( + }), + 8589934470: (components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( translation: (12.0, 0.09, 14.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (16), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (16), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.55, g: 0.52, @@ -740,7 +703,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 5.0, @@ -748,27 +711,25 @@ z_length: 0.5, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (5.0, 0.18, 0.5), ), - }, - ), - 8589934471: ( - components: { - "bevy_ecs::name::Name": "Ramp", - "bevy_transform::components::transform::Transform": ( + }), + 8589934471: (components: { + "bevy_ecs::name::Name": "Ramp", + "bevy_transform::components::transform::Transform": ( translation: (-14.0, 1.6, 0.0), rotation: (-0.17364821, 0.0, 0.0, 0.9848078), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (17), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (17), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.3, g: 0.45, @@ -782,7 +743,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 5.0, @@ -790,27 +751,25 @@ z_length: 9.0, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (5.0, 0.4, 9.0), ), - }, - ), - 8589934472: ( - components: { - "bevy_ecs::name::Name": "Crate", - "bevy_transform::components::transform::Transform": ( + }), + 8589934472: (components: { + "bevy_ecs::name::Name": "Crate", + "bevy_transform::components::transform::Transform": ( translation: (5.4, 0.5, 2.4), rotation: (0.0, 0.94948566, 0.0, -0.31381062), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (18), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (18), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.72, g: 0.45, @@ -824,7 +783,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 1.0, @@ -832,27 +791,25 @@ z_length: 1.0, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (1.0, 1.0, 1.0), ), - }, - ), - 8589934473: ( - components: { - "bevy_ecs::name::Name": "Crate", - "bevy_transform::components::transform::Transform": ( + }), + 8589934473: (components: { + "bevy_ecs::name::Name": "Crate", + "bevy_transform::components::transform::Transform": ( translation: (4.0, 0.75, 2.0), rotation: (0.0, 0.9854498, 0.0, 0.16996713), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (19), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (19), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.72, g: 0.45, @@ -866,7 +823,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 1.5, @@ -874,27 +831,25 @@ z_length: 1.5, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (1.5, 1.5, 1.5), ), - }, - ), - 8589934474: ( - components: { - "bevy_ecs::name::Name": "Crate", - "bevy_transform::components::transform::Transform": ( + }), + 8589934474: (components: { + "bevy_ecs::name::Name": "Crate", + "bevy_transform::components::transform::Transform": ( translation: (-2.3, 0.5, -3.1), rotation: (0.0, 0.72083056, 0.0, -0.6931113), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (20), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (20), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.72, g: 0.45, @@ -908,7 +863,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 1.0, @@ -916,27 +871,25 @@ z_length: 1.0, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (1.0, 1.0, 1.0), ), - }, - ), - 8589934475: ( - components: { - "bevy_ecs::name::Name": "Crate", - "bevy_transform::components::transform::Transform": ( + }), + 8589934475: (components: { + "bevy_ecs::name::Name": "Crate", + "bevy_transform::components::transform::Transform": ( translation: (-1.6, 0.5, -3.2), rotation: (0.0, -0.53118616, 0.0, 0.8472551), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (21), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (21), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.72, g: 0.45, @@ -950,7 +903,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 1.0, @@ -958,27 +911,25 @@ z_length: 1.0, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (1.0, 1.0, 1.0), ), - }, - ), - 8589934476: ( - components: { - "bevy_ecs::name::Name": "Crate", - "bevy_transform::components::transform::Transform": ( + }), + 8589934476: (components: { + "bevy_ecs::name::Name": "Crate", + "bevy_transform::components::transform::Transform": ( translation: (-3.0, 0.5, -3.0), rotation: (0.0, 0.86742324, 0.0, -0.49757105), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (22), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (22), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.72, g: 0.45, @@ -992,7 +943,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 1.0, @@ -1000,27 +951,25 @@ z_length: 1.0, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (1.0, 1.0, 1.0), ), - }, - ), - 8589934477: ( - components: { - "bevy_ecs::name::Name": "Pillar", - "bevy_transform::components::transform::Transform": ( + }), + 8589934477: (components: { + "bevy_ecs::name::Name": "Pillar", + "bevy_transform::components::transform::Transform": ( translation: (8.0, 3.5, 6.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (23), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (23), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.85, g: 0.85, @@ -1034,7 +983,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 1.2, @@ -1042,27 +991,25 @@ z_length: 1.2, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (1.2, 7.0, 1.2), ), - }, - ), - 8589934478: ( - components: { - "bevy_ecs::name::Name": "Pillar", - "bevy_transform::components::transform::Transform": ( + }), + 8589934478: (components: { + "bevy_ecs::name::Name": "Pillar", + "bevy_transform::components::transform::Transform": ( translation: (-8.0, 3.5, 6.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (24), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (24), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.85, g: 0.85, @@ -1076,7 +1023,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 1.2, @@ -1084,27 +1031,25 @@ z_length: 1.2, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (1.2, 7.0, 1.2), ), - }, - ), - 8589934479: ( - components: { - "bevy_ecs::name::Name": "Pillar", - "bevy_transform::components::transform::Transform": ( + }), + 8589934479: (components: { + "bevy_ecs::name::Name": "Pillar", + "bevy_transform::components::transform::Transform": ( translation: (8.0, 3.5, -6.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (25), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (25), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.85, g: 0.85, @@ -1118,7 +1063,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 1.2, @@ -1126,27 +1071,25 @@ z_length: 1.2, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (1.2, 7.0, 1.2), ), - }, - ), - 8589934480: ( - components: { - "bevy_ecs::name::Name": "Pillar", - "bevy_transform::components::transform::Transform": ( + }), + 8589934480: (components: { + "bevy_ecs::name::Name": "Pillar", + "bevy_transform::components::transform::Transform": ( translation: (-8.0, 3.5, -6.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (26), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (26), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.85, g: 0.85, @@ -1160,7 +1103,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 1.2, @@ -1168,11 +1111,10 @@ z_length: 1.2, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (1.2, 7.0, 1.2), ), - }, - ), - }, + }), + }, ) \ No newline at end of file diff --git a/assets/levels/editor_scene 3.scn.ron b/assets/levels/editor_scene 3.scn.ron index a467119..78ed583 100644 --- a/assets/levels/editor_scene 3.scn.ron +++ b/assets/levels/editor_scene 3.scn.ron @@ -1,21 +1,20 @@ -(schema_version: 2, - resources: {}, - entities: { - 4294967124: ( - components: { - "bevy_ecs::name::Name": "Cube", - "bevy_transform::components::transform::Transform": ( +(schema_version: 4,resources: { + }, + entities: { + 4294967124: (components: { + "bevy_ecs::name::Name": "Cube", + "bevy_transform::components::transform::Transform": ( translation: (-14.0, 0.66941494, -1.9732417), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (29), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (29), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.8, g: 0.8, @@ -37,7 +36,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 1.0, @@ -45,27 +44,25 @@ z_length: 1.0, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (1.0, 1.0, 1.0), ), - }, - ), - 4294967126: ( - components: { - "bevy_ecs::name::Name": "Cube", - "bevy_transform::components::transform::Transform": ( + }), + 4294967126: (components: { + "bevy_ecs::name::Name": "Cube", + "bevy_transform::components::transform::Transform": ( translation: (-13.0, 0.5, 2.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (28), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (28), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.8, g: 0.8, @@ -87,7 +84,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 1.0, @@ -95,27 +92,25 @@ z_length: 1.0, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (1.0, 1.0, 1.0), ), - }, - ), - 4294967128: ( - components: { - "bevy_ecs::name::Name": "Point Light", - "bevy_transform::components::transform::Transform": ( + }), + 4294967128: (components: { + "bevy_ecs::name::Name": "Point Light", + "bevy_transform::components::transform::Transform": ( translation: (-10.83656, 1.680895, -1.0713063), rotation: (0.0, 0.0, 0.0, 1.0), scale: (2.7062726, 1.0, 1.0), ), - "shared::components::ActorKind": Light, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": Light, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (27), - "shared::components::LevelObject": (), - "shared::components::LightDesc": ( + "shared::components::HierarchySiblingIndex": (27), + "shared::components::LevelObject": (), + "shared::components::LightDesc": ( kind: Point, color: ( r: 1.0, @@ -129,23 +124,21 @@ inner_angle_deg: 25.0, outer_angle_deg: 35.0, ), - }, - ), - 4294967158: ( - components: { - "bevy_ecs::name::Name": "Scene Sun", - "bevy_transform::components::transform::Transform": ( + }), + 4294967158: (components: { + "bevy_ecs::name::Name": "Scene Sun", + "bevy_transform::components::transform::Transform": ( translation: (0.0, 5.316672, 0.0), rotation: (0.11260023, -0.04732944, 0.7427888, 0.65829015), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": Light, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": Light, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (0), - "shared::components::LevelObject": (), - "shared::components::LightDesc": ( + "shared::components::HierarchySiblingIndex": (0), + "shared::components::LevelObject": (), + "shared::components::LightDesc": ( kind: Directional, color: ( r: 1.0, @@ -159,23 +152,21 @@ inner_angle_deg: 25.0, outer_angle_deg: 35.0, ), - }, - ), - 4294967159: ( - components: { - "bevy_ecs::name::Name": "Ground", - "bevy_transform::components::transform::Transform": ( + }), + 4294967159: (components: { + "bevy_ecs::name::Name": "Ground", + "bevy_transform::components::transform::Transform": ( translation: (0.0, -0.5, 0.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (1), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (1), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.42, g: 0.45, @@ -197,7 +188,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 200.0, @@ -205,27 +196,25 @@ z_length: 200.0, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (200.0, 1.0, 200.0), ), - }, - ), - 8589934417: ( - components: { - "bevy_ecs::name::Name": "Spot Light", - "bevy_transform::components::transform::Transform": ( + }), + 8589934417: (components: { + "bevy_ecs::name::Name": "Spot Light", + "bevy_transform::components::transform::Transform": ( translation: (-13.387727, 0.9319196, -5.454666), rotation: (-0.1494833, 0.0, 0.0, 0.9887642), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": Light, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": Light, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (30), - "shared::components::LevelObject": (), - "shared::components::LightDesc": ( + "shared::components::HierarchySiblingIndex": (30), + "shared::components::LevelObject": (), + "shared::components::LightDesc": ( kind: Spot, color: ( r: 1.0, @@ -239,40 +228,36 @@ inner_angle_deg: 25.0, outer_angle_deg: 35.0, ), - }, - ), - 8589934456: ( - components: { - "bevy_ecs::name::Name": "Player Start", - "bevy_transform::components::transform::Transform": ( + }), + 8589934456: (components: { + "bevy_ecs::name::Name": "Player Start", + "bevy_transform::components::transform::Transform": ( translation: (0.0, 0.9900649, 12.46175), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": PlayerSpawn, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": PlayerSpawn, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (2), - "shared::components::LevelObject": (), - "shared::components::PlayerSpawn": (), - }, - ), - 8589934457: ( - components: { - "bevy_ecs::name::Name": "ShowcaseSphere", - "bevy_transform::components::transform::Transform": ( + "shared::components::HierarchySiblingIndex": (2), + "shared::components::LevelObject": (), + "shared::components::PlayerSpawn": (), + }), + 8589934457: (components: { + "bevy_ecs::name::Name": "ShowcaseSphere", + "bevy_transform::components::transform::Transform": ( translation: (-1.3999996, 0.6, 4.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (3), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (3), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.9, g: 0.9, @@ -294,33 +279,31 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Sphere( radius: 0.6, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Sphere, size: (1.2, 1.2, 1.2), ), - }, - ), - 8589934458: ( - components: { - "bevy_ecs::name::Name": "ShowcaseSphere", - "bevy_transform::components::transform::Transform": ( + }), + 8589934458: (components: { + "bevy_ecs::name::Name": "ShowcaseSphere", + "bevy_transform::components::transform::Transform": ( translation: (-3.6, 0.6, 4.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (4), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (4), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.9, g: 0.9, @@ -342,33 +325,31 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Sphere( radius: 0.6, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Sphere, size: (1.2, 1.2, 1.2), ), - }, - ), - 8589934459: ( - components: { - "bevy_ecs::name::Name": "ShowcaseSphere", - "bevy_transform::components::transform::Transform": ( + }), + 8589934459: (components: { + "bevy_ecs::name::Name": "ShowcaseSphere", + "bevy_transform::components::transform::Transform": ( translation: (-5.8, 0.6, 4.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (5), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (5), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.9, g: 0.9, @@ -390,33 +371,31 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Sphere( radius: 0.6, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Sphere, size: (1.2, 1.2, 1.2), ), - }, - ), - 8589934460: ( - components: { - "bevy_ecs::name::Name": "ShowcaseSphere", - "bevy_transform::components::transform::Transform": ( + }), + 8589934460: (components: { + "bevy_ecs::name::Name": "ShowcaseSphere", + "bevy_transform::components::transform::Transform": ( translation: (-8.0, 0.6, 4.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (6), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (6), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.9, g: 0.9, @@ -438,33 +417,31 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Sphere( radius: 0.6, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Sphere, size: (1.2, 1.2, 1.2), ), - }, - ), - 8589934461: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( + }), + 8589934461: (components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( translation: (12.0, 0.90000004, 9.5), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (7), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (7), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.55, g: 0.52, @@ -486,7 +463,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 5.0, @@ -494,27 +471,25 @@ z_length: 0.5, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (5.0, 1.8000001, 0.5), ), - }, - ), - 8589934462: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( + }), + 8589934462: (components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( translation: (12.0, 0.81000006, 10.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (8), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (8), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.55, g: 0.52, @@ -536,7 +511,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 5.0, @@ -544,27 +519,25 @@ z_length: 0.5, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (5.0, 1.6200001, 0.5), ), - }, - ), - 8589934463: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( + }), + 8589934463: (components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( translation: (12.0, 0.72, 10.5), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (9), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (9), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.55, g: 0.52, @@ -586,7 +559,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 5.0, @@ -594,27 +567,25 @@ z_length: 0.5, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (5.0, 1.44, 0.5), ), - }, - ), - 8589934464: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( + }), + 8589934464: (components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( translation: (12.0, 0.63, 11.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (10), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (10), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.55, g: 0.52, @@ -636,7 +607,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 5.0, @@ -644,27 +615,25 @@ z_length: 0.5, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (5.0, 1.26, 0.5), ), - }, - ), - 8589934465: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( + }), + 8589934465: (components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( translation: (12.0, 0.54, 11.5), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (11), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (11), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.55, g: 0.52, @@ -686,7 +655,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 5.0, @@ -694,27 +663,25 @@ z_length: 0.5, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (5.0, 1.08, 0.5), ), - }, - ), - 8589934466: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( + }), + 8589934466: (components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( translation: (12.0, 0.45000002, 12.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (12), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (12), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.55, g: 0.52, @@ -736,7 +703,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 5.0, @@ -744,27 +711,25 @@ z_length: 0.5, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (5.0, 0.90000004, 0.5), ), - }, - ), - 8589934467: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( + }), + 8589934467: (components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( translation: (12.0, 0.36, 12.5), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (13), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (13), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.55, g: 0.52, @@ -786,7 +751,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 5.0, @@ -794,27 +759,25 @@ z_length: 0.5, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (5.0, 0.72, 0.5), ), - }, - ), - 8589934468: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( + }), + 8589934468: (components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( translation: (12.0, 0.27, 13.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (14), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (14), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.55, g: 0.52, @@ -836,7 +799,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 5.0, @@ -844,27 +807,25 @@ z_length: 0.5, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (5.0, 0.54, 0.5), ), - }, - ), - 8589934469: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( + }), + 8589934469: (components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( translation: (12.0, 0.18, 13.5), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (15), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (15), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.55, g: 0.52, @@ -886,7 +847,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 5.0, @@ -894,27 +855,25 @@ z_length: 0.5, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (5.0, 0.36, 0.5), ), - }, - ), - 8589934470: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( + }), + 8589934470: (components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( translation: (12.0, 0.09, 14.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (16), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (16), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.55, g: 0.52, @@ -936,7 +895,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 5.0, @@ -944,27 +903,25 @@ z_length: 0.5, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (5.0, 0.18, 0.5), ), - }, - ), - 8589934471: ( - components: { - "bevy_ecs::name::Name": "Ramp", - "bevy_transform::components::transform::Transform": ( + }), + 8589934471: (components: { + "bevy_ecs::name::Name": "Ramp", + "bevy_transform::components::transform::Transform": ( translation: (-14.0, 2.292737, 0.0), rotation: (-0.17364821, 0.0, 0.0, 0.9848078), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (17), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (17), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.3, g: 0.45, @@ -986,7 +943,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 5.0, @@ -994,27 +951,25 @@ z_length: 9.0, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (5.0, 0.4, 9.0), ), - }, - ), - 8589934472: ( - components: { - "bevy_ecs::name::Name": "Crate", - "bevy_transform::components::transform::Transform": ( + }), + 8589934472: (components: { + "bevy_ecs::name::Name": "Crate", + "bevy_transform::components::transform::Transform": ( translation: (5.4, 0.5, 2.4), rotation: (0.0, 0.94948566, 0.0, -0.31381062), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (18), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (18), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.72, g: 0.45, @@ -1036,7 +991,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 1.0, @@ -1044,27 +999,25 @@ z_length: 1.0, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (1.0, 1.0, 1.0), ), - }, - ), - 8589934473: ( - components: { - "bevy_ecs::name::Name": "Crate", - "bevy_transform::components::transform::Transform": ( + }), + 8589934473: (components: { + "bevy_ecs::name::Name": "Crate", + "bevy_transform::components::transform::Transform": ( translation: (4.0, 0.75, 2.0), rotation: (0.0, 0.9854498, 0.0, 0.16996713), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (19), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (19), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.72, g: 0.45, @@ -1086,7 +1039,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 1.5, @@ -1094,27 +1047,25 @@ z_length: 1.5, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (1.5, 1.5, 1.5), ), - }, - ), - 8589934474: ( - components: { - "bevy_ecs::name::Name": "Crate", - "bevy_transform::components::transform::Transform": ( + }), + 8589934474: (components: { + "bevy_ecs::name::Name": "Crate", + "bevy_transform::components::transform::Transform": ( translation: (-2.3, 0.5, -3.1), rotation: (0.0, 0.72083056, 0.0, -0.6931113), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (20), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (20), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.72, g: 0.45, @@ -1136,7 +1087,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 1.0, @@ -1144,27 +1095,25 @@ z_length: 1.0, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (1.0, 1.0, 1.0), ), - }, - ), - 8589934475: ( - components: { - "bevy_ecs::name::Name": "Crate", - "bevy_transform::components::transform::Transform": ( + }), + 8589934475: (components: { + "bevy_ecs::name::Name": "Crate", + "bevy_transform::components::transform::Transform": ( translation: (-1.6, 0.5, -3.2), rotation: (0.0, -0.53118616, 0.0, 0.8472551), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (21), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (21), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.72, g: 0.45, @@ -1186,7 +1135,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 1.0, @@ -1194,27 +1143,25 @@ z_length: 1.0, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (1.0, 1.0, 1.0), ), - }, - ), - 8589934476: ( - components: { - "bevy_ecs::name::Name": "Crate", - "bevy_transform::components::transform::Transform": ( + }), + 8589934476: (components: { + "bevy_ecs::name::Name": "Crate", + "bevy_transform::components::transform::Transform": ( translation: (-3.0, 0.5, -3.0), rotation: (0.0, 0.86742324, 0.0, -0.49757105), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (22), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (22), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.72, g: 0.45, @@ -1236,7 +1183,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 1.0, @@ -1244,27 +1191,25 @@ z_length: 1.0, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (1.0, 1.0, 1.0), ), - }, - ), - 8589934477: ( - components: { - "bevy_ecs::name::Name": "Pillar", - "bevy_transform::components::transform::Transform": ( + }), + 8589934477: (components: { + "bevy_ecs::name::Name": "Pillar", + "bevy_transform::components::transform::Transform": ( translation: (8.0, 3.5, 6.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (23), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (23), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.85, g: 0.85, @@ -1286,7 +1231,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 1.2, @@ -1294,27 +1239,25 @@ z_length: 1.2, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (1.2, 7.0, 1.2), ), - }, - ), - 8589934478: ( - components: { - "bevy_ecs::name::Name": "Pillar", - "bevy_transform::components::transform::Transform": ( + }), + 8589934478: (components: { + "bevy_ecs::name::Name": "Pillar", + "bevy_transform::components::transform::Transform": ( translation: (-8.0, 3.5, 6.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (24), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (24), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.85, g: 0.85, @@ -1336,7 +1279,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 1.2, @@ -1344,27 +1287,25 @@ z_length: 1.2, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (1.2, 7.0, 1.2), ), - }, - ), - 8589934479: ( - components: { - "bevy_ecs::name::Name": "Pillar", - "bevy_transform::components::transform::Transform": ( + }), + 8589934479: (components: { + "bevy_ecs::name::Name": "Pillar", + "bevy_transform::components::transform::Transform": ( translation: (8.0, 3.5, -6.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (25), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (25), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.85, g: 0.85, @@ -1386,7 +1327,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 1.2, @@ -1394,27 +1335,25 @@ z_length: 1.2, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (1.2, 7.0, 1.2), ), - }, - ), - 8589934480: ( - components: { - "bevy_ecs::name::Name": "Pillar", - "bevy_transform::components::transform::Transform": ( + }), + 8589934480: (components: { + "bevy_ecs::name::Name": "Pillar", + "bevy_transform::components::transform::Transform": ( translation: (-8.0, 3.5, -6.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (26), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (26), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.85, g: 0.85, @@ -1436,7 +1375,7 @@ metallic_roughness_texture: None, material_asset_path: None, ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 1.2, @@ -1444,27 +1383,25 @@ z_length: 1.2, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (1.2, 7.0, 1.2), ), - }, - ), - 12884901711: ( - components: { - "bevy_ecs::name::Name": "Cube", - "bevy_transform::components::transform::Transform": ( + }), + 12884901711: (components: { + "bevy_ecs::name::Name": "Cube", + "bevy_transform::components::transform::Transform": ( translation: (-13.391208, 0.5, -6.673126), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (31), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (31), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( base_color: ( r: 0.7322267, g: 0.0, @@ -1486,7 +1423,7 @@ metallic_roughness_texture: None, material_asset_path: Some("assets/materials/concrete.ron"), ), - "shared::components::PhysicsBody": ( + "shared::components::PhysicsBody": ( body: Static, collider: Cuboid( x_length: 1.0, @@ -1494,11 +1431,10 @@ z_length: 1.0, ), ), - "shared::components::Primitive": ( + "shared::components::Primitive": ( shape: Box, size: (1.0, 1.0, 1.0), ), - }, - ), - }, + }), + }, ) \ No newline at end of file diff --git a/assets/levels/editor_scene.scn.ron b/assets/levels/editor_scene.scn.ron index 5055b76..4a58538 100644 --- a/assets/levels/editor_scene.scn.ron +++ b/assets/levels/editor_scene.scn.ron @@ -1,1075 +1,30 @@ -(schema_version: 2, - resources: {}, - entities: { - 4294967158: ( - components: { - "bevy_ecs::name::Name": "Scene Sun", - "bevy_transform::components::transform::Transform": ( - translation: (0.0, 5.316672, 0.0), - rotation: (-0.21401538, 0.31161797, 0.67592126, 0.6326311), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::ActorKind": Light, - "shared::components::EditorVisibility": ( - visible: true, - ), - "shared::components::HierarchySiblingIndex": (0), - "shared::components::LevelObject": (), - "shared::components::LightDesc": ( - kind: Directional, - color: ( - r: 1.0, - g: 0.95, - b: 0.85, - a: 1.0, - ), - intensity: 100000.0, - range: 0.0, - shadows: true, - inner_angle_deg: 25.0, - outer_angle_deg: 35.0, - ), - }, - ), - 4294967159: ( - components: { - "bevy_ecs::name::Name": "Ground", - "bevy_transform::components::transform::Transform": ( - translation: (0.0, -0.5, 0.0), +(schema_version: 4,resources: { + "shared::components::SceneComposition": (scene_id:"legacy:assets/levels/editor_scene.scn.ron",subscenes:[]), + }, + entities: { + 4294966587: (components: { + "bevy_ecs::name::Name": "Brush Part 2", + "bevy_transform::components::transform::Transform": ( + translation: (25.75, 0.0, 9.5), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( - visible: true, - ), - "shared::components::HierarchySiblingIndex": (1), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - shader: ( - kind: StandardLit, - schema_path: None, - shader_path: None, - ), - base_color: ( - r: 0.42, - g: 0.45, - b: 0.48, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.92, - emissive_color: ( - r: 1.0, - g: 1.0, - b: 1.0, - a: 1.0, - ), - emissive_intensity: 0.0, - base_color_texture: None, - emissive_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - material_asset_path: None, - parameters: [], - textures: [], - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 200.0, - y_length: 1.0, - z_length: 200.0, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (200.0, 1.0, 200.0), - ), - }, - ), - 8589934456: ( - components: { - "bevy_ecs::name::Name": "Player Start", - "bevy_transform::components::transform::Transform": ( - translation: (0.0, 0.9900649, 12.46175), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::ActorKind": PlayerSpawn, - "shared::components::EditorVisibility": ( - visible: true, - ), - "shared::components::HierarchySiblingIndex": (2), - "shared::components::LevelObject": (), - "shared::components::PlayerSpawn": (), - }, - ), - 8589934457: ( - components: { - "bevy_ecs::name::Name": "ShowcaseSphere", - "bevy_transform::components::transform::Transform": ( - translation: (-1.3999996, 0.6, 4.0), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( - visible: true, - ), - "shared::components::HierarchySiblingIndex": (3), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - shader: ( - kind: StandardLit, - schema_path: None, - shader_path: None, - ), - base_color: ( - r: 0.9, - g: 0.9, - b: 0.92, - a: 1.0, - ), - metallic: 1.0, - roughness: 0.95, - emissive_color: ( - r: 1.0, - g: 1.0, - b: 1.0, - a: 1.0, - ), - emissive_intensity: 0.0, - base_color_texture: None, - emissive_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - material_asset_path: None, - parameters: [], - textures: [], - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Sphere( - radius: 0.6, - ), - ), - "shared::components::Primitive": ( - shape: Sphere, - size: (1.2, 1.2, 1.2), - ), - }, - ), - 8589934458: ( - components: { - "bevy_ecs::name::Name": "ShowcaseSphere", - "bevy_transform::components::transform::Transform": ( - translation: (-3.6, 0.6, 4.0), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( - visible: true, - ), - "shared::components::HierarchySiblingIndex": (4), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - shader: ( - kind: StandardLit, - schema_path: None, - shader_path: None, - ), - base_color: ( - r: 0.9, - g: 0.9, - b: 0.92, - a: 1.0, - ), - metallic: 1.0, - roughness: 0.6, - emissive_color: ( - r: 1.0, - g: 1.0, - b: 1.0, - a: 1.0, - ), - emissive_intensity: 0.0, - base_color_texture: None, - emissive_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - material_asset_path: None, - parameters: [], - textures: [], - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Sphere( - radius: 0.6, - ), - ), - "shared::components::Primitive": ( - shape: Sphere, - size: (1.2, 1.2, 1.2), - ), - }, - ), - 8589934459: ( - components: { - "bevy_ecs::name::Name": "ShowcaseSphere", - "bevy_transform::components::transform::Transform": ( - translation: (-5.8, 0.6, 4.0), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( - visible: true, - ), - "shared::components::HierarchySiblingIndex": (5), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - shader: ( - kind: StandardLit, - schema_path: None, - shader_path: None, - ), - base_color: ( - r: 0.9, - g: 0.9, - b: 0.92, - a: 1.0, - ), - metallic: 1.0, - roughness: 0.3, - emissive_color: ( - r: 1.0, - g: 1.0, - b: 1.0, - a: 1.0, - ), - emissive_intensity: 0.0, - base_color_texture: None, - emissive_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - material_asset_path: None, - parameters: [], - textures: [], - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Sphere( - radius: 0.6, - ), - ), - "shared::components::Primitive": ( - shape: Sphere, - size: (1.2, 1.2, 1.2), - ), - }, - ), - 8589934460: ( - components: { - "bevy_ecs::name::Name": "ShowcaseSphere", - "bevy_transform::components::transform::Transform": ( - translation: (-8.0, 0.6, 4.0), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( - visible: true, - ), - "shared::components::HierarchySiblingIndex": (6), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - shader: ( - kind: StandardLit, - schema_path: None, - shader_path: None, - ), - base_color: ( - r: 0.9, - g: 0.9, - b: 0.92, - a: 1.0, - ), - metallic: 1.0, - roughness: 0.05, - emissive_color: ( - r: 1.0, - g: 1.0, - b: 1.0, - a: 1.0, - ), - emissive_intensity: 0.0, - base_color_texture: None, - emissive_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - material_asset_path: None, - parameters: [], - textures: [], - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Sphere( - radius: 0.6, - ), - ), - "shared::components::Primitive": ( - shape: Sphere, - size: (1.2, 1.2, 1.2), - ), - }, - ), - 8589934461: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( - translation: (12.0, 0.90000004, 9.5), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( - visible: true, - ), - "shared::components::HierarchySiblingIndex": (7), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - shader: ( - kind: StandardLit, - schema_path: None, - shader_path: None, - ), - base_color: ( - r: 0.55, - g: 0.52, - b: 0.48, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.8, - emissive_color: ( - r: 1.0, - g: 1.0, - b: 1.0, - a: 1.0, - ), - emissive_intensity: 0.0, - base_color_texture: None, - emissive_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - material_asset_path: None, - parameters: [], - textures: [], - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 5.0, - y_length: 1.8000001, - z_length: 0.5, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (5.0, 1.8000001, 0.5), - ), - }, - ), - 8589934462: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( - translation: (12.0, 0.81000006, 10.0), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( - visible: true, - ), - "shared::components::HierarchySiblingIndex": (8), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - shader: ( - kind: StandardLit, - schema_path: None, - shader_path: None, - ), - base_color: ( - r: 0.55, - g: 0.52, - b: 0.48, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.8, - emissive_color: ( - r: 1.0, - g: 1.0, - b: 1.0, - a: 1.0, - ), - emissive_intensity: 0.0, - base_color_texture: None, - emissive_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - material_asset_path: None, - parameters: [], - textures: [], - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 5.0, - y_length: 1.6200001, - z_length: 0.5, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (5.0, 1.6200001, 0.5), - ), - }, - ), - 8589934463: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( - translation: (12.0, 0.72, 10.5), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( - visible: true, - ), - "shared::components::HierarchySiblingIndex": (9), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - shader: ( - kind: StandardLit, - schema_path: None, - shader_path: None, - ), - base_color: ( - r: 0.55, - g: 0.52, - b: 0.48, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.8, - emissive_color: ( - r: 1.0, - g: 1.0, - b: 1.0, - a: 1.0, - ), - emissive_intensity: 0.0, - base_color_texture: None, - emissive_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - material_asset_path: None, - parameters: [], - textures: [], - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 5.0, - y_length: 1.44, - z_length: 0.5, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (5.0, 1.44, 0.5), - ), - }, - ), - 8589934464: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( - translation: (12.0, 0.63, 11.0), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( - visible: true, - ), - "shared::components::HierarchySiblingIndex": (10), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - shader: ( - kind: StandardLit, - schema_path: None, - shader_path: None, - ), - base_color: ( - r: 0.55, - g: 0.52, - b: 0.48, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.8, - emissive_color: ( - r: 1.0, - g: 1.0, - b: 1.0, - a: 1.0, - ), - emissive_intensity: 0.0, - base_color_texture: None, - emissive_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - material_asset_path: None, - parameters: [], - textures: [], - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 5.0, - y_length: 1.26, - z_length: 0.5, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (5.0, 1.26, 0.5), - ), - }, - ), - 8589934465: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( - translation: (12.0, 0.54, 11.5), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( - visible: true, - ), - "shared::components::HierarchySiblingIndex": (11), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - shader: ( - kind: StandardLit, - schema_path: None, - shader_path: None, - ), - base_color: ( - r: 0.55, - g: 0.52, - b: 0.48, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.8, - emissive_color: ( - r: 1.0, - g: 1.0, - b: 1.0, - a: 1.0, - ), - emissive_intensity: 0.0, - base_color_texture: None, - emissive_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - material_asset_path: None, - parameters: [], - textures: [], - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 5.0, - y_length: 1.08, - z_length: 0.5, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (5.0, 1.08, 0.5), - ), - }, - ), - 8589934466: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( - translation: (12.0, 0.45000002, 12.0), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( - visible: true, - ), - "shared::components::HierarchySiblingIndex": (12), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - shader: ( - kind: StandardLit, - schema_path: None, - shader_path: None, - ), - base_color: ( - r: 0.55, - g: 0.52, - b: 0.48, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.8, - emissive_color: ( - r: 1.0, - g: 1.0, - b: 1.0, - a: 1.0, - ), - emissive_intensity: 0.0, - base_color_texture: None, - emissive_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - material_asset_path: None, - parameters: [], - textures: [], - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 5.0, - y_length: 0.90000004, - z_length: 0.5, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (5.0, 0.90000004, 0.5), - ), - }, - ), - 8589934467: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( - translation: (12.0, 0.36, 12.5), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( - visible: true, - ), - "shared::components::HierarchySiblingIndex": (13), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - shader: ( - kind: StandardLit, - schema_path: None, - shader_path: None, - ), - base_color: ( - r: 0.55, - g: 0.52, - b: 0.48, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.8, - emissive_color: ( - r: 1.0, - g: 1.0, - b: 1.0, - a: 1.0, - ), - emissive_intensity: 0.0, - base_color_texture: None, - emissive_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - material_asset_path: None, - parameters: [], - textures: [], - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 5.0, - y_length: 0.72, - z_length: 0.5, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (5.0, 0.72, 0.5), - ), - }, - ), - 8589934468: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( - translation: (12.0, 0.27, 13.0), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( - visible: true, - ), - "shared::components::HierarchySiblingIndex": (14), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - shader: ( - kind: StandardLit, - schema_path: None, - shader_path: None, - ), - base_color: ( - r: 0.55, - g: 0.52, - b: 0.48, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.8, - emissive_color: ( - r: 1.0, - g: 1.0, - b: 1.0, - a: 1.0, - ), - emissive_intensity: 0.0, - base_color_texture: None, - emissive_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - material_asset_path: None, - parameters: [], - textures: [], - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 5.0, - y_length: 0.54, - z_length: 0.5, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (5.0, 0.54, 0.5), - ), - }, - ), - 8589934469: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( - translation: (12.0, 0.18, 13.5), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( - visible: true, - ), - "shared::components::HierarchySiblingIndex": (15), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - shader: ( - kind: StandardLit, - schema_path: None, - shader_path: None, - ), - base_color: ( - r: 0.55, - g: 0.52, - b: 0.48, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.8, - emissive_color: ( - r: 1.0, - g: 1.0, - b: 1.0, - a: 1.0, - ), - emissive_intensity: 0.0, - base_color_texture: None, - emissive_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - material_asset_path: None, - parameters: [], - textures: [], - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 5.0, - y_length: 0.36, - z_length: 0.5, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (5.0, 0.36, 0.5), - ), - }, - ), - 8589934470: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( - translation: (12.0, 0.09, 14.0), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( - visible: true, - ), - "shared::components::HierarchySiblingIndex": (16), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - shader: ( - kind: StandardLit, - schema_path: None, - shader_path: None, - ), - base_color: ( - r: 0.55, - g: 0.52, - b: 0.48, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.8, - emissive_color: ( - r: 1.0, - g: 1.0, - b: 1.0, - a: 1.0, - ), - emissive_intensity: 0.0, - base_color_texture: None, - emissive_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - material_asset_path: None, - parameters: [], - textures: [], - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 5.0, - y_length: 0.18, - z_length: 0.5, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (5.0, 0.18, 0.5), - ), - }, - ), - 8589934471: ( - components: { - "bevy_ecs::name::Name": "Ramp", - "bevy_transform::components::transform::Transform": ( - translation: (-14.0, 1.6, 0.0), - rotation: (-0.17364821, 0.0, 0.0, 0.9848078), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( - visible: true, - ), - "shared::components::HierarchySiblingIndex": (17), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - shader: ( - kind: StandardLit, - schema_path: None, - shader_path: None, - ), - base_color: ( - r: 0.3, - g: 0.45, - b: 0.7, - a: 1.0, - ), - metallic: 0.1, - roughness: 0.55, - emissive_color: ( - r: 1.0, - g: 1.0, - b: 1.0, - a: 1.0, - ), - emissive_intensity: 0.0, - base_color_texture: None, - emissive_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - material_asset_path: None, - parameters: [], - textures: [], - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 5.0, - y_length: 0.4, - z_length: 9.0, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (5.0, 0.4, 9.0), - ), - }, - ), - 8589934472: ( - components: { - "bevy_ecs::name::Name": "Crate", - "bevy_transform::components::transform::Transform": ( - translation: (5.4, 0.5, 2.4), - rotation: (0.0, 0.94948566, 0.0, -0.31381062), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( - visible: true, - ), - "shared::components::HierarchySiblingIndex": (18), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - shader: ( - kind: StandardLit, - schema_path: None, - shader_path: None, - ), - base_color: ( - r: 0.72, - g: 0.45, - b: 0.2, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.85, - emissive_color: ( - r: 1.0, - g: 1.0, - b: 1.0, - a: 1.0, - ), - emissive_intensity: 0.0, - base_color_texture: None, - emissive_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - material_asset_path: None, - parameters: [], - textures: [], - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 1.0, - y_length: 1.0, - z_length: 1.0, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (1.0, 1.0, 1.0), - ), - }, - ), - 8589934473: ( - components: { - "bevy_ecs::name::Name": "Crate", - "bevy_transform::components::transform::Transform": ( - translation: (14.575503, 4.5, -15.5), - rotation: (0.77714586, -0.0, -0.6293205, -0.0), - scale: (8.5, 6.7, 5.6), - ), - "shared::components::ActorKind": Brush, - "shared::components::BrushDesc": ( + "shared::components::ActorId": ("2b066a80-77aa-4b5b-85c2-2cfdd15ad922"), + "shared::components::ActorKind": Brush, + "shared::components::BrushDesc": ( kind: Additive, faces: [ - ( - id: ("face:+x"), - plane: ( - normal: (1.0, 0.0, 0.0), - distance: 0.5, - ), - vertices: [ - (0.5, -0.5, -0.5), - (0.5, 0.5, -0.5), - (0.5, 0.5, 0.5), - (0.5, -0.5, 0.5), - ], - material: None, - texture: None, - uv_offset: (0.0, 0.0), - uv_scale: (1.0, 1.0), - uv_rotation: 0.0, - smoothing_group: 0, - ), - ( - id: ("face:-x"), - plane: ( - normal: (-1.0, 0.0, 0.0), - distance: 0.5, - ), - vertices: [ - (-0.5, -0.5, 0.5), - (-0.5, 0.5, 0.5), - (-0.5, 0.5, -0.5), - (-0.5, -0.5, -0.5), - ], - material: None, - texture: None, - uv_offset: (0.0, 0.0), - uv_scale: (1.0, 1.0), - uv_rotation: 0.0, - smoothing_group: 0, - ), ( id: ("face:+y"), plane: ( normal: (0.0, 1.0, 0.0), - distance: 0.5, + distance: 2.3, ), vertices: [ - (-0.5, 0.5, -0.5), - (-0.5, 0.5, 0.5), - (0.5, 0.5, 0.5), - (0.5, 0.5, -0.5), + (-1.75, 2.3, 0.5), + (0.25, 2.3, 0.5), + (2.25, 2.3, -0.5), + (-0.75, 2.3, -0.5), ], material: None, texture: None, @@ -1082,13 +37,13 @@ id: ("face:-y"), plane: ( normal: (0.0, -1.0, 0.0), - distance: 0.5, + distance: -0.0, ), vertices: [ - (-0.5, -0.5, 0.5), - (-0.5, -0.5, -0.5), - (0.5, -0.5, -0.5), - (0.5, -0.5, 0.5), + (-0.75, 0.0, -0.5), + (2.25, 0.0, -0.5), + (0.25, 0.0, 0.5), + (-1.75, 0.0, 0.5), ], material: None, texture: None, @@ -1098,35 +53,73 @@ smoothing_group: 0, ), ( - id: ("face:+z"), - plane: ( - normal: (0.0, 0.0, 1.0), - distance: 0.5, - ), - vertices: [ - (0.5, -0.5, 0.5), - (0.5, 0.5, 0.5), - (-0.5, 0.5, 0.5), - (-0.5, -0.5, 0.5), - ], - material: None, - texture: None, - uv_offset: (0.0, 0.0), - uv_scale: (1.0, 1.0), - uv_rotation: 0.0, - smoothing_group: 0, - ), - ( - id: ("face:-z"), + id: ("face:side:0"), plane: ( normal: (0.0, 0.0, -1.0), distance: 0.5, ), vertices: [ - (-0.5, -0.5, -0.5), - (-0.5, 0.5, -0.5), - (0.5, 0.5, -0.5), - (0.5, -0.5, -0.5), + (-0.75, 0.0, -0.5), + (-0.75, 2.3, -0.5), + (2.25, 2.3, -0.5), + (2.25, 0.0, -0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ( + id: ("face:side:1"), + plane: ( + normal: (0.4472136, 0.0, 0.8944272), + distance: 0.559017, + ), + vertices: [ + (2.25, 0.0, -0.5), + (2.25, 2.3, -0.5), + (0.25, 2.3, 0.5), + (0.25, 0.0, 0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ( + id: ("face:side:2"), + plane: ( + normal: (0.0, 0.0, 1.0), + distance: 0.5, + ), + vertices: [ + (0.25, 0.0, 0.5), + (0.25, 2.3, 0.5), + (-1.75, 2.3, 0.5), + (-1.75, 0.0, 0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ( + id: ("face:side:3"), + plane: ( + normal: (-0.70710677, 0.0, -0.70710677), + distance: 0.8838835, + ), + vertices: [ + (-1.75, 0.0, 0.5), + (-1.75, 2.3, 0.5), + (-0.75, 2.3, -0.5), + (-0.75, 0.0, -0.5), ], material: None, texture: None, @@ -1139,70 +132,25 @@ cast_shadows: true, receive_shadows: true, ), - "shared::components::EditorVisibility": ( + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (19), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (0), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( shader: ( kind: StandardLit, schema_path: None, shader_path: None, ), base_color: ( - r: 0.55, - g: 0.54, - b: 0.52, + r: 0.8, + g: 0.8, + b: 0.8, a: 1.0, ), metallic: 0.0, - roughness: 0.85, - emissive_color: ( - r: 1.0, - g: 1.0, - b: 1.0, - a: 1.0, - ), - emissive_intensity: 0.0, - base_color_texture: None, - emissive_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - material_asset_path: Some("assets/materials/concrete.ron"), - parameters: [], - textures: [], - ), - }, - ), - 8589934474: ( - components: { - "bevy_ecs::name::Name": "Crate", - "bevy_transform::components::transform::Transform": ( - translation: (-2.3, 0.5, -3.1), - rotation: (0.0, 0.72083056, 0.0, -0.6931113), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( - visible: true, - ), - "shared::components::HierarchySiblingIndex": (20), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - shader: ( - kind: StandardLit, - schema_path: None, - shader_path: None, - ), - base_color: ( - r: 0.72, - g: 0.45, - b: 0.2, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.85, + roughness: 0.65, emissive_color: ( r: 1.0, g: 1.0, @@ -1218,424 +166,30 @@ parameters: [], textures: [], ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 1.0, - y_length: 1.0, - z_length: 1.0, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (1.0, 1.0, 1.0), - ), - }, - ), - 8589934475: ( - components: { - "bevy_ecs::name::Name": "Crate", - "bevy_transform::components::transform::Transform": ( - translation: (-1.6, 0.5, -3.2), - rotation: (0.0, -0.53118616, 0.0, 0.8472551), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( - visible: true, - ), - "shared::components::HierarchySiblingIndex": (21), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - shader: ( - kind: StandardLit, - schema_path: None, - shader_path: None, - ), - base_color: ( - r: 0.72, - g: 0.45, - b: 0.2, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.85, - emissive_color: ( - r: 1.0, - g: 1.0, - b: 1.0, - a: 1.0, - ), - emissive_intensity: 0.0, - base_color_texture: None, - emissive_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - material_asset_path: None, - parameters: [], - textures: [], - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 1.0, - y_length: 1.0, - z_length: 1.0, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (1.0, 1.0, 1.0), - ), - }, - ), - 8589934476: ( - components: { - "bevy_ecs::name::Name": "Crate", - "bevy_transform::components::transform::Transform": ( - translation: (-3.0, 0.5, -3.0), - rotation: (0.0, 0.86742324, 0.0, -0.49757105), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( - visible: true, - ), - "shared::components::HierarchySiblingIndex": (22), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - shader: ( - kind: StandardLit, - schema_path: None, - shader_path: None, - ), - base_color: ( - r: 0.72, - g: 0.45, - b: 0.2, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.85, - emissive_color: ( - r: 1.0, - g: 1.0, - b: 1.0, - a: 1.0, - ), - emissive_intensity: 0.0, - base_color_texture: None, - emissive_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - material_asset_path: None, - parameters: [], - textures: [], - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 1.0, - y_length: 1.0, - z_length: 1.0, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (1.0, 1.0, 1.0), - ), - }, - ), - 8589934477: ( - components: { - "bevy_ecs::name::Name": "Pillar", - "bevy_transform::components::transform::Transform": ( - translation: (8.0, 3.5, 6.0), + }), + 4294966588: (components: { + "bevy_ecs::name::Name": "Brush Part 1", + "bevy_transform::components::transform::Transform": ( + translation: (24.5, 0.0, 7.75), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( - visible: true, - ), - "shared::components::HierarchySiblingIndex": (23), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - shader: ( - kind: StandardLit, - schema_path: None, - shader_path: None, - ), - base_color: ( - r: 0.85, - g: 0.85, - b: 0.88, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.3, - emissive_color: ( - r: 1.0, - g: 1.0, - b: 1.0, - a: 1.0, - ), - emissive_intensity: 0.0, - base_color_texture: None, - emissive_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - material_asset_path: None, - parameters: [], - textures: [], - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 1.2, - y_length: 7.0, - z_length: 1.2, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (1.2, 7.0, 1.2), - ), - }, - ), - 8589934478: ( - components: { - "bevy_ecs::name::Name": "Pillar", - "bevy_transform::components::transform::Transform": ( - translation: (-8.0, 3.5, 6.0), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( - visible: true, - ), - "shared::components::HierarchySiblingIndex": (24), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - shader: ( - kind: StandardLit, - schema_path: None, - shader_path: None, - ), - base_color: ( - r: 0.85, - g: 0.85, - b: 0.88, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.3, - emissive_color: ( - r: 1.0, - g: 1.0, - b: 1.0, - a: 1.0, - ), - emissive_intensity: 0.0, - base_color_texture: None, - emissive_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - material_asset_path: None, - parameters: [], - textures: [], - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 1.2, - y_length: 7.0, - z_length: 1.2, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (1.2, 7.0, 1.2), - ), - }, - ), - 8589934479: ( - components: { - "bevy_ecs::name::Name": "Pillar", - "bevy_transform::components::transform::Transform": ( - translation: (8.0, 3.5, -6.0), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( - visible: true, - ), - "shared::components::HierarchySiblingIndex": (25), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - shader: ( - kind: StandardLit, - schema_path: None, - shader_path: None, - ), - base_color: ( - r: 0.85, - g: 0.85, - b: 0.88, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.3, - emissive_color: ( - r: 1.0, - g: 1.0, - b: 1.0, - a: 1.0, - ), - emissive_intensity: 0.0, - base_color_texture: None, - emissive_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - material_asset_path: None, - parameters: [], - textures: [], - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 1.2, - y_length: 7.0, - z_length: 1.2, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (1.2, 7.0, 1.2), - ), - }, - ), - 8589934480: ( - components: { - "bevy_ecs::name::Name": "Pillar", - "bevy_transform::components::transform::Transform": ( - translation: (-8.0, 3.5, -6.0), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::ActorKind": StaticMesh, - "shared::components::EditorVisibility": ( - visible: true, - ), - "shared::components::HierarchySiblingIndex": (26), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - shader: ( - kind: StandardLit, - schema_path: None, - shader_path: None, - ), - base_color: ( - r: 0.85, - g: 0.85, - b: 0.88, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.3, - emissive_color: ( - r: 1.0, - g: 1.0, - b: 1.0, - a: 1.0, - ), - emissive_intensity: 0.0, - base_color_texture: None, - emissive_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - material_asset_path: None, - parameters: [], - textures: [], - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 1.2, - y_length: 7.0, - z_length: 1.2, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (1.2, 7.0, 1.2), - ), - }, - ), - 21474836282: ( - components: { - "bevy_ecs::name::Name": "Post Process Volume", - "bevy_transform::components::transform::Transform": ( - translation: (28.0, 2.0, -1.0), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (8.257915, 1.0, 1.0), - ), - "shared::components::ActorId": ("77e41415-e7c0-4fef-82ba-8dacad7ad0a0"), - "shared::components::ActorKind": Brush, - "shared::components::BrushDesc": ( + "shared::components::ActorId": ("962cd555-de58-4fce-ba29-fed8dc24543c"), + "shared::components::ActorKind": Brush, + "shared::components::BrushDesc": ( kind: Additive, faces: [ - ( - id: ("face:+x"), - plane: ( - normal: (1.0, 0.0, 0.0), - distance: 0.5, - ), - vertices: [ - (0.5, -0.5, -0.5), - (0.5, 0.5, -0.5), - (0.5, 0.5, 0.5), - (0.5, -0.5, 0.5), - ], - material: None, - texture: None, - uv_offset: (0.0, 0.0), - uv_scale: (1.0, 1.0), - uv_rotation: 0.0, - smoothing_group: 0, - ), - ( - id: ("face:-x"), - plane: ( - normal: (-1.0, 0.0, 0.0), - distance: 0.5, - ), - vertices: [ - (-0.5, -0.5, 0.5), - (-0.5, 0.5, 0.5), - (-0.5, 0.5, -0.5), - (-0.5, -0.5, -0.5), - ], - material: None, - texture: None, - uv_offset: (0.0, 0.0), - uv_scale: (1.0, 1.0), - uv_rotation: 0.0, - smoothing_group: 0, - ), ( id: ("face:+y"), plane: ( normal: (0.0, 1.0, 0.0), - distance: 0.5, + distance: 2.3, ), vertices: [ - (-0.5, 0.5, -0.5), - (-0.5, 0.5, 0.5), - (0.5, 0.5, 0.5), - (0.5, 0.5, -0.5), + (-0.5, 2.3, 2.25), + (0.5, 2.3, 1.25), + (0.5, 2.3, -2.75), + (-0.5, 2.3, -0.75), ], material: None, texture: None, @@ -1648,13 +202,13 @@ id: ("face:-y"), plane: ( normal: (0.0, -1.0, 0.0), - distance: 0.5, + distance: -0.0, ), vertices: [ - (-0.5, -0.5, 0.5), - (-0.5, -0.5, -0.5), - (0.5, -0.5, -0.5), - (0.5, -0.5, 0.5), + (-0.5, 0.0, -0.75), + (0.5, 0.0, -2.75), + (0.5, 0.0, 1.25), + (-0.5, 0.0, 2.25), ], material: None, texture: None, @@ -1664,16 +218,16 @@ smoothing_group: 0, ), ( - id: ("face:+z"), + id: ("face:side:0"), plane: ( - normal: (0.0, 0.0, 1.0), - distance: 0.5, + normal: (-0.8944272, 0.0, -0.4472136), + distance: 0.78262377, ), vertices: [ - (0.5, -0.5, 0.5), - (0.5, 0.5, 0.5), - (-0.5, 0.5, 0.5), - (-0.5, -0.5, 0.5), + (-0.5, 0.0, -0.75), + (-0.5, 2.3, -0.75), + (0.5, 2.3, -2.75), + (0.5, 0.0, -2.75), ], material: None, texture: None, @@ -1683,16 +237,54 @@ smoothing_group: 0, ), ( - id: ("face:-z"), + id: ("face:side:1"), plane: ( - normal: (0.0, 0.0, -1.0), + normal: (1.0, 0.0, -0.0), distance: 0.5, ), vertices: [ - (-0.5, -0.5, -0.5), - (-0.5, 0.5, -0.5), - (0.5, 0.5, -0.5), - (0.5, -0.5, -0.5), + (0.5, 0.0, -2.75), + (0.5, 2.3, -2.75), + (0.5, 2.3, 1.25), + (0.5, 0.0, 1.25), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ( + id: ("face:side:2"), + plane: ( + normal: (0.70710677, 0.0, 0.70710677), + distance: 1.2374369, + ), + vertices: [ + (0.5, 0.0, 1.25), + (0.5, 2.3, 1.25), + (-0.5, 2.3, 2.25), + (-0.5, 0.0, 2.25), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ( + id: ("face:side:3"), + plane: ( + normal: (-1.0, 0.0, -0.0), + distance: 0.5, + ), + vertices: [ + (-0.5, 0.0, 2.25), + (-0.5, 2.3, 2.25), + (-0.5, 2.3, -0.75), + (-0.5, 0.0, -0.75), ], material: None, texture: None, @@ -1705,43 +297,51 @@ cast_shadows: true, receive_shadows: true, ), - "shared::components::EditorVisibility": ( + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (27), - "shared::components::LevelObject": (), - "shared::components::PostProcessVolumeDesc": ( - half_extents: (4.0, 2.0, 4.0), - priority: 0, - blend_distance: 2.0, - overrides: ( - exposure_ev100: None, - fog_color: None, - fog_density: None, - bloom: None, - taa: None, - ssao: None, - tonemapping_aces: None, - atmosphere: None, - hdr: None, + "shared::components::HierarchySiblingIndex": (1), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, ), - fullscreen_effect: None, - profile: None, - label: None, + base_color: ( + r: 0.8, + g: 0.8, + b: 0.8, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.65, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], ), - }, - ), - 21474836285: ( - components: { - "bevy_ecs::name::Name": "Crate Copy", - "bevy_transform::components::transform::Transform": ( + }), + 4294966589: (components: { + "bevy_ecs::name::Name": "Crate Copy", + "bevy_transform::components::transform::Transform": ( translation: (15.624287, 0.75, 1.8058548), rotation: (0.0, 0.77714604, 0.0, -0.62932026), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorId": ("a3abc97b-7560-4b41-9670-023efb5d61bd"), - "shared::components::ActorKind": Brush, - "shared::components::BrushDesc": ( + "shared::components::ActorId": ("a3abc97b-7560-4b41-9670-023efb5d61bd"), + "shared::components::ActorKind": Brush, + "shared::components::BrushDesc": ( kind: SubtractiveMarker, faces: [ ( @@ -1862,12 +462,12 @@ cast_shadows: true, receive_shadows: true, ), - "shared::components::EditorVisibility": ( + "shared::components::EditorVisibility": ( visible: true, ), - "shared::components::HierarchySiblingIndex": (19), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( + "shared::components::HierarchySiblingIndex": (2), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( shader: ( kind: StandardLit, schema_path: None, @@ -1896,7 +496,1680 @@ parameters: [], textures: [], ), - }, - ), - }, + }), + 4294966590: (components: { + "bevy_ecs::name::Name": "Post Process Volume", + "bevy_transform::components::transform::Transform": ( + translation: (28.0, 2.0, -1.0), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (8.257915, 1.0, 1.0), + ), + "shared::components::ActorId": ("77e41415-e7c0-4fef-82ba-8dacad7ad0a0"), + "shared::components::ActorKind": Brush, + "shared::components::BrushDesc": ( + kind: Additive, + faces: [ + ( + id: ("face:+x"), + plane: ( + normal: (1.0, 0.0, 0.0), + distance: 0.5, + ), + vertices: [ + (0.5, -0.5, -0.5), + (0.5, 0.5, -0.5), + (0.5, 0.5, 0.5), + (0.5, -0.5, 0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ( + id: ("face:-x"), + plane: ( + normal: (-1.0, 0.0, 0.0), + distance: 0.5, + ), + vertices: [ + (-0.5, -0.5, 0.5), + (-0.5, 0.5, 0.5), + (-0.5, 0.5, -0.5), + (-0.5, -0.5, -0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ( + id: ("face:+y"), + plane: ( + normal: (0.0, 1.0, 0.0), + distance: 0.5, + ), + vertices: [ + (-0.5, 0.5, -0.5), + (-0.5, 0.5, 0.5), + (0.5, 0.5, 0.5), + (0.5, 0.5, -0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ( + id: ("face:-y"), + plane: ( + normal: (0.0, -1.0, 0.0), + distance: 0.5, + ), + vertices: [ + (-0.5, -0.5, 0.5), + (-0.5, -0.5, -0.5), + (0.5, -0.5, -0.5), + (0.5, -0.5, 0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ( + id: ("face:+z"), + plane: ( + normal: (0.0, 0.0, 1.0), + distance: 0.5, + ), + vertices: [ + (0.5, -0.5, 0.5), + (0.5, 0.5, 0.5), + (-0.5, 0.5, 0.5), + (-0.5, -0.5, 0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ( + id: ("face:-z"), + plane: ( + normal: (0.0, 0.0, -1.0), + distance: 0.5, + ), + vertices: [ + (-0.5, -0.5, -0.5), + (-0.5, 0.5, -0.5), + (0.5, 0.5, -0.5), + (0.5, -0.5, -0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ], + cast_shadows: true, + receive_shadows: true, + ), + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (3), + "shared::components::LevelObject": (), + "shared::components::PostProcessVolumeDesc": ( + half_extents: (4.0, 2.0, 4.0), + priority: 0, + blend_distance: 2.0, + overrides: ( + exposure_ev100: None, + fog_color: None, + fog_density: None, + bloom: None, + taa: None, + ssao: None, + tonemapping_aces: None, + atmosphere: None, + hdr: None, + ), + fullscreen_effect: None, + profile: None, + label: None, + ), + }), + 4294966591: (components: { + "bevy_ecs::name::Name": "Pillar", + "bevy_transform::components::transform::Transform": ( + translation: (-8.0, 3.5, -6.0), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (8), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.85, + g: 0.85, + b: 0.88, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.3, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 1.2, + y_length: 7.0, + z_length: 1.2, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (1.2, 7.0, 1.2), + ), + }), + 4294966592: (components: { + "bevy_ecs::name::Name": "Pillar", + "bevy_transform::components::transform::Transform": ( + translation: (8.0, 3.5, -6.0), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (9), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.85, + g: 0.85, + b: 0.88, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.3, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 1.2, + y_length: 7.0, + z_length: 1.2, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (1.2, 7.0, 1.2), + ), + }), + 4294966593: (components: { + "bevy_ecs::name::Name": "Pillar", + "bevy_transform::components::transform::Transform": ( + translation: (-8.0, 3.5, 6.0), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (10), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.85, + g: 0.85, + b: 0.88, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.3, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 1.2, + y_length: 7.0, + z_length: 1.2, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (1.2, 7.0, 1.2), + ), + }), + 4294966594: (components: { + "bevy_ecs::name::Name": "Pillar", + "bevy_transform::components::transform::Transform": ( + translation: (8.0, 3.5, 6.0), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (11), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.85, + g: 0.85, + b: 0.88, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.3, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 1.2, + y_length: 7.0, + z_length: 1.2, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (1.2, 7.0, 1.2), + ), + }), + 4294966595: (components: { + "bevy_ecs::name::Name": "Crate", + "bevy_transform::components::transform::Transform": ( + translation: (-0.8005953, 0.3732614, 0.21393776), + rotation: (0.0, 0.86742324, 0.0, -0.49757105), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (1), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.72, + g: 0.45, + b: 0.2, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.85, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 1.0, + y_length: 1.0, + z_length: 1.0, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (1.0, 1.0, 1.0), + ), + }), + 4294966596: (components: { + "bevy_ecs::name::Name": "Crate", + "bevy_transform::components::transform::Transform": ( + translation: (0.59940434, 0.3732614, 0.013936996), + rotation: (0.0, -0.53118616, 0.0, 0.8472551), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (2), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.72, + g: 0.45, + b: 0.2, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.85, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 1.0, + y_length: 1.0, + z_length: 1.0, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (1.0, 1.0, 1.0), + ), + }), + 4294966597: (components: { + "bevy_ecs::name::Name": "Crate", + "bevy_transform::components::transform::Transform": ( + translation: (-0.10059604, 1.3955705, 0.11393738), + rotation: (0.0, 0.72083056, 0.0, -0.6931113), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (0), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.72, + g: 0.45, + b: 0.2, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.85, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 1.0, + y_length: 1.0, + z_length: 1.0, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (1.0, 1.0, 1.0), + ), + }), + 4294966598: (components: { + "bevy_ecs::name::Name": "Crate", + "bevy_transform::components::transform::Transform": ( + translation: (14.575503, 4.5, -15.5), + rotation: (0.77714586, -0.0, -0.6293205, -0.0), + scale: (8.5, 6.7, 5.6), + ), + "shared::components::ActorKind": Brush, + "shared::components::BrushDesc": ( + kind: Additive, + faces: [ + ( + id: ("face:+x"), + plane: ( + normal: (1.0, 0.0, 0.0), + distance: 0.5, + ), + vertices: [ + (0.5, -0.5, -0.5), + (0.5, 0.5, -0.5), + (0.5, 0.5, 0.5), + (0.5, -0.5, 0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ( + id: ("face:-x"), + plane: ( + normal: (-1.0, 0.0, 0.0), + distance: 0.5, + ), + vertices: [ + (-0.5, -0.5, 0.5), + (-0.5, 0.5, 0.5), + (-0.5, 0.5, -0.5), + (-0.5, -0.5, -0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ( + id: ("face:+y"), + plane: ( + normal: (0.0, 1.0, 0.0), + distance: 0.5, + ), + vertices: [ + (-0.5, 0.5, -0.5), + (-0.5, 0.5, 0.5), + (0.5, 0.5, 0.5), + (0.5, 0.5, -0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ( + id: ("face:-y"), + plane: ( + normal: (0.0, -1.0, 0.0), + distance: 0.5, + ), + vertices: [ + (-0.5, -0.5, 0.5), + (-0.5, -0.5, -0.5), + (0.5, -0.5, -0.5), + (0.5, -0.5, 0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ( + id: ("face:+z"), + plane: ( + normal: (0.0, 0.0, 1.0), + distance: 0.5, + ), + vertices: [ + (0.5, -0.5, 0.5), + (0.5, 0.5, 0.5), + (-0.5, 0.5, 0.5), + (-0.5, -0.5, 0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ( + id: ("face:-z"), + plane: ( + normal: (0.0, 0.0, -1.0), + distance: 0.5, + ), + vertices: [ + (-0.5, -0.5, -0.5), + (-0.5, 0.5, -0.5), + (0.5, 0.5, -0.5), + (0.5, -0.5, -0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ], + cast_shadows: true, + receive_shadows: true, + ), + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (4), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.55, + g: 0.54, + b: 0.52, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.85, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: Some("assets/materials/concrete.ron"), + parameters: [], + textures: [], + ), + }), + 4294966599: (components: { + "bevy_ecs::name::Name": "Crate", + "bevy_transform::components::transform::Transform": ( + translation: (5.4, 0.5, 2.4), + rotation: (0.0, 0.94948566, 0.0, -0.31381056), + scale: (1.0000002, 1.0, 1.0000002), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (28), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.72, + g: 0.45, + b: 0.2, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.85, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 1.0, + y_length: 1.0, + z_length: 1.0, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (1.0, 1.0, 1.0), + ), + }), + 4294966600: (components: { + "bevy_ecs::name::Name": "Ramp", + "bevy_transform::components::transform::Transform": ( + translation: (-14.0, 1.6, 0.0), + rotation: (-0.17364825, 0.0, 0.0, 0.9848077), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (16), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.3, + g: 0.45, + b: 0.7, + a: 1.0, + ), + metallic: 0.1, + roughness: 0.55, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 5.0, + y_length: 0.4, + z_length: 9.0, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (5.0, 0.4, 9.0), + ), + }), + 4294966601: (components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( + translation: (12.0, 0.09, 14.0), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (17), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.55, + g: 0.52, + b: 0.48, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.8, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 5.0, + y_length: 0.18, + z_length: 0.5, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (5.0, 0.18, 0.5), + ), + }), + 4294966602: (components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( + translation: (12.0, 0.18, 13.5), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (18), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.55, + g: 0.52, + b: 0.48, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.8, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 5.0, + y_length: 0.36, + z_length: 0.5, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (5.0, 0.36, 0.5), + ), + }), + 4294966603: (components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( + translation: (12.0, 0.27, 13.0), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (19), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.55, + g: 0.52, + b: 0.48, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.8, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 5.0, + y_length: 0.54, + z_length: 0.5, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (5.0, 0.54, 0.5), + ), + }), + 4294966604: (components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( + translation: (12.0, 0.36, 12.5), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (20), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.55, + g: 0.52, + b: 0.48, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.8, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 5.0, + y_length: 0.72, + z_length: 0.5, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (5.0, 0.72, 0.5), + ), + }), + 4294966605: (components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( + translation: (12.0, 0.45000002, 12.0), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (21), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.55, + g: 0.52, + b: 0.48, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.8, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 5.0, + y_length: 0.90000004, + z_length: 0.5, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (5.0, 0.90000004, 0.5), + ), + }), + 4294966606: (components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( + translation: (12.0, 0.54, 11.5), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (22), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.55, + g: 0.52, + b: 0.48, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.8, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 5.0, + y_length: 1.08, + z_length: 0.5, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (5.0, 1.08, 0.5), + ), + }), + 4294966607: (components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( + translation: (12.0, 0.63, 11.0), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (23), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.55, + g: 0.52, + b: 0.48, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.8, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 5.0, + y_length: 1.26, + z_length: 0.5, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (5.0, 1.26, 0.5), + ), + }), + 4294966608: (components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( + translation: (12.0, 0.72, 10.5), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (24), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.55, + g: 0.52, + b: 0.48, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.8, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 5.0, + y_length: 1.44, + z_length: 0.5, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (5.0, 1.44, 0.5), + ), + }), + 4294966609: (components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( + translation: (12.0, 0.81000006, 10.0), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (25), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.55, + g: 0.52, + b: 0.48, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.8, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 5.0, + y_length: 1.6200001, + z_length: 0.5, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (5.0, 1.6200001, 0.5), + ), + }), + 4294966610: (components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( + translation: (12.0, 0.90000004, 9.5), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (26), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.55, + g: 0.52, + b: 0.48, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.8, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 5.0, + y_length: 1.8000001, + z_length: 0.5, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (5.0, 1.8000001, 0.5), + ), + }), + 4294966611: (components: { + "bevy_ecs::name::Name": "ShowcaseSphere", + "bevy_transform::components::transform::Transform": ( + translation: (-8.0, 0.6, 4.0), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (15), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.9, + g: 0.9, + b: 0.92, + a: 1.0, + ), + metallic: 1.0, + roughness: 0.05, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Sphere( + radius: 0.6, + ), + ), + "shared::components::Primitive": ( + shape: Sphere, + size: (1.2, 1.2, 1.2), + ), + }), + 4294966612: (components: { + "bevy_ecs::name::Name": "ShowcaseSphere", + "bevy_transform::components::transform::Transform": ( + translation: (-5.8, 0.6, 4.0), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (13), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.9, + g: 0.9, + b: 0.92, + a: 1.0, + ), + metallic: 1.0, + roughness: 0.3, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Sphere( + radius: 0.6, + ), + ), + "shared::components::Primitive": ( + shape: Sphere, + size: (1.2, 1.2, 1.2), + ), + }), + 4294966613: (components: { + "bevy_ecs::name::Name": "ShowcaseSphere", + "bevy_transform::components::transform::Transform": ( + translation: (-3.6, 0.6, 4.0), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (14), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.9, + g: 0.9, + b: 0.92, + a: 1.0, + ), + metallic: 1.0, + roughness: 0.6, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Sphere( + radius: 0.6, + ), + ), + "shared::components::Primitive": ( + shape: Sphere, + size: (1.2, 1.2, 1.2), + ), + }), + 4294966614: (components: { + "bevy_ecs::name::Name": "ShowcaseSphere", + "bevy_transform::components::transform::Transform": ( + translation: (-1.3999996, 0.6, 4.0), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (7), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.9, + g: 0.9, + b: 0.92, + a: 1.0, + ), + metallic: 1.0, + roughness: 0.95, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Sphere( + radius: 0.6, + ), + ), + "shared::components::Primitive": ( + shape: Sphere, + size: (1.2, 1.2, 1.2), + ), + }), + 4294966615: (components: { + "bevy_ecs::name::Name": "Player Start", + "bevy_transform::components::transform::Transform": ( + translation: (0.0, 0.9900649, 12.46175), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": PlayerSpawn, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (5), + "shared::components::LevelObject": (), + "shared::components::PlayerSpawn": (), + }), + 4294966616: (components: { + "bevy_ecs::name::Name": "Ground", + "bevy_transform::components::transform::Transform": ( + translation: (0.0, -0.5, 0.0), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (12), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.42, + g: 0.45, + b: 0.48, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.92, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 200.0, + y_length: 1.0, + z_length: 200.0, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (200.0, 1.0, 200.0), + ), + }), + 4294966617: (components: { + "bevy_ecs::name::Name": "Scene Sun", + "bevy_transform::components::transform::Transform": ( + translation: (0.0, 5.316672, 0.0), + rotation: (-0.21401538, 0.31161797, 0.67592126, 0.6326311), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": Light, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (6), + "shared::components::LevelObject": (), + "shared::components::LightDesc": ( + kind: Directional, + color: ( + r: 1.0, + g: 0.95, + b: 0.85, + a: 1.0, + ), + intensity: 100000.0, + range: 0.0, + shadows: true, + inner_angle_deg: 25.0, + outer_angle_deg: 35.0, + ), + }), + }, ) \ No newline at end of file diff --git a/assets/levels/navigation_authoring_showcase.scn.ron b/assets/levels/navigation_authoring_showcase.scn.ron new file mode 100644 index 0000000..4aa012f --- /dev/null +++ b/assets/levels/navigation_authoring_showcase.scn.ron @@ -0,0 +1,70 @@ +(schema_version: 4,resources: { + }, + entities: { + 1: (components: { + "bevy_ecs::name::Name": "Navigation Bounds", + "bevy_transform::components::transform::Transform": (translation: (0.0, 0.0, 0.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0)), + "shared::components::ActorId": ("navigation-bounds-main"), + "shared::components::ActorKind": Navigation, + "shared::components::EditorVisibility": (visible: true), + "shared::components::HierarchySiblingIndex": (0), + "shared::components::LevelObject": (), + "shared::navigation::NavigationBounds": ( + half_extents: (8.0, 2.0, 8.0), + agent: ( + id: "humanoid", + radius: 0.35, + height: 1.8, + max_climb: 0.45, + max_slope_deg: 45.0, + cell_size_fraction: 3.0, + cell_height_fraction: 6.0, + min_region_size: 1, + merge_region_size: 2, + ), + artifact_path: "assets/navigation/generated/navigation_showcase_humanoid.nav.ron", + auto_bake: false, + ), + }), + 2: (components: { + "bevy_ecs::name::Name": "Center Obstacle", + "bevy_transform::components::transform::Transform": (translation: (0.0, 0.5, 0.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0)), + "shared::components::ActorId": ("navigation-obstacle-center"), + "shared::components::ActorKind": Navigation, + "shared::components::EditorVisibility": (visible: true), + "shared::components::HierarchySiblingIndex": (1), + "shared::components::LevelObject": (), + "shared::navigation::NavigationObstacle": (half_extents: (1.0, 1.0, 2.5), dynamic: false), + }), + 3: (components: { + "bevy_ecs::name::Name": "Slow Area", + "bevy_transform::components::transform::Transform": (translation: (-4.0, 0.5, -4.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0)), + "shared::components::ActorId": ("navigation-area-slow"), + "shared::components::ActorKind": Navigation, + "shared::components::EditorVisibility": (visible: true), + "shared::components::HierarchySiblingIndex": (2), + "shared::components::LevelObject": (), + "shared::navigation::NavigationArea": (id: "slow", half_extents: (1.5, 1.0, 1.5), cost: 1.5, walkable: true), + }), + 4: (components: { + "bevy_ecs::name::Name": "Obstacle Link", + "bevy_transform::components::transform::Transform": (translation: (0.0, 0.0, 0.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0)), + "shared::components::ActorId": ("navigation-link-center"), + "shared::components::ActorKind": Navigation, + "shared::components::EditorVisibility": (visible: true), + "shared::components::HierarchySiblingIndex": (3), + "shared::components::LevelObject": (), + "shared::navigation::NavigationLink": (start: (-1.5, 0.0, 0.0), end: (1.5, 0.0, 0.0), bidirectional: true, cost: 1.2, enabled: true), + }), + 5: (components: { + "bevy_ecs::name::Name": "Walkable Floor Geometry", + "bevy_transform::components::transform::Transform": (translation: (0.0, -0.25, 0.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0)), + "shared::components::ActorId": ("navigation-floor-geometry"), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": (visible: true), + "shared::components::HierarchySiblingIndex": (4), + "shared::components::LevelObject": (), + "shared::components::Primitive": (shape: Box, size: (16.0, 0.5, 16.0)), + }), + }, +) \ No newline at end of file diff --git a/assets/levels/rendering_showcase.scn.ron b/assets/levels/rendering_showcase.scn.ron index 4f16031..54be998 100644 --- a/assets/levels/rendering_showcase.scn.ron +++ b/assets/levels/rendering_showcase.scn.ron @@ -1,17 +1,16 @@ -(schema_version: 2, - resources: {}, - entities: { - 4294968001: ( - components: { - "bevy_ecs::name::Name": "Fog Volume", - "bevy_transform::components::transform::Transform": ( +(schema_version: 4,resources: { + }, + entities: { + 4294968001: (components: { + "bevy_ecs::name::Name": "Fog Volume", + "bevy_transform::components::transform::Transform": ( translation: (0.0, 2.0, 0.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::LevelObject": (), - "shared::components::ActorKind": PostProcessVolume, - "shared::components::PostProcessVolumeDesc": ( + "shared::components::ActorKind": PostProcessVolume, + "shared::components::LevelObject": (), + "shared::components::PostProcessVolumeDesc": ( half_extents: (6.0, 3.0, 6.0), priority: 0, blend_distance: 2.0, @@ -23,19 +22,17 @@ profile: None, label: Some("Foggy courtyard"), ), - }, - ), - 4294968002: ( - components: { - "bevy_ecs::name::Name": "Dark Exposure Volume", - "bevy_transform::components::transform::Transform": ( + }), + 4294968002: (components: { + "bevy_ecs::name::Name": "Dark Exposure Volume", + "bevy_transform::components::transform::Transform": ( translation: (14.0, 2.0, 0.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::LevelObject": (), - "shared::components::ActorKind": PostProcessVolume, - "shared::components::PostProcessVolumeDesc": ( + "shared::components::ActorKind": PostProcessVolume, + "shared::components::LevelObject": (), + "shared::components::PostProcessVolumeDesc": ( half_extents: (4.0, 2.5, 4.0), priority: 5, blend_distance: 1.5, @@ -47,19 +44,17 @@ profile: Some("assets/rendering_profiles/cave_dark.ron"), label: Some("Cave mouth"), ), - }, - ), - 4294968003: ( - components: { - "bevy_ecs::name::Name": "Vignette FX Volume", - "bevy_transform::components::transform::Transform": ( + }), + 4294968003: (components: { + "bevy_ecs::name::Name": "Vignette FX Volume", + "bevy_transform::components::transform::Transform": ( translation: (-12.0, 2.0, 0.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::LevelObject": (), - "shared::components::ActorKind": PostProcessVolume, - "shared::components::PostProcessVolumeDesc": ( + "shared::components::ActorKind": PostProcessVolume, + "shared::components::LevelObject": (), + "shared::components::PostProcessVolumeDesc": ( half_extents: (5.0, 2.0, 5.0), priority: 2, blend_distance: 2.0, @@ -68,7 +63,6 @@ profile: None, label: Some("Vignette demo"), ), - }, - ), - }, -) + }), + }, +) \ No newline at end of file diff --git a/assets/materials/concrete.ron b/assets/materials/concrete.ron index 091f375..dc8ffd8 100644 --- a/assets/materials/concrete.ron +++ b/assets/materials/concrete.ron @@ -1,12 +1,40 @@ ( + schema_version: 1, label: "Concrete", + shader: None, + shader_ref: None, + render_state: ( + alpha_mode: Opaque, + alpha_cutoff: 0.5, + double_sided: false, + ), material: ( - base_color: (r: 0.55, g: 0.54, b: 0.52, a: 1.0), + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.55, + g: 0.54, + b: 0.52, + a: 1.0, + ), metallic: 0.0, roughness: 0.85, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, base_color_texture: None, + emissive_texture: None, normal_map_texture: None, metallic_roughness_texture: None, material_asset_path: None, + parameters: [], + textures: [], ), -) +) \ No newline at end of file diff --git a/assets/materials/emissive_panel.ron b/assets/materials/emissive_panel.ron index 449e3f8..e9d296c 100644 --- a/assets/materials/emissive_panel.ron +++ b/assets/materials/emissive_panel.ron @@ -1,15 +1,40 @@ ( + schema_version: 1, label: "Emissive Panel", + shader: None, + shader_ref: None, + render_state: ( + alpha_mode: Opaque, + alpha_cutoff: 0.5, + double_sided: false, + ), material: ( - base_color: (r: 0.15, g: 0.22, b: 0.28, a: 1.0), + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.15, + g: 0.22, + b: 0.28, + a: 1.0, + ), metallic: 0.0, roughness: 0.35, - emissive_color: (r: 0.55, g: 0.85, b: 1.0, a: 1.0), + emissive_color: ( + r: 0.55, + g: 0.85, + b: 1.0, + a: 1.0, + ), emissive_intensity: 2500.0, base_color_texture: None, emissive_texture: None, normal_map_texture: None, metallic_roughness_texture: None, material_asset_path: None, + parameters: [], + textures: [], ), -) +) \ No newline at end of file diff --git a/assets/materials/surface_tint.ron b/assets/materials/surface_tint.ron new file mode 100644 index 0000000..3844b3a --- /dev/null +++ b/assets/materials/surface_tint.ron @@ -0,0 +1,71 @@ +( + schema_version: 1, + label: "Surface Tint", + shader: Some("assets/shaders/surface_tint.shader.ron"), + shader_ref: None, + render_state: ( + alpha_mode: Opaque, + alpha_cutoff: 0.5, + double_sided: false, + ), + material: ( + shader: ( + kind: Custom, + schema_path: Some("assets/shaders/surface_tint.shader.ron"), + shader_path: Some("assets/shaders/surface_tint.wgsl"), + ), + base_color: ( + r: 0.12, + g: 0.42, + b: 0.95, + a: 1.0, + ), + metallic: 0.15, + roughness: 0.28, + emissive_color: ( + r: 0.02, + g: 0.08, + b: 0.25, + a: 1.0, + ), + emissive_intensity: 3.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [ + ( + name: "tint", + value: Color(( + r: 0.12, + g: 0.42, + b: 0.95, + a: 1.0, + )), + ), + ( + name: "roughness", + value: Float(0.28), + ), + ( + name: "metallic", + value: Float(0.15), + ), + ( + name: "emissive", + value: Color(( + r: 0.02, + g: 0.08, + b: 0.25, + a: 1.0, + )), + ), + ( + name: "emissive_intensity", + value: Float(3.0), + ), + ], + textures: [], + ), +) \ No newline at end of file diff --git a/assets/materials/surface_tint_instance.ron b/assets/materials/surface_tint_instance.ron new file mode 100644 index 0000000..c0b16a2 --- /dev/null +++ b/assets/materials/surface_tint_instance.ron @@ -0,0 +1,12 @@ +( + schema_version: 1, + label: "surface_tint Instance", + base: (( + asset_id: "d6cb4151-7124-4237-aaf9-f7f8abd5fb76", + sub_asset_id: "material:source", + label: "surface_tint", + source_path: Some("assets/materials/surface_tint.ron"), + )), + parameters: [], + textures: [], +) \ No newline at end of file diff --git a/assets/meshes/generated/113f74df-e39c-41d4-9b5b-e48efe541f7f.static_mesh.ron b/assets/meshes/generated/113f74df-e39c-41d4-9b5b-e48efe541f7f.static_mesh.ron new file mode 100644 index 0000000..303ecca --- /dev/null +++ b/assets/meshes/generated/113f74df-e39c-41d4-9b5b-e48efe541f7f.static_mesh.ron @@ -0,0 +1,360 @@ +( + schema_version: 3, + asset_id: "113f74df-e39c-41d4-9b5b-e48efe541f7f", + label: "RobotExpressive", + source: ( + path: "assets/models/RobotExpressive.glb", + format: "glb", + fingerprint: ( + byte_len: 463988, + modified_unix_secs: 1783799272, + ), + dependencies: [], + ), + import: ( + scale: 1.0, + generate_collider: true, + lod0_only: true, + placement_mode: SceneInstance, + hierarchy_mode: SourceHierarchy, + material_policy: SourceMaterials, + ), + metadata: ( + mesh_count: 14, + material_count: 3, + node_count: 74, + animation_count: 14, + skin_count: 2, + light_count: 0, + camera_count: 0, + ), + parts: [ + ( + id: "draw:scene0:node4:mesh0:primitive0", + name: "Foot.L / Primitive 0", + mesh_label: "Mesh0/Primitive0", + material_id: Some("material:material0"), + material_slot_name: "Grey", + material_label: Some("Material0"), + local_transform: ( + translation: (0.62405795, 0.39766362, 0.19111444), + rotation: (0.7071068, 0.00000005228401, 0.000000015504241, -0.70710665), + scale: (100.0, 99.999985, 100.0), + ), + source_node: Some("RootNode/RobotArmature/Bone/Foot.L/Foot.L"), + source_mesh: Some("Mesh0"), + source_material: Some("Grey"), + skinned: false, + ), + ( + id: "draw:scene0:node7:mesh1:primitive0", + name: "Torso / Primitive 0", + mesh_label: "Mesh1/Primitive0", + material_id: Some("material:material0"), + material_slot_name: "Grey", + material_label: Some("Material0"), + local_transform: ( + translation: (-0.0027156367, 2.1737509, -0.019581214), + rotation: (0.70710677, 0.000000000000031351603, 0.000000000000055267226, -0.7071067), + scale: (100.0, 99.99998, 99.99998), + ), + source_node: Some("RootNode/RobotArmature/Bone/Body/Torso"), + source_mesh: Some("Mesh1"), + source_material: Some("Grey"), + skinned: false, + ), + ( + id: "draw:scene0:node7:mesh1:primitive1", + name: "Torso / Primitive 1", + mesh_label: "Mesh1/Primitive1", + material_id: Some("material:material1"), + material_slot_name: "Main", + material_label: Some("Material1"), + local_transform: ( + translation: (-0.0027156367, 2.1737509, -0.019581214), + rotation: (0.70710677, 0.000000000000031351603, 0.000000000000055267226, -0.7071067), + scale: (100.0, 99.99998, 99.99998), + ), + source_node: Some("RootNode/RobotArmature/Bone/Body/Torso"), + source_mesh: Some("Mesh1"), + source_material: Some("Main"), + skinned: false, + ), + ( + id: "draw:scene0:node61:mesh10:primitive0", + name: "Leg.R / Primitive 0", + mesh_label: "Mesh10/Primitive0", + material_id: Some("material:material1"), + material_slot_name: "Main", + material_label: Some("Material1"), + local_transform: ( + translation: (-0.6746981, 1.1586236, 0.046261936), + rotation: (0.7693698, 0.080835514, 0.110868365, -0.6238942), + scale: (99.99999, 99.99886, 100.00024), + ), + source_node: Some("RootNode/RobotArmature/Bone/Body/UpperLeg.R/Leg.R"), + source_mesh: Some("Mesh10"), + source_material: Some("Main"), + skinned: false, + ), + ( + id: "draw:scene0:node63:mesh11:primitive0", + name: "LowerLeg.R / Primitive 0", + mesh_label: "Mesh11/Primitive0", + material_id: Some("material:material1"), + material_slot_name: "Main", + material_label: Some("Material1"), + local_transform: ( + translation: (-0.73626167, 0.34982973, 0.22594015), + rotation: (-0.65091395, -0.099058665, -0.09436109, 0.7467224), + scale: (99.99999, 99.998604, 100.00058), + ), + source_node: Some("RootNode/RobotArmature/Bone/Body/UpperLeg.R/LowerLeg.R/LowerLeg.R"), + source_mesh: Some("Mesh11"), + source_material: Some("Main"), + skinned: false, + ), + ( + id: "draw:scene0:node72:mesh12:primitive0", + name: "Hand.R / Primitive 0", + mesh_label: "Mesh12/Primitive0", + material_id: Some("material:material1"), + material_slot_name: "Main", + material_label: Some("Material1"), + local_transform: ( + translation: (-0.002715637, 2.3702974, -0.020994147), + rotation: (0.70710677, 0.0, 0.0, -0.7071067), + scale: (100.0, 99.99999, 99.99999), + ), + source_node: Some("RootNode/Hand.R"), + source_mesh: Some("Mesh12"), + source_material: Some("Main"), + skinned: true, + ), + ( + id: "draw:scene0:node72:mesh12:primitive1", + name: "Hand.R / Primitive 1", + mesh_label: "Mesh12/Primitive1", + material_id: Some("material:material0"), + material_slot_name: "Grey", + material_label: Some("Material0"), + local_transform: ( + translation: (-0.002715637, 2.3702974, -0.020994147), + rotation: (0.70710677, 0.0, 0.0, -0.7071067), + scale: (100.0, 99.99999, 99.99999), + ), + source_node: Some("RootNode/Hand.R"), + source_mesh: Some("Mesh12"), + source_material: Some("Grey"), + skinned: true, + ), + ( + id: "draw:scene0:node73:mesh13:primitive0", + name: "Hand.L / Primitive 0", + mesh_label: "Mesh13/Primitive0", + material_id: Some("material:material1"), + material_slot_name: "Main", + material_label: Some("Material1"), + local_transform: ( + translation: (-0.002715637, 2.3702974, -0.020994147), + rotation: (0.70710677, 0.0, 0.0, -0.7071067), + scale: (100.0, 99.99999, 99.99999), + ), + source_node: Some("RootNode/Hand.L"), + source_mesh: Some("Mesh13"), + source_material: Some("Main"), + skinned: true, + ), + ( + id: "draw:scene0:node73:mesh13:primitive1", + name: "Hand.L / Primitive 1", + mesh_label: "Mesh13/Primitive1", + material_id: Some("material:material0"), + material_slot_name: "Grey", + material_label: Some("Material0"), + local_transform: ( + translation: (-0.002715637, 2.3702974, -0.020994147), + rotation: (0.70710677, 0.0, 0.0, -0.7071067), + scale: (100.0, 99.99999, 99.99999), + ), + source_node: Some("RootNode/Hand.L"), + source_mesh: Some("Mesh13"), + source_material: Some("Grey"), + skinned: true, + ), + ( + id: "draw:scene0:node13:mesh2:primitive0", + name: "Head / Primitive 0", + mesh_label: "Mesh2/Primitive0", + material_id: Some("material:material0"), + material_slot_name: "Grey", + material_label: Some("Material0"), + local_transform: ( + translation: (-0.05334633, 3.6175368, -0.0068152454), + rotation: (0.7249889, 0.0852804, 0.05460569, -0.6812757), + scale: (99.99997, 99.99996, 99.99997), + ), + source_node: Some("RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Neck/Head/Head"), + source_mesh: Some("Mesh2"), + source_material: Some("Grey"), + skinned: false, + ), + ( + id: "draw:scene0:node13:mesh2:primitive1", + name: "Head / Primitive 1", + mesh_label: "Mesh2/Primitive1", + material_id: Some("material:material1"), + material_slot_name: "Main", + material_label: Some("Material1"), + local_transform: ( + translation: (-0.05334633, 3.6175368, -0.0068152454), + rotation: (0.7249889, 0.0852804, 0.05460569, -0.6812757), + scale: (99.99997, 99.99996, 99.99997), + ), + source_node: Some("RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Neck/Head/Head"), + source_mesh: Some("Mesh2"), + source_material: Some("Main"), + skinned: false, + ), + ( + id: "draw:scene0:node13:mesh2:primitive2", + name: "Head / Primitive 2", + mesh_label: "Mesh2/Primitive2", + material_id: Some("material:material2"), + material_slot_name: "Black", + material_label: Some("Material2"), + local_transform: ( + translation: (-0.05334633, 3.6175368, -0.0068152454), + rotation: (0.7249889, 0.0852804, 0.05460569, -0.6812757), + scale: (99.99997, 99.99996, 99.99997), + ), + source_node: Some("RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Neck/Head/Head"), + source_mesh: Some("Mesh2"), + source_material: Some("Black"), + skinned: false, + ), + ( + id: "draw:scene0:node68:mesh3:primitive0", + name: "Foot.R / Primitive 0", + mesh_label: "Mesh3/Primitive0", + material_id: Some("material:material0"), + material_slot_name: "Grey", + material_label: Some("Material0"), + local_transform: ( + translation: (-0.637997, 0.3976636, 0.19111452), + rotation: (0.70710677, 0.00000005201811, 0.000000015770217, -0.7071067), + scale: (100.0, 99.999985, 100.0), + ), + source_node: Some("RootNode/RobotArmature/Bone/Foot.R/Foot.R"), + source_mesh: Some("Mesh3"), + source_material: Some("Grey"), + skinned: false, + ), + ( + id: "draw:scene0:node16:mesh4:primitive0", + name: "Shoulder.L / Primitive 0", + mesh_label: "Mesh4/Primitive0", + material_id: Some("material:material1"), + material_slot_name: "Main", + material_label: Some("Material1"), + local_transform: ( + translation: (0.58268076, 2.3724597, -0.020994142), + rotation: (-0.59886926, 0.37597278, -0.37597266, 0.59886944), + scale: (99.99996, 99.999954, 99.99997), + ), + source_node: Some("RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/Shoulder.L"), + source_mesh: Some("Mesh4"), + source_material: Some("Main"), + skinned: false, + ), + ( + id: "draw:scene0:node18:mesh5:primitive0", + name: "Arm.L / Primitive 0", + mesh_label: "Mesh5/Primitive0", + material_id: Some("material:material1"), + material_slot_name: "Main", + material_label: Some("Material1"), + local_transform: ( + translation: (0.26500192, 2.8489676, 0.13069445), + rotation: (-0.5164575, 0.4218856, -0.32294926, 0.67155623), + scale: (99.99994, 99.99994, 99.99999), + ), + source_node: Some("RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/Arm.L"), + source_mesh: Some("Mesh5"), + source_material: Some("Main"), + skinned: false, + ), + ( + id: "draw:scene0:node36:mesh6:primitive0", + name: "Shoulder.R / Primitive 0", + mesh_label: "Mesh6/Primitive0", + material_id: Some("material:material1"), + material_slot_name: "Main", + material_label: Some("Material1"), + local_transform: ( + translation: (-0.6369861, 2.3718858, -0.020994166), + rotation: (0.6017577, 0.37133226, -0.37133226, -0.6017577), + scale: (99.999954, 99.99997, 99.99994), + ), + source_node: Some("RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/Shoulder.R"), + source_mesh: Some("Mesh6"), + source_material: Some("Main"), + skinned: false, + ), + ( + id: "draw:scene0:node38:mesh7:primitive0", + name: "Arm.R / Primitive 0", + mesh_label: "Mesh7/Primitive0", + material_id: Some("material:material1"), + material_slot_name: "Main", + material_label: Some("Material1"), + local_transform: ( + translation: (-0.82568955, 1.9825263, 0.040243305), + rotation: (0.6276481, 0.35463694, -0.3873087, -0.5747021), + scale: (22.70405, 22.704054, 22.704046), + ), + source_node: Some("RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/Arm.R"), + source_mesh: Some("Mesh7"), + source_material: Some("Main"), + skinned: false, + ), + ( + id: "draw:scene0:node56:mesh8:primitive0", + name: "Leg.L / Primitive 0", + mesh_label: "Mesh8/Primitive0", + material_id: Some("material:material1"), + material_slot_name: "Main", + material_label: Some("Material1"), + local_transform: ( + translation: (0.6488191, 1.1620699, 0.057466835), + rotation: (0.76387787, -0.05410902, -0.070370995, -0.6392268), + scale: (99.99999, 100.00079, 100.00024), + ), + source_node: Some("RootNode/RobotArmature/Bone/Body/UpperLeg.L/Leg.L"), + source_mesh: Some("Mesh8"), + source_material: Some("Main"), + skinned: false, + ), + ( + id: "draw:scene0:node58:mesh9:primitive0", + name: "LowerLeg.L / Primitive 0", + mesh_label: "Mesh9/Primitive0", + material_id: Some("material:material1"), + material_slot_name: "Main", + material_label: Some("Material1"), + local_transform: ( + translation: (0.68805873, 0.3497491, 0.23465614), + rotation: (-0.6547612, 0.06408298, 0.061960325, 0.7505613), + scale: (100.000015, 100.0006, 100.000496), + ), + source_node: Some("RootNode/RobotArmature/Bone/Body/UpperLeg.L/LowerLeg.L/LowerLeg.L"), + source_mesh: Some("Mesh9"), + source_material: Some("Main"), + skinned: false, + ), + ], + warnings: [ + "Animations are recorded as metadata; animated placement uses SkinnedMeshRenderer.", + "Skinned primitives are excluded from StaticMeshRenderer and use SkinnedMeshRenderer.", + ], +) \ No newline at end of file diff --git a/assets/meshes/generated/3f63f359-45eb-4cb2-8970-71921cbd7bd0.static_mesh.ron b/assets/meshes/generated/3f63f359-45eb-4cb2-8970-71921cbd7bd0.static_mesh.ron new file mode 100644 index 0000000..bbf92f0 --- /dev/null +++ b/assets/meshes/generated/3f63f359-45eb-4cb2-8970-71921cbd7bd0.static_mesh.ron @@ -0,0 +1,360 @@ +( + schema_version: 3, + asset_id: "3f63f359-45eb-4cb2-8970-71921cbd7bd0", + label: "robot_expressive", + source: ( + path: "assets/models/robot_expressive.glb", + format: "glb", + fingerprint: ( + byte_len: 463988, + modified_unix_secs: 1783750957, + ), + dependencies: [], + ), + import: ( + scale: 1.0, + generate_collider: true, + lod0_only: true, + placement_mode: StaticAsset, + hierarchy_mode: SingleActor, + material_policy: SourceMaterials, + ), + metadata: ( + mesh_count: 14, + material_count: 3, + node_count: 74, + animation_count: 14, + skin_count: 2, + light_count: 0, + camera_count: 0, + ), + parts: [ + ( + id: "draw:scene0:node4:mesh0:primitive0", + name: "Foot.L / Primitive 0", + mesh_label: "Mesh0/Primitive0", + material_id: Some("material:material0"), + material_slot_name: "Grey", + material_label: Some("Material0"), + local_transform: ( + translation: (0.62405795, 0.39766362, 0.19111444), + rotation: (0.7071068, 0.00000005228401, 0.000000015504241, -0.70710665), + scale: (100.0, 99.999985, 100.0), + ), + source_node: Some("RootNode/RobotArmature/Bone/Foot.L/Foot.L"), + source_mesh: Some("Mesh0"), + source_material: Some("Grey"), + skinned: false, + ), + ( + id: "draw:scene0:node7:mesh1:primitive0", + name: "Torso / Primitive 0", + mesh_label: "Mesh1/Primitive0", + material_id: Some("material:material0"), + material_slot_name: "Grey", + material_label: Some("Material0"), + local_transform: ( + translation: (-0.0027156367, 2.1737509, -0.019581214), + rotation: (0.70710677, 0.000000000000031351603, 0.000000000000055267226, -0.7071067), + scale: (100.0, 99.99998, 99.99998), + ), + source_node: Some("RootNode/RobotArmature/Bone/Body/Torso"), + source_mesh: Some("Mesh1"), + source_material: Some("Grey"), + skinned: false, + ), + ( + id: "draw:scene0:node7:mesh1:primitive1", + name: "Torso / Primitive 1", + mesh_label: "Mesh1/Primitive1", + material_id: Some("material:material1"), + material_slot_name: "Main", + material_label: Some("Material1"), + local_transform: ( + translation: (-0.0027156367, 2.1737509, -0.019581214), + rotation: (0.70710677, 0.000000000000031351603, 0.000000000000055267226, -0.7071067), + scale: (100.0, 99.99998, 99.99998), + ), + source_node: Some("RootNode/RobotArmature/Bone/Body/Torso"), + source_mesh: Some("Mesh1"), + source_material: Some("Main"), + skinned: false, + ), + ( + id: "draw:scene0:node61:mesh10:primitive0", + name: "Leg.R / Primitive 0", + mesh_label: "Mesh10/Primitive0", + material_id: Some("material:material1"), + material_slot_name: "Main", + material_label: Some("Material1"), + local_transform: ( + translation: (-0.6746981, 1.1586236, 0.046261936), + rotation: (0.7693698, 0.080835514, 0.110868365, -0.6238942), + scale: (99.99999, 99.99886, 100.00024), + ), + source_node: Some("RootNode/RobotArmature/Bone/Body/UpperLeg.R/Leg.R"), + source_mesh: Some("Mesh10"), + source_material: Some("Main"), + skinned: false, + ), + ( + id: "draw:scene0:node63:mesh11:primitive0", + name: "LowerLeg.R / Primitive 0", + mesh_label: "Mesh11/Primitive0", + material_id: Some("material:material1"), + material_slot_name: "Main", + material_label: Some("Material1"), + local_transform: ( + translation: (-0.73626167, 0.34982973, 0.22594015), + rotation: (-0.65091395, -0.099058665, -0.09436109, 0.7467224), + scale: (99.99999, 99.998604, 100.00058), + ), + source_node: Some("RootNode/RobotArmature/Bone/Body/UpperLeg.R/LowerLeg.R/LowerLeg.R"), + source_mesh: Some("Mesh11"), + source_material: Some("Main"), + skinned: false, + ), + ( + id: "draw:scene0:node72:mesh12:primitive0", + name: "Hand.R / Primitive 0", + mesh_label: "Mesh12/Primitive0", + material_id: Some("material:material1"), + material_slot_name: "Main", + material_label: Some("Material1"), + local_transform: ( + translation: (-0.002715637, 2.3702974, -0.020994147), + rotation: (0.70710677, 0.0, 0.0, -0.7071067), + scale: (100.0, 99.99999, 99.99999), + ), + source_node: Some("RootNode/Hand.R"), + source_mesh: Some("Mesh12"), + source_material: Some("Main"), + skinned: true, + ), + ( + id: "draw:scene0:node72:mesh12:primitive1", + name: "Hand.R / Primitive 1", + mesh_label: "Mesh12/Primitive1", + material_id: Some("material:material0"), + material_slot_name: "Grey", + material_label: Some("Material0"), + local_transform: ( + translation: (-0.002715637, 2.3702974, -0.020994147), + rotation: (0.70710677, 0.0, 0.0, -0.7071067), + scale: (100.0, 99.99999, 99.99999), + ), + source_node: Some("RootNode/Hand.R"), + source_mesh: Some("Mesh12"), + source_material: Some("Grey"), + skinned: true, + ), + ( + id: "draw:scene0:node73:mesh13:primitive0", + name: "Hand.L / Primitive 0", + mesh_label: "Mesh13/Primitive0", + material_id: Some("material:material1"), + material_slot_name: "Main", + material_label: Some("Material1"), + local_transform: ( + translation: (-0.002715637, 2.3702974, -0.020994147), + rotation: (0.70710677, 0.0, 0.0, -0.7071067), + scale: (100.0, 99.99999, 99.99999), + ), + source_node: Some("RootNode/Hand.L"), + source_mesh: Some("Mesh13"), + source_material: Some("Main"), + skinned: true, + ), + ( + id: "draw:scene0:node73:mesh13:primitive1", + name: "Hand.L / Primitive 1", + mesh_label: "Mesh13/Primitive1", + material_id: Some("material:material0"), + material_slot_name: "Grey", + material_label: Some("Material0"), + local_transform: ( + translation: (-0.002715637, 2.3702974, -0.020994147), + rotation: (0.70710677, 0.0, 0.0, -0.7071067), + scale: (100.0, 99.99999, 99.99999), + ), + source_node: Some("RootNode/Hand.L"), + source_mesh: Some("Mesh13"), + source_material: Some("Grey"), + skinned: true, + ), + ( + id: "draw:scene0:node13:mesh2:primitive0", + name: "Head / Primitive 0", + mesh_label: "Mesh2/Primitive0", + material_id: Some("material:material0"), + material_slot_name: "Grey", + material_label: Some("Material0"), + local_transform: ( + translation: (-0.05334633, 3.6175368, -0.0068152454), + rotation: (0.7249889, 0.0852804, 0.05460569, -0.6812757), + scale: (99.99997, 99.99996, 99.99997), + ), + source_node: Some("RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Neck/Head/Head"), + source_mesh: Some("Mesh2"), + source_material: Some("Grey"), + skinned: false, + ), + ( + id: "draw:scene0:node13:mesh2:primitive1", + name: "Head / Primitive 1", + mesh_label: "Mesh2/Primitive1", + material_id: Some("material:material1"), + material_slot_name: "Main", + material_label: Some("Material1"), + local_transform: ( + translation: (-0.05334633, 3.6175368, -0.0068152454), + rotation: (0.7249889, 0.0852804, 0.05460569, -0.6812757), + scale: (99.99997, 99.99996, 99.99997), + ), + source_node: Some("RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Neck/Head/Head"), + source_mesh: Some("Mesh2"), + source_material: Some("Main"), + skinned: false, + ), + ( + id: "draw:scene0:node13:mesh2:primitive2", + name: "Head / Primitive 2", + mesh_label: "Mesh2/Primitive2", + material_id: Some("material:material2"), + material_slot_name: "Black", + material_label: Some("Material2"), + local_transform: ( + translation: (-0.05334633, 3.6175368, -0.0068152454), + rotation: (0.7249889, 0.0852804, 0.05460569, -0.6812757), + scale: (99.99997, 99.99996, 99.99997), + ), + source_node: Some("RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Neck/Head/Head"), + source_mesh: Some("Mesh2"), + source_material: Some("Black"), + skinned: false, + ), + ( + id: "draw:scene0:node68:mesh3:primitive0", + name: "Foot.R / Primitive 0", + mesh_label: "Mesh3/Primitive0", + material_id: Some("material:material0"), + material_slot_name: "Grey", + material_label: Some("Material0"), + local_transform: ( + translation: (-0.637997, 0.3976636, 0.19111452), + rotation: (0.70710677, 0.00000005201811, 0.000000015770217, -0.7071067), + scale: (100.0, 99.999985, 100.0), + ), + source_node: Some("RootNode/RobotArmature/Bone/Foot.R/Foot.R"), + source_mesh: Some("Mesh3"), + source_material: Some("Grey"), + skinned: false, + ), + ( + id: "draw:scene0:node16:mesh4:primitive0", + name: "Shoulder.L / Primitive 0", + mesh_label: "Mesh4/Primitive0", + material_id: Some("material:material1"), + material_slot_name: "Main", + material_label: Some("Material1"), + local_transform: ( + translation: (0.58268076, 2.3724597, -0.020994142), + rotation: (-0.59886926, 0.37597278, -0.37597266, 0.59886944), + scale: (99.99996, 99.999954, 99.99997), + ), + source_node: Some("RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/Shoulder.L"), + source_mesh: Some("Mesh4"), + source_material: Some("Main"), + skinned: false, + ), + ( + id: "draw:scene0:node18:mesh5:primitive0", + name: "Arm.L / Primitive 0", + mesh_label: "Mesh5/Primitive0", + material_id: Some("material:material1"), + material_slot_name: "Main", + material_label: Some("Material1"), + local_transform: ( + translation: (0.26500192, 2.8489676, 0.13069445), + rotation: (-0.5164575, 0.4218856, -0.32294926, 0.67155623), + scale: (99.99994, 99.99994, 99.99999), + ), + source_node: Some("RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.L/UpperArm.L/Arm.L"), + source_mesh: Some("Mesh5"), + source_material: Some("Main"), + skinned: false, + ), + ( + id: "draw:scene0:node36:mesh6:primitive0", + name: "Shoulder.R / Primitive 0", + mesh_label: "Mesh6/Primitive0", + material_id: Some("material:material1"), + material_slot_name: "Main", + material_label: Some("Material1"), + local_transform: ( + translation: (-0.6369861, 2.3718858, -0.020994166), + rotation: (0.6017577, 0.37133226, -0.37133226, -0.6017577), + scale: (99.999954, 99.99997, 99.99994), + ), + source_node: Some("RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/Shoulder.R"), + source_mesh: Some("Mesh6"), + source_material: Some("Main"), + skinned: false, + ), + ( + id: "draw:scene0:node38:mesh7:primitive0", + name: "Arm.R / Primitive 0", + mesh_label: "Mesh7/Primitive0", + material_id: Some("material:material1"), + material_slot_name: "Main", + material_label: Some("Material1"), + local_transform: ( + translation: (-0.82568955, 1.9825263, 0.040243305), + rotation: (0.6276481, 0.35463694, -0.3873087, -0.5747021), + scale: (22.70405, 22.704054, 22.704046), + ), + source_node: Some("RootNode/RobotArmature/Bone/Body/Hips/Abdomen/Torso/Shoulder.R/UpperArm.R/Arm.R"), + source_mesh: Some("Mesh7"), + source_material: Some("Main"), + skinned: false, + ), + ( + id: "draw:scene0:node56:mesh8:primitive0", + name: "Leg.L / Primitive 0", + mesh_label: "Mesh8/Primitive0", + material_id: Some("material:material1"), + material_slot_name: "Main", + material_label: Some("Material1"), + local_transform: ( + translation: (0.6488191, 1.1620699, 0.057466835), + rotation: (0.76387787, -0.05410902, -0.070370995, -0.6392268), + scale: (99.99999, 100.00079, 100.00024), + ), + source_node: Some("RootNode/RobotArmature/Bone/Body/UpperLeg.L/Leg.L"), + source_mesh: Some("Mesh8"), + source_material: Some("Main"), + skinned: false, + ), + ( + id: "draw:scene0:node58:mesh9:primitive0", + name: "LowerLeg.L / Primitive 0", + mesh_label: "Mesh9/Primitive0", + material_id: Some("material:material1"), + material_slot_name: "Main", + material_label: Some("Material1"), + local_transform: ( + translation: (0.68805873, 0.3497491, 0.23465614), + rotation: (-0.6547612, 0.06408298, 0.061960325, 0.7505613), + scale: (100.000015, 100.0006, 100.000496), + ), + source_node: Some("RootNode/RobotArmature/Bone/Body/UpperLeg.L/LowerLeg.L/LowerLeg.L"), + source_mesh: Some("Mesh9"), + source_material: Some("Main"), + skinned: false, + ), + ], + warnings: [ + "Animations are recorded as metadata; animated placement uses SkinnedMeshRenderer.", + "Skinned primitives are excluded from StaticMeshRenderer and use SkinnedMeshRenderer.", + ], +) \ No newline at end of file diff --git a/assets/meshes/generated/b98ef565-3500-49e7-9935-f685fa9b2594.static_mesh.ron b/assets/meshes/generated/b98ef565-3500-49e7-9935-f685fa9b2594.static_mesh.ron index fbcee12..683980a 100644 --- a/assets/meshes/generated/b98ef565-3500-49e7-9935-f685fa9b2594.static_mesh.ron +++ b/assets/meshes/generated/b98ef565-3500-49e7-9935-f685fa9b2594.static_mesh.ron @@ -1,5 +1,5 @@ ( - schema_version: 1, + schema_version: 3, asset_id: "b98ef565-3500-49e7-9935-f685fa9b2594", label: "painted_wooden_chair_02_2k", source: ( @@ -30,7 +30,7 @@ ), parts: [ ( - id: "mesh:mesh1000", + id: "draw:scene0:node1:material0", name: "painted_wooden_chair_02 / Material 0", mesh_label: "Mesh1000", material_id: Some("material:material0"), @@ -44,6 +44,7 @@ source_node: Some("Node1"), source_mesh: Some("Mesh1"), source_material: Some("painted_wooden_chair_02"), + skinned: false, ), ], warnings: [], diff --git a/assets/models/RobotExpressive.glb b/assets/models/RobotExpressive.glb new file mode 100644 index 0000000..7cb7550 --- /dev/null +++ b/assets/models/RobotExpressive.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:047f5e5fb3bb6d378bd1df16ca6137f2a596c99b3a1b5690b4020c05aaf6f319 +size 463988 diff --git a/assets/navigation/generated/navigation_showcase_humanoid.nav.ron b/assets/navigation/generated/navigation_showcase_humanoid.nav.ron new file mode 100644 index 0000000..316112f --- /dev/null +++ b/assets/navigation/generated/navigation_showcase_humanoid.nav.ron @@ -0,0 +1,963 @@ +( + schema_version: 1, + generator: "rerecast-0.3/polyanya-0.16", + source_scene: "assets/levels/navigation_authoring_showcase.scn.ron", + source_fingerprint: "c3f7b1f6eb8cb9ab5d4bb3a9b0d09ce47641c563f47fa75df4e7eb30862d039c", + bounds_actor_id: "navigation-bounds-main", + center: (0.0, 0.0, 0.0), + half_extents: (8.0, 2.0, 8.0), + agent: ( + id: "humanoid", + radius: 0.35, + height: 1.8, + max_climb: 0.45, + max_slope_deg: 45.0, + cell_size_fraction: 3.0, + cell_height_fraction: 6.0, + min_region_size: 1, + merge_region_size: 2, + ), + mesh: ( + layers: [ + ( + vertices: [ + ( + coords: (-1.0, -2.516667), + polygons: [ + 0, + 4294967295, + 1, + 3, + 2, + ], + is_corner: true, + ), + ( + coords: (-0.3000002, -2.516667), + polygons: [ + 4, + 4294967295, + 0, + ], + is_corner: true, + ), + ( + coords: (-0.41666698, -7.3), + polygons: [ + 5, + 4, + 0, + 2, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (-7.3, -1.5833335), + polygons: [ + 3, + 1, + 10, + 12, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (-1.0, -1.5833335), + polygons: [ + 10, + 1, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (-1.0, -2.516667), + polygons: [ + 0, + 4294967295, + 1, + 3, + 2, + ], + is_corner: true, + ), + ( + coords: (-1.0, -2.516667), + polygons: [ + 0, + 4294967295, + 1, + 3, + 2, + ], + is_corner: true, + ), + ( + coords: (-0.41666698, -7.3), + polygons: [ + 5, + 4, + 0, + 2, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (-7.3, -7.3), + polygons: [ + 2, + 3, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (-7.3, -1.5833335), + polygons: [ + 3, + 1, + 10, + 12, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (-0.3000002, -2.516667), + polygons: [ + 4, + 4294967295, + 0, + ], + is_corner: true, + ), + ( + coords: (0.2833333, -2.516667), + polygons: [ + 6, + 4294967295, + 4, + 5, + ], + is_corner: true, + ), + ( + coords: (0.39999962, -7.3), + polygons: [ + 8, + 6, + 5, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (-0.41666698, -7.3), + polygons: [ + 5, + 4, + 0, + 2, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (0.39999962, -7.3), + polygons: [ + 8, + 6, + 5, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (0.2833333, -2.516667), + polygons: [ + 6, + 4294967295, + 4, + 5, + ], + is_corner: true, + ), + ( + coords: (0.9833336, -2.516667), + polygons: [ + 8, + 9, + 7, + 4294967295, + 6, + ], + is_corner: true, + ), + ( + coords: (0.9833336, -2.516667), + polygons: [ + 8, + 9, + 7, + 4294967295, + 6, + ], + is_corner: true, + ), + ( + coords: (0.9833336, -1.5833335), + polygons: [ + 7, + 13, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (7.283333, -1.5833335), + polygons: [ + 15, + 13, + 7, + 9, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (0.9833336, -2.516667), + polygons: [ + 8, + 9, + 7, + 4294967295, + 6, + ], + is_corner: true, + ), + ( + coords: (7.283333, -1.5833335), + polygons: [ + 15, + 13, + 7, + 9, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (7.283333, -7.3), + polygons: [ + 9, + 8, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (0.39999962, -7.3), + polygons: [ + 8, + 6, + 5, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (-1.0, -0.0666666), + polygons: [ + 11, + 12, + 10, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (-1.0, -1.5833335), + polygons: [ + 10, + 1, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (-7.3, -1.5833335), + polygons: [ + 3, + 1, + 10, + 12, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (-7.3, 1.5666666), + polygons: [ + 12, + 11, + 17, + 19, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (-1.0, 1.5666666), + polygons: [ + 17, + 11, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (-1.0, -0.0666666), + polygons: [ + 11, + 12, + 10, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (-7.3, -1.5833335), + polygons: [ + 3, + 1, + 10, + 12, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (7.283333, -1.5833335), + polygons: [ + 15, + 13, + 7, + 9, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (0.9833336, -1.5833335), + polygons: [ + 7, + 13, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (0.9833336, -0.0666666), + polygons: [ + 13, + 15, + 14, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (7.283333, 1.5666666), + polygons: [ + 23, + 21, + 14, + 15, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (7.283333, -1.5833335), + polygons: [ + 15, + 13, + 7, + 9, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (0.9833336, -0.0666666), + polygons: [ + 13, + 15, + 14, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (0.9833336, 1.5666666), + polygons: [ + 14, + 21, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (-0.41666698, 7.283333), + polygons: [ + 24, + 25, + 4294967295, + 18, + 16, + ], + is_corner: true, + ), + ( + coords: (-0.3000002, 2.5), + polygons: [ + 24, + 16, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (-1.0, 2.5), + polygons: [ + 16, + 18, + 19, + 17, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (-1.0, 2.5), + polygons: [ + 16, + 18, + 19, + 17, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (-1.0, 1.5666666), + polygons: [ + 17, + 11, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (-7.3, 1.5666666), + polygons: [ + 12, + 11, + 17, + 19, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (-7.3, 7.283333), + polygons: [ + 19, + 18, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (-0.41666698, 7.283333), + polygons: [ + 24, + 25, + 4294967295, + 18, + 16, + ], + is_corner: true, + ), + ( + coords: (-1.0, 2.5), + polygons: [ + 16, + 18, + 19, + 17, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (-7.3, 1.5666666), + polygons: [ + 12, + 11, + 17, + 19, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (0.9833336, 2.5), + polygons: [ + 21, + 23, + 22, + 20, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (0.2833333, 2.5), + polygons: [ + 20, + 25, + 24, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (0.39999962, 7.283333), + polygons: [ + 20, + 22, + 4294967295, + 25, + ], + is_corner: true, + ), + ( + coords: (7.283333, 1.5666666), + polygons: [ + 23, + 21, + 14, + 15, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (0.9833336, 1.5666666), + polygons: [ + 14, + 21, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (0.9833336, 2.5), + polygons: [ + 21, + 23, + 22, + 20, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (0.9833336, 2.5), + polygons: [ + 21, + 23, + 22, + 20, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (0.39999962, 7.283333), + polygons: [ + 20, + 22, + 4294967295, + 25, + ], + is_corner: true, + ), + ( + coords: (7.283333, 7.283333), + polygons: [ + 22, + 23, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (7.283333, 1.5666666), + polygons: [ + 23, + 21, + 14, + 15, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (-0.41666698, 7.283333), + polygons: [ + 24, + 25, + 4294967295, + 18, + 16, + ], + is_corner: true, + ), + ( + coords: (0.39999962, 7.283333), + polygons: [ + 20, + 22, + 4294967295, + 25, + ], + is_corner: true, + ), + ( + coords: (0.2833333, 2.5), + polygons: [ + 20, + 25, + 24, + 4294967295, + ], + is_corner: true, + ), + ( + coords: (-0.3000002, 2.5), + polygons: [ + 24, + 16, + 4294967295, + ], + is_corner: true, + ), + ], + polygons: [ + ( + vertices: [ + 2, + 1, + 0, + ], + is_one_way: false, + ), + ( + vertices: [ + 4, + 3, + 5, + ], + is_one_way: false, + ), + ( + vertices: [ + 6, + 8, + 7, + ], + is_one_way: false, + ), + ( + vertices: [ + 6, + 9, + 8, + ], + is_one_way: false, + ), + ( + vertices: [ + 13, + 11, + 10, + ], + is_one_way: false, + ), + ( + vertices: [ + 13, + 12, + 11, + ], + is_one_way: false, + ), + ( + vertices: [ + 15, + 14, + 16, + ], + is_one_way: false, + ), + ( + vertices: [ + 18, + 17, + 19, + ], + is_one_way: false, + ), + ( + vertices: [ + 22, + 20, + 23, + ], + is_one_way: false, + ), + ( + vertices: [ + 22, + 21, + 20, + ], + is_one_way: false, + ), + ( + vertices: [ + 25, + 24, + 26, + ], + is_one_way: false, + ), + ( + vertices: [ + 27, + 29, + 28, + ], + is_one_way: false, + ), + ( + vertices: [ + 27, + 30, + 29, + ], + is_one_way: false, + ), + ( + vertices: [ + 32, + 31, + 33, + ], + is_one_way: false, + ), + ( + vertices: [ + 36, + 34, + 37, + ], + is_one_way: false, + ), + ( + vertices: [ + 36, + 35, + 34, + ], + is_one_way: false, + ), + ( + vertices: [ + 40, + 39, + 38, + ], + is_one_way: false, + ), + ( + vertices: [ + 42, + 41, + 43, + ], + is_one_way: false, + ), + ( + vertices: [ + 44, + 46, + 45, + ], + is_one_way: false, + ), + ( + vertices: [ + 44, + 47, + 46, + ], + is_one_way: false, + ), + ( + vertices: [ + 49, + 48, + 50, + ], + is_one_way: false, + ), + ( + vertices: [ + 52, + 51, + 53, + ], + is_one_way: false, + ), + ( + vertices: [ + 54, + 56, + 55, + ], + is_one_way: false, + ), + ( + vertices: [ + 54, + 57, + 56, + ], + is_one_way: false, + ), + ( + vertices: [ + 60, + 58, + 61, + ], + is_one_way: false, + ), + ( + vertices: [ + 60, + 59, + 58, + ], + is_one_way: false, + ), + ], + offset: (0.0, 0.0), + baked_polygons: None, + islands: None, + height: [ + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + 0.10000008, + ], + ), + ], + search_delta: 0.1, + search_steps: 2, + ), + links: [ + ( + actor_id: "navigation-link-center", + start: (-1.5, 0.0, 0.0), + end: (1.5, 0.0, 0.0), + bidirectional: true, + cost: 1.2, + enabled: true, + ), + ], + areas: [ + ( + actor_id: "navigation-area-slow", + id: "slow", + center: (-4.0, 0.5, -4.0), + half_extents: (1.5, 1.0, 1.5), + cost: 1.5, + walkable: true, + ), + ], +) \ No newline at end of file diff --git a/assets/prefabs/example_base.scn.ron b/assets/prefabs/example_base.scn.ron index 23ca1dc..e3f91e6 100644 --- a/assets/prefabs/example_base.scn.ron +++ b/assets/prefabs/example_base.scn.ron @@ -1,19 +1,17 @@ -(schema_version: 2, - resources: {}, - entities: { - 1: ( - components: { - "bevy_ecs::name::Name": "Example Base Mesh", - "bevy_transform::components::transform::Transform": ( +(schema_version: 4,resources: { + }, + entities: { + 1: (components: { + "bevy_ecs::name::Name": "Example Base Mesh", + "bevy_transform::components::transform::Transform": ( translation: (0.0, 0.0, 0.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorId": ("base-mesh"), - "shared::components::ActorKind": Empty, - "shared::components::EditorVisibility": (visible: true), - "shared::components::LevelObject": (), - }, - ), - }, -) + "shared::components::ActorId": ("base-mesh"), + "shared::components::ActorKind": Empty, + "shared::components::EditorVisibility": (visible: true), + "shared::components::LevelObject": (), + }), + }, +) \ No newline at end of file diff --git a/assets/prefabs/example_nested.scn.ron b/assets/prefabs/example_nested.scn.ron index 8039fbd..a98c3c4 100644 --- a/assets/prefabs/example_nested.scn.ron +++ b/assets/prefabs/example_nested.scn.ron @@ -1,23 +1,21 @@ -(schema_version: 2, - resources: {}, - entities: { - 1: ( - components: { - "bevy_ecs::name::Name": "Example Base Link", - "bevy_transform::components::transform::Transform": ( +(schema_version: 4,resources: { + }, + entities: { + 1: (components: { + "bevy_ecs::name::Name": "Example Base Link", + "bevy_transform::components::transform::Transform": ( translation: (0.0, 0.0, 0.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorId": ("base-link"), - "shared::components::ActorKind": PrefabAnchor, - "shared::components::LevelObject": (), - "shared::components::PrefabInstance": ( + "shared::components::ActorId": ("base-link"), + "shared::components::ActorKind": PrefabAnchor, + "shared::components::LevelObject": (), + "shared::components::PrefabInstance": ( asset_id: "11111111-1111-4111-8111-111111111111", source_path: "assets/prefabs/example_base.scn.ron", overrides_ron: None, ), - }, - ), - }, -) + }), + }, +) \ No newline at end of file diff --git a/assets/prefabs/example_variant.scn.ron b/assets/prefabs/example_variant.scn.ron index 852daf7..58623bd 100644 --- a/assets/prefabs/example_variant.scn.ron +++ b/assets/prefabs/example_variant.scn.ron @@ -1,23 +1,21 @@ -(schema_version: 2, - resources: {}, - entities: { - 1: ( - components: { - "bevy_ecs::name::Name": "Example Nested Variant Link", - "bevy_transform::components::transform::Transform": ( +(schema_version: 4,resources: { + }, + entities: { + 1: (components: { + "bevy_ecs::name::Name": "Example Nested Variant Link", + "bevy_transform::components::transform::Transform": ( translation: (0.0, 0.0, 0.0), rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), - "shared::components::ActorId": ("nested-link"), - "shared::components::ActorKind": PrefabAnchor, - "shared::components::LevelObject": (), - "shared::components::PrefabInstance": ( + "shared::components::ActorId": ("nested-link"), + "shared::components::ActorKind": PrefabAnchor, + "shared::components::LevelObject": (), + "shared::components::PrefabInstance": ( asset_id: "22222222-2222-4222-8222-222222222222", source_path: "assets/prefabs/example_nested.scn.ron", overrides_ron: Some("(format_version:2,source_revision:None,transform:None,material:None,child_visibility:{},properties:[],components:[(target:(instance_chain:[\"base-link\"],actor_id:\"base-mesh\"),component_type:\"shared::components::EditorVisibility\",base_component_ron:Some(\"(visible:true)\"),value_component_ron:Some(\"(visible:false)\"))],structural:[])"), ), - }, - ), - }, -) + }), + }, +) \ No newline at end of file diff --git a/assets/shaders/blacksite_surface.wgsl b/assets/shaders/blacksite_surface.wgsl new file mode 100644 index 0000000..5f76d10 --- /dev/null +++ b/assets/shaders/blacksite_surface.wgsl @@ -0,0 +1,42 @@ +#import bevy_pbr::{ + pbr_fragment::pbr_input_from_standard_material, + pbr_functions::alpha_discard, +} + +#ifdef PREPASS_PIPELINE +#import bevy_pbr::{ + prepass_io::{VertexOutput, FragmentOutput}, + pbr_deferred_functions::deferred_output, +} +#else +#import bevy_pbr::{ + forward_io::{VertexOutput, FragmentOutput}, + pbr_functions::{apply_pbr_lighting, main_pass_post_lighting_processing}, +} +#endif + +struct SurfaceUniform { + shader_id: u32, + flags: u32, + alpha_cutoff: f32, + abi_version: u32, + params: array, 16>, + uv_transforms: array, 8>, +} + +@group(#{MATERIAL_BIND_GROUP}) @binding(100) var surface_uniform: SurfaceUniform; + +@fragment +fn fragment(in: VertexOutput, @builtin(front_facing) is_front: bool) -> FragmentOutput { + var pbr_input = pbr_input_from_standard_material(in, is_front); + pbr_input.material.base_color = alpha_discard(pbr_input.material, pbr_input.material.base_color); +#ifdef PREPASS_PIPELINE + return deferred_output(in, pbr_input); +#else + var out: FragmentOutput; + out.color = apply_pbr_lighting(pbr_input); + out.color = main_pass_post_lighting_processing(pbr_input, out.color); + return out; +#endif +} + diff --git a/assets/shaders/standard_lit.shader.ron b/assets/shaders/standard_lit.shader.ron index 37a2c19..02d4ca7 100644 --- a/assets/shaders/standard_lit.shader.ron +++ b/assets/shaders/standard_lit.shader.ron @@ -1,18 +1,73 @@ ( + schema_version: 1, label: "Standard Lit", kind: StandardLit, wgsl_path: None, parameters: [ - (name: "base_color", display_name: "Base Color", group: "Surface", property_type: Color), - (name: "metallic", display_name: "Metallic", group: "Surface", property_type: Float(min: Some(0.0), max: Some(1.0))), - (name: "roughness", display_name: "Roughness", group: "Surface", property_type: Float(min: Some(0.0), max: Some(1.0))), - (name: "emissive_color", display_name: "Emissive Color", group: "Emission", property_type: Color), - (name: "emissive_intensity", display_name: "Emissive Nits", group: "Emission", property_type: Float(min: Some(0.0), max: Some(20000.0))), - (name: "base_color_texture", display_name: "Base Color Texture", group: "Textures", property_type: Texture), - (name: "normal_map_texture", display_name: "Normal Map", group: "Textures", property_type: Texture), - (name: "metallic_roughness_texture", display_name: "Metallic/Roughness Texture", group: "Textures", property_type: Texture), - (name: "emissive_texture", display_name: "Emissive Texture", group: "Textures", property_type: Texture), + ( + name: "base_color", + display_name: "Base Color", + group: "Surface", + property_type: Color, + ), + ( + name: "metallic", + display_name: "Metallic", + group: "Surface", + property_type: Float( + min: Some(0.0), + max: Some(1.0), + ), + ), + ( + name: "roughness", + display_name: "Roughness", + group: "Surface", + property_type: Float( + min: Some(0.0), + max: Some(1.0), + ), + ), + ( + name: "emissive_color", + display_name: "Emissive Color", + group: "Emission", + property_type: Color, + ), + ( + name: "emissive_intensity", + display_name: "Emissive Nits", + group: "Emission", + property_type: Float( + min: Some(0.0), + max: Some(20000.0), + ), + ), + ( + name: "base_color_texture", + display_name: "Base Color Texture", + group: "Textures", + property_type: Texture, + ), + ( + name: "normal_map_texture", + display_name: "Normal Map", + group: "Textures", + property_type: Texture, + ), + ( + name: "metallic_roughness_texture", + display_name: "Metallic/Roughness Texture", + group: "Textures", + property_type: Texture, + ), + ( + name: "emissive_texture", + display_name: "Emissive Texture", + group: "Textures", + property_type: Texture, + ), ], default_values: [], default_textures: [], -) +) \ No newline at end of file diff --git a/assets/shaders/surface_tint.shader.ron b/assets/shaders/surface_tint.shader.ron new file mode 100644 index 0000000..6c346a3 --- /dev/null +++ b/assets/shaders/surface_tint.shader.ron @@ -0,0 +1,80 @@ +( + schema_version: 1, + label: "Surface Tint", + kind: Custom, + wgsl_path: Some("assets/shaders/surface_tint.wgsl"), + parameters: [ + ( + name: "tint", + display_name: "Tint", + group: "Surface", + property_type: Color, + ), + ( + name: "roughness", + display_name: "Roughness", + group: "Surface", + property_type: Float( + min: Some(0.001), + max: Some(1.0), + ), + ), + ( + name: "metallic", + display_name: "Metallic", + group: "Surface", + property_type: Float( + min: Some(0.0), + max: Some(1.0), + ), + ), + ( + name: "emissive", + display_name: "Emissive", + group: "Emission", + property_type: Color, + ), + ( + name: "emissive_intensity", + display_name: "Emissive Nits", + group: "Emission", + property_type: Float( + min: Some(0.0), + max: Some(20000.0), + ), + ), + ], + default_values: [ + ( + name: "tint", + value: Color(( + r: 0.12, + g: 0.42, + b: 0.95, + a: 1.0, + )), + ), + ( + name: "roughness", + value: Float(0.28), + ), + ( + name: "metallic", + value: Float(0.15), + ), + ( + name: "emissive", + value: Color(( + r: 0.02, + g: 0.08, + b: 0.25, + a: 1.0, + )), + ), + ( + name: "emissive_intensity", + value: Float(3.0), + ), + ], + default_textures: [], +) \ No newline at end of file diff --git a/assets/shaders/surface_tint.wgsl b/assets/shaders/surface_tint.wgsl new file mode 100644 index 0000000..afa02d8 --- /dev/null +++ b/assets/shaders/surface_tint.wgsl @@ -0,0 +1,14 @@ +fn evaluate( + input: SurfaceInput, + params: SurfaceParams, + samples: SurfaceSamples, +) -> Surface { + var surface = surface_default(); + surface.base_color = params.lanes[0]; + surface.perceptual_roughness = params.lanes[1].x; + surface.metallic = params.lanes[2].x; + surface.emissive = params.lanes[3].rgb * params.lanes[4].x; + surface.reflectance = 0.5; + surface.occlusion = 1.0; + return surface; +} diff --git a/assets/shaders/unlit.shader.ron b/assets/shaders/unlit.shader.ron index 31d2686..bf04d97 100644 --- a/assets/shaders/unlit.shader.ron +++ b/assets/shaders/unlit.shader.ron @@ -1,13 +1,37 @@ ( + schema_version: 1, label: "Unlit", kind: Unlit, wgsl_path: None, parameters: [ - (name: "base_color", display_name: "Base Color", group: "Surface", property_type: Color), - (name: "base_color_texture", display_name: "Base Color Texture", group: "Textures", property_type: Texture), - (name: "emissive_color", display_name: "Emissive Color", group: "Emission", property_type: Color), - (name: "emissive_intensity", display_name: "Emissive Nits", group: "Emission", property_type: Float(min: Some(0.0), max: Some(20000.0))), + ( + name: "base_color", + display_name: "Base Color", + group: "Surface", + property_type: Color, + ), + ( + name: "base_color_texture", + display_name: "Base Color Texture", + group: "Textures", + property_type: Texture, + ), + ( + name: "emissive_color", + display_name: "Emissive Color", + group: "Emission", + property_type: Color, + ), + ( + name: "emissive_intensity", + display_name: "Emissive Nits", + group: "Emission", + property_type: Float( + min: Some(0.0), + max: Some(20000.0), + ), + ), ], default_values: [], default_textures: [], -) +) \ No newline at end of file diff --git a/crates/blacksite_surface/Cargo.toml b/crates/blacksite_surface/Cargo.toml new file mode 100644 index 0000000..1e40fc5 --- /dev/null +++ b/crates/blacksite_surface/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "blacksite_surface" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "Blacksite raster and ray-tracing Surface ABI material runtime" + +[dependencies] +bevy.workspace = true +naga = { version = "29", features = ["wgsl-in"] } +ron = "0.8" +shared.workspace = true +uuid = "1" diff --git a/crates/blacksite_surface/src/lib.rs b/crates/blacksite_surface/src/lib.rs new file mode 100644 index 0000000..f029f75 --- /dev/null +++ b/crates/blacksite_surface/src/lib.rs @@ -0,0 +1,777 @@ +//! Blacksite Surface ABI v1. +//! +//! The authoring layer persists named values. This crate validates and packs them into a fixed GPU +//! contract and supplies the same surface evaluator source to raster and Solari integrations. + +use std::collections::HashMap; +use std::fs; + +use bevy::mesh::MeshVertexBufferLayoutRef; +use bevy::pbr::{ + ExtendedMaterial, MaterialExtension, MaterialExtensionKey, MaterialExtensionPipeline, +}; +use bevy::prelude::*; +use bevy::render::extract_resource::ExtractResource; +use bevy::render::render_resource::{ + AsBindGroup, RenderPipelineDescriptor, ShaderType, SpecializedMeshPipelineError, +}; +use bevy::shader::{Shader, ShaderRef}; +use shared::{ + asset_server_path, material_from_desc, HydratedRendererMaterialBinding, MaterialAsset, + MaterialInstanceAsset, MaterialParameterValue, MaterialRef, ShaderPropertyType, + ShaderSchemaAsset, MATERIAL_INSTANCE_SCHEMA_VERSION, +}; + +pub const SURFACE_ABI_VERSION: u32 = 1; +pub const SURFACE_PARAMETER_LANES: usize = 16; +pub const SURFACE_TEXTURE_SLOTS: usize = 8; +pub const DEFAULT_SURFACE_SHADER_PATH: &str = "shaders/blacksite_surface.wgsl"; + +#[derive(ShaderType, Reflect, Debug, Clone, Copy, PartialEq)] +pub struct SurfaceUniform { + pub shader_id: u32, + pub flags: u32, + pub alpha_cutoff: f32, + pub abi_version: u32, + pub params: [Vec4; SURFACE_PARAMETER_LANES], + pub uv_transforms: [Vec4; SURFACE_TEXTURE_SLOTS], +} + +impl Default for SurfaceUniform { + fn default() -> Self { + Self { + shader_id: 0, + flags: 0, + alpha_cutoff: 0.5, + abi_version: SURFACE_ABI_VERSION, + params: [Vec4::ZERO; SURFACE_PARAMETER_LANES], + uv_transforms: [Vec4::new(1.0, 1.0, 0.0, 0.0); SURFACE_TEXTURE_SLOTS], + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct SurfaceExtensionKey { + shader: AssetId, +} + +impl From<&SurfaceExtension> for SurfaceExtensionKey { + fn from(extension: &SurfaceExtension) -> Self { + Self { + shader: extension.shader.id(), + } + } +} + +#[derive(Asset, AsBindGroup, Reflect, Debug, Clone)] +#[bind_group_data(SurfaceExtensionKey)] +pub struct SurfaceExtension { + #[uniform(100)] + pub uniform: SurfaceUniform, + #[texture(101)] + #[sampler(102)] + pub texture0: Option>, + #[texture(103)] + #[sampler(104)] + pub texture1: Option>, + #[texture(105)] + #[sampler(106)] + pub texture2: Option>, + #[texture(107)] + #[sampler(108)] + pub texture3: Option>, + #[texture(109)] + #[sampler(110)] + pub texture4: Option>, + #[texture(111)] + #[sampler(112)] + pub texture5: Option>, + #[texture(113)] + #[sampler(114)] + pub texture6: Option>, + #[texture(115)] + #[sampler(116)] + pub texture7: Option>, + #[reflect(ignore)] + pub shader: Handle, +} + +impl Default for SurfaceExtension { + fn default() -> Self { + Self { + uniform: SurfaceUniform::default(), + texture0: None, + texture1: None, + texture2: None, + texture3: None, + texture4: None, + texture5: None, + texture6: None, + texture7: None, + shader: Handle::default(), + } + } +} + +impl SurfaceExtension { + pub fn set_texture(&mut self, index: usize, handle: Option>) { + match index { + 0 => self.texture0 = handle, + 1 => self.texture1 = handle, + 2 => self.texture2 = handle, + 3 => self.texture3 = handle, + 4 => self.texture4 = handle, + 5 => self.texture5 = handle, + 6 => self.texture6 = handle, + 7 => self.texture7 = handle, + _ => {} + } + } +} + +impl MaterialExtension for SurfaceExtension { + fn fragment_shader() -> ShaderRef { + DEFAULT_SURFACE_SHADER_PATH.into() + } + + fn deferred_fragment_shader() -> ShaderRef { + DEFAULT_SURFACE_SHADER_PATH.into() + } + + fn prepass_fragment_shader() -> ShaderRef { + DEFAULT_SURFACE_SHADER_PATH.into() + } + + fn specialize( + _pipeline: &MaterialExtensionPipeline, + descriptor: &mut RenderPipelineDescriptor, + _layout: &MeshVertexBufferLayoutRef, + key: MaterialExtensionKey, + ) -> Result<(), SpecializedMeshPipelineError> { + if let (AssetId::Uuid { uuid }, Some(fragment)) = + (key.bind_group_data.shader, descriptor.fragment.as_mut()) + { + fragment.shader = Handle::Uuid(uuid, Default::default()); + } + Ok(()) + } +} + +pub type SurfaceMaterial = ExtendedMaterial; + +#[derive(Resource, Default)] +pub struct SurfaceMaterialCache { + handles: HashMap>, + standard_handles: HashMap>, + revisions: HashMap, + standard_only: HashMap, + dependencies: HashMap>, + failed_revisions: HashMap, +} + +enum BuiltRendererMaterial { + Standard(StandardMaterial), + Surface(SurfaceMaterial), +} + +#[derive(Resource, Default, Debug, Clone)] +pub struct SurfaceDiagnostics(pub Vec); + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SurfaceEvaluatorRecord { + pub shader_id: u32, + pub evaluator_source: String, + pub revision: u64, + pub alpha_mode: shared::MaterialAlphaMode, +} + +/// Active, validated evaluator generation consumed by both raster and Solari render worlds. +#[derive(Resource, ExtractResource, Default, Debug, Clone)] +pub struct SurfaceEvaluatorRegistry { + pub evaluators: HashMap, +} + +pub struct SurfaceMaterialPlugin; + +impl Plugin for SurfaceMaterialPlugin { + fn build(&self, app: &mut App) { + app.add_plugins(MaterialPlugin::::default()) + .init_resource::() + .init_resource::() + .init_resource::() + .add_systems(Update, sync_surface_material_bindings); + } +} + +#[allow(clippy::too_many_arguments)] +fn sync_surface_material_bindings( + mut commands: Commands, + asset_server: Res, + mut shaders: ResMut>, + mut standard_materials: ResMut>, + mut surface_materials: ResMut>, + mut cache: ResMut, + mut diagnostics: ResMut, + mut evaluator_registry: ResMut, + bindings: Query<( + Entity, + &HydratedRendererMaterialBinding, + Option<&MeshMaterial3d>, + Option<&MeshMaterial3d>, + )>, +) { + for (entity, binding, standard_handle, surface_handle) in &bindings { + let Some(reference) = binding.effective_material.as_ref() else { + continue; + }; + let Some(path) = reference.0.source_path.as_deref() else { + continue; + }; + let dependencies = cache + .dependencies + .get(reference) + .cloned() + .unwrap_or_else(|| vec![path.to_string()]); + let revision = dependency_revision(&dependencies); + if cache.failed_revisions.get(reference) == Some(&revision) { + continue; + } + if cache.standard_only.get(reference) == Some(&revision) { + if let Some(handle) = cache.standard_handles.get(reference).cloned() { + if standard_handle.is_none_or(|current| current.0 != handle) + || surface_handle.is_some() + { + commands + .entity(entity) + .remove::>() + .insert(MeshMaterial3d(handle)); + } + } + continue; + } + let stale = cache.revisions.get(reference) != Some(&revision); + let handle = if !stale { + cache.handles.get(reference).cloned() + } else { + None + }; + let handle = match handle { + Some(handle) => handle, + None => match build_surface_material( + reference, + path, + &asset_server, + &mut shaders, + &mut standard_materials, + standard_handle, + &mut evaluator_registry, + ) { + Ok(BuiltRendererMaterial::Surface(material)) => { + cache.standard_only.remove(reference); + cache.standard_handles.remove(reference); + cache.failed_revisions.remove(reference); + if let Some(existing) = cache.handles.get(reference).cloned() { + if let Some(mut slot) = surface_materials.get_mut(&existing) { + *slot = material; + } + existing + } else { + let handle = surface_materials.add(material); + cache.handles.insert(reference.clone(), handle.clone()); + handle + } + } + Ok(BuiltRendererMaterial::Standard(material)) => { + // Plain Material assets stay on Bevy's StandardMaterial path, while still + // sharing one live-updated handle across every renderer slot. + let handle = standard_handle + .map(|handle| handle.0.clone()) + .or_else(|| cache.standard_handles.get(reference).cloned()) + .unwrap_or_else(|| standard_materials.add(material.clone())); + if let Some(mut slot) = standard_materials.get_mut(&handle) { + *slot = material; + } + cache.handles.remove(reference); + cache.revisions.remove(reference); + cache + .standard_handles + .insert(reference.clone(), handle.clone()); + let next_dependencies = + material_dependency_paths(path).unwrap_or_else(|_| vec![path.to_string()]); + let next_revision = dependency_revision(&next_dependencies); + cache + .dependencies + .insert(reference.clone(), next_dependencies); + cache.standard_only.insert(reference.clone(), next_revision); + cache.failed_revisions.remove(reference); + evaluator_registry + .evaluators + .remove(&stable_shader_id(reference)); + if standard_handle.is_none_or(|current| current.0 != handle) + || surface_handle.is_some() + { + commands + .entity(entity) + .remove::>() + .insert(MeshMaterial3d(handle)); + } + continue; + } + Err(error) => { + cache.failed_revisions.insert(reference.clone(), revision); + diagnostics.0.push(format!( + "material {} could not build Surface ABI: {error}", + reference.0.label + )); + continue; + } + }, + }; + let next_dependencies = + material_dependency_paths(path).unwrap_or_else(|_| vec![path.to_string()]); + let next_revision = dependency_revision(&next_dependencies); + cache + .dependencies + .insert(reference.clone(), next_dependencies); + cache.revisions.insert(reference.clone(), next_revision); + if surface_handle.is_none_or(|current| current.0 != handle) { + commands + .entity(entity) + .remove::>() + .insert(MeshMaterial3d(handle)); + } + } +} + +fn build_surface_material( + reference: &MaterialRef, + path: &str, + asset_server: &AssetServer, + shaders: &mut Assets, + standard_materials: &mut Assets, + existing_standard: Option<&MeshMaterial3d>, + evaluator_registry: &mut SurfaceEvaluatorRegistry, +) -> Result { + let (mut asset, instance) = match MaterialAsset::load_from_path(path) { + Ok(asset) => (asset, None), + Err(_) => { + let instance = MaterialInstanceAsset::load_from_path(path)?; + if instance.schema_version != MATERIAL_INSTANCE_SCHEMA_VERSION { + return Err(format!( + "unsupported material-instance schema {}", + instance.schema_version + )); + } + let base_path = instance + .base + .0 + .source_path + .as_deref() + .ok_or_else(|| "material instance base has no loadable path".to_string())?; + (MaterialAsset::load_from_path(base_path)?, Some(instance)) + } + }; + if let Some(instance) = instance.as_ref() { + instance.apply_to(&mut asset.material); + } + let schema_path = asset + .shader_ref + .as_ref() + .and_then(|reference| reference.source_path.as_deref()) + .or(asset.material.shader.schema_path.as_deref()) + .or(asset + .shader + .as_deref() + .filter(|path| path.ends_with(".ron"))); + let schema = schema_path + .map(ShaderSchemaAsset::load_from_path) + .transpose()?; + let evaluator_source = schema + .as_ref() + .and_then(|schema| schema.wgsl_path.as_deref()) + .map(fs::read_to_string) + .transpose() + .map_err(|error| format!("could not read surface WGSL: {error}"))?; + + let mut base = existing_standard + .and_then(|handle| standard_materials.get(&handle.0).cloned()) + .unwrap_or_else(|| material_from_desc(asset_server, &asset.material)); + base.alpha_mode = match asset.render_state.alpha_mode { + shared::MaterialAlphaMode::Opaque => AlphaMode::Opaque, + shared::MaterialAlphaMode::Cutout => AlphaMode::Mask(asset.render_state.alpha_cutoff), + }; + base.cull_mode = + (!asset.render_state.double_sided).then_some(bevy::render::render_resource::Face::Back); + + let Some(evaluator) = evaluator_source.as_deref() else { + return Ok(BuiltRendererMaterial::Standard(base)); + }; + validate_surface_evaluator(evaluator)?; + let runtime_shader_id = stable_shader_id(reference); + evaluator_registry.evaluators.insert( + runtime_shader_id, + SurfaceEvaluatorRecord { + shader_id: runtime_shader_id, + evaluator_source: evaluator.to_string(), + revision: evaluator_revision(evaluator), + alpha_mode: asset.render_state.alpha_mode, + }, + ); + let virtual_path = format!("generated://surface/{}.wgsl", reference.0.asset_id); + let shader_uuid = shader_uuid_for_material(reference); + let shader_id = AssetId::::Uuid { uuid: shader_uuid }; + shaders + .insert( + shader_id, + Shader::from_wgsl(compose_surface_shader(evaluator), virtual_path), + ) + .map_err(|error| format!("could not replace generated surface shader: {error}"))?; + let shader = Handle::Uuid(shader_uuid, Default::default()); + + let mut extension = SurfaceExtension { + shader, + ..default() + }; + extension.uniform.alpha_cutoff = asset.render_state.alpha_cutoff; + extension.uniform.shader_id = runtime_shader_id; + if let Some(schema) = schema.as_ref() { + pack_schema_values( + &mut extension, + schema, + &asset.material, + instance.as_ref(), + asset_server, + )?; + } + Ok(BuiltRendererMaterial::Surface(ExtendedMaterial { + base, + extension, + })) +} + +fn material_dependency_paths(path: &str) -> Result, String> { + let mut dependencies = vec![path.to_string()]; + let asset = match MaterialAsset::load_from_path(path) { + Ok(asset) => asset, + Err(_) => { + let instance = MaterialInstanceAsset::load_from_path(path)?; + let base_path = instance + .base + .0 + .source_path + .as_deref() + .ok_or_else(|| "material instance base has no loadable path".to_string())?; + dependencies.push(base_path.to_string()); + MaterialAsset::load_from_path(base_path)? + } + }; + let schema_path = asset + .shader_ref + .as_ref() + .and_then(|reference| reference.source_path.as_deref()) + .or(asset.material.shader.schema_path.as_deref()) + .or(asset + .shader + .as_deref() + .filter(|path| path.ends_with(".ron"))); + if let Some(schema_path) = schema_path { + dependencies.push(schema_path.to_string()); + if let Some(wgsl_path) = ShaderSchemaAsset::load_from_path(schema_path)?.wgsl_path { + dependencies.push(wgsl_path); + } + } + dependencies.sort(); + dependencies.dedup(); + Ok(dependencies) +} + +fn dependency_revision(paths: &[String]) -> u64 { + let mut hash = 0xcbf29ce484222325u64; + for path in paths { + for byte in path.bytes() { + hash ^= u64::from(byte); + hash = hash.wrapping_mul(0x100000001b3); + } + match fs::metadata(path) { + Ok(metadata) => { + for byte in metadata.len().to_le_bytes() { + hash ^= u64::from(byte); + hash = hash.wrapping_mul(0x100000001b3); + } + if let Ok(modified) = metadata.modified() { + if let Ok(duration) = modified.duration_since(std::time::UNIX_EPOCH) { + for byte in duration.as_nanos().to_le_bytes() { + hash ^= u64::from(byte); + hash = hash.wrapping_mul(0x100000001b3); + } + } + } + } + Err(_) => { + hash ^= u64::MAX; + hash = hash.wrapping_mul(0x100000001b3); + } + } + } + hash +} + +fn stable_shader_id(reference: &MaterialRef) -> u32 { + let mut hash = 0x811c9dc5u32; + for byte in reference.0.asset_id.bytes() { + hash ^= u32::from(byte); + hash = hash.wrapping_mul(0x01000193); + } + hash +} + +fn evaluator_revision(source: &str) -> u64 { + let mut hash = 0xcbf29ce484222325u64; + for byte in source.bytes() { + hash ^= u64::from(byte); + hash = hash.wrapping_mul(0x100000001b3); + } + hash +} + +fn shader_uuid_for_material(reference: &MaterialRef) -> uuid::Uuid { + if let Ok(source) = uuid::Uuid::parse_str(&reference.0.asset_id) { + let mixed = source.as_u128() ^ 0x7f8c_4e90_f9ab_4f28_9b44_47c1_b2d5_5ac1u128; + return uuid::Uuid::from_u128(mixed); + } + let mut bytes = [0u8; 16]; + for (index, byte) in reference.0.asset_id.bytes().enumerate() { + bytes[index % 16] = bytes[index % 16].wrapping_mul(31).wrapping_add(byte); + } + uuid::Uuid::from_bytes(bytes) +} + +fn pack_schema_values( + extension: &mut SurfaceExtension, + schema: &ShaderSchemaAsset, + material: &shared::MaterialDesc, + instance: Option<&MaterialInstanceAsset>, + asset_server: &AssetServer, +) -> Result<(), String> { + let mut numeric_index = 0usize; + let mut texture_index = 0usize; + for property in &schema.parameters { + if matches!(property.property_type, ShaderPropertyType::Texture) { + if texture_index >= SURFACE_TEXTURE_SLOTS { + return Err(format!( + "surface schema exceeds {SURFACE_TEXTURE_SLOTS} textures" + )); + } + let binding = instance + .and_then(|instance| { + instance + .textures + .iter() + .find(|value| value.name == property.name) + }) + .or_else(|| { + material + .textures + .iter() + .find(|value| value.name == property.name) + }); + let handle = binding + .and_then(|binding| binding.texture.as_ref()) + .and_then(|reference| reference.source_path.as_deref()) + .map(asset_server_path) + .map(|path| asset_server.load(path)); + extension.set_texture(texture_index, handle); + texture_index += 1; + continue; + } + if numeric_index >= SURFACE_PARAMETER_LANES { + return Err(format!( + "surface schema exceeds {SURFACE_PARAMETER_LANES} parameter lanes" + )); + } + let value = instance + .and_then(|instance| { + instance + .parameters + .iter() + .find(|value| value.name == property.name) + }) + .or_else(|| { + material + .parameters + .iter() + .find(|value| value.name == property.name) + }) + .or_else(|| { + schema + .default_values + .iter() + .find(|value| value.name == property.name) + }); + extension.uniform.params[numeric_index] = value + .map(|value| parameter_lane(&value.value, &property.property_type)) + .transpose()? + .unwrap_or(Vec4::ZERO); + numeric_index += 1; + } + Ok(()) +} + +fn parameter_lane( + value: &MaterialParameterValue, + property_type: &ShaderPropertyType, +) -> Result { + match (value, property_type) { + (MaterialParameterValue::Bool(value), ShaderPropertyType::Bool) => { + Ok(Vec4::new(u32::from(*value) as f32, 0.0, 0.0, 0.0)) + } + (MaterialParameterValue::Float(value), ShaderPropertyType::Float { .. }) => { + Ok(Vec4::new(*value, 0.0, 0.0, 0.0)) + } + (MaterialParameterValue::Vec2(value), ShaderPropertyType::Vec2) => { + Ok(Vec4::new(value.x, value.y, 0.0, 0.0)) + } + (MaterialParameterValue::Vec3(value), ShaderPropertyType::Vec3) => Ok(value.extend(0.0)), + (MaterialParameterValue::Color(value), ShaderPropertyType::Color) => { + Ok(Vec4::new(value.r, value.g, value.b, value.a)) + } + (MaterialParameterValue::Enum(value), ShaderPropertyType::Enum { options }) => { + let index = options + .iter() + .position(|option| option == value) + .ok_or_else(|| format!("enum value `{value}` is not declared by the schema"))?; + Ok(Vec4::new(index as f32, 0.0, 0.0, 0.0)) + } + _ => Err("material value does not match shader schema type".into()), + } +} + +pub fn validate_surface_evaluator(source: &str) -> Result<(), String> { + if !source.contains("fn evaluate(") { + return Err("surface WGSL must define `fn evaluate(`".into()); + } + for forbidden in [ + "@group", + "@binding", + "@vertex", + "@fragment", + "@compute", + "var<", + "rayQuery", + "dpdx", + "dpdy", + "fwidth", + "discard", + "Barrier", + "subgroup", + "textureStore", + "atomic", + ] { + if source.contains(forbidden) { + return Err(format!( + "surface WGSL uses forbidden construct `{forbidden}`" + )); + } + } + let validation_source = format!("{EVALUATOR_VALIDATION_PRELUDE}\n{source}"); + let module = naga::front::wgsl::parse_str(&validation_source) + .map_err(|error| format!("surface WGSL syntax error: {error}"))?; + naga::valid::Validator::new( + naga::valid::ValidationFlags::all(), + naga::valid::Capabilities::empty(), + ) + .validate(&module) + .map_err(|error| format!("surface WGSL validation error: {error}"))?; + Ok(()) +} + +const EVALUATOR_VALIDATION_PRELUDE: &str = r#" +struct SurfaceInput { + uv0: vec2, + world_position: vec3, + world_normal: vec3, +} +struct SurfaceParams { lanes: array, 16>, } +struct SurfaceSamples { values: array, 8>, } +struct Surface { + base_color: vec4, + normal_ts: vec3, + emissive: vec3, + metallic: f32, + perceptual_roughness: f32, + reflectance: f32, + occlusion: f32, + model: u32, +} +fn surface_default() -> Surface { + var surface: Surface; + surface.base_color = vec4(1.0); + surface.normal_ts = vec3(0.0, 0.0, 1.0); + surface.emissive = vec3(0.0); + surface.metallic = 0.0; + surface.perceptual_roughness = 0.5; + surface.reflectance = 0.5; + surface.occlusion = 1.0; + surface.model = 0u; + return surface; +} +"#; + +pub fn compose_surface_shader(evaluator: &str) -> String { + format!( + "{}\n{}\n{}", + include_str!("surface_header.wgsl"), + evaluator, + include_str!("surface_footer.wgsl") + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn rejects_resource_bindings_and_missing_entry() { + assert!(validate_surface_evaluator("fn nope() {}").is_err()); + assert!(validate_surface_evaluator( + "@group(0) @binding(0) var t: texture_2d; fn evaluate() {}" + ) + .is_err()); + for compute_incompatible in [ + "dpdx(input.uv0.x)", + "dpdy(input.uv0.y)", + "fwidth(input.uv0.x)", + "discard", + "workgroupBarrier()", + "subgroupAdd(1u)", + ] { + let source = format!( + "fn evaluate(input: SurfaceInput, params: SurfaceParams, samples: SurfaceSamples) -> Surface {{ let invalid = {compute_incompatible}; return surface_default(); }}" + ); + assert!( + validate_surface_evaluator(&source).is_err(), + "accepted compute-incompatible evaluator: {compute_incompatible}" + ); + } + assert!(validate_surface_evaluator( + "fn evaluate(input: SurfaceInput, params: SurfaceParams, samples: SurfaceSamples) -> Surface { return missing_symbol; }" + ) + .is_err()); + } + + #[test] + fn accepts_surface_only_module_and_composes_wrapper() { + let source = "fn evaluate(input: SurfaceInput, params: SurfaceParams, samples: SurfaceSamples) -> Surface { return surface_default(); }"; + validate_surface_evaluator(source).unwrap(); + let composed = compose_surface_shader(source); + assert!(composed.contains(source)); + assert!(composed.contains("@fragment")); + } + + #[test] + fn abi_limits_are_stable() { + assert_eq!(SURFACE_PARAMETER_LANES, 16); + assert_eq!(SURFACE_TEXTURE_SLOTS, 8); + assert_eq!(std::mem::size_of::(), 400); + } +} diff --git a/crates/blacksite_surface/src/surface_footer.wgsl b/crates/blacksite_surface/src/surface_footer.wgsl new file mode 100644 index 0000000..de272d8 --- /dev/null +++ b/crates/blacksite_surface/src/surface_footer.wgsl @@ -0,0 +1,41 @@ +@fragment +fn fragment(in: VertexOutput, @builtin(front_facing) is_front: bool) -> FragmentOutput { + var pbr_input = pbr_input_from_standard_material(in, is_front); +#ifdef VERTEX_UVS_A + let uv0 = in.uv; +#else + let uv0 = vec2(0.0); +#endif + let input = SurfaceInput(uv0, pbr_input.world_position.xyz, pbr_input.N); + let params = SurfaceParams(surface_uniform.params); + let surface = evaluate(input, params, surface_samples(uv0)); + pbr_input.material.base_color = surface.base_color; + pbr_input.material.emissive = vec4(surface.emissive, 1.0); + pbr_input.material.metallic = clamp(surface.metallic, 0.0, 1.0); + pbr_input.material.perceptual_roughness = clamp(surface.perceptual_roughness, 0.001, 1.0); + pbr_input.material.reflectance = vec3(clamp(surface.reflectance, 0.0, 1.0)); + pbr_input.diffuse_occlusion = vec3(clamp(surface.occlusion, 0.0, 1.0)); +#ifdef VERTEX_TANGENTS + let surface_tbn = calculate_tbn_mikktspace(pbr_input.world_normal, in.world_tangent); + let surface_nt = normalize(surface.normal_ts); + pbr_input.N = normalize( + surface_nt.x * surface_tbn[0] + + surface_nt.y * surface_tbn[1] + + surface_nt.z * surface_tbn[2] + ); +#endif + if surface.model == BLACKSITE_SURFACE_UNLIT { + pbr_input.material.flags |= STANDARD_MATERIAL_FLAGS_UNLIT_BIT; + } else { + pbr_input.material.flags &= ~STANDARD_MATERIAL_FLAGS_UNLIT_BIT; + } + pbr_input.material.base_color = alpha_discard(pbr_input.material, pbr_input.material.base_color); +#ifdef PREPASS_PIPELINE + return deferred_output(in, pbr_input); +#else + var out: FragmentOutput; + out.color = apply_pbr_lighting(pbr_input); + out.color = main_pass_post_lighting_processing(pbr_input, out.color); + return out; +#endif +} diff --git a/crates/blacksite_surface/src/surface_header.wgsl b/crates/blacksite_surface/src/surface_header.wgsl new file mode 100644 index 0000000..a1a845f --- /dev/null +++ b/crates/blacksite_surface/src/surface_header.wgsl @@ -0,0 +1,103 @@ +#import bevy_pbr::{ + pbr_fragment::pbr_input_from_standard_material, + pbr_functions::{alpha_discard, calculate_tbn_mikktspace}, +} + +#ifdef PREPASS_PIPELINE +#import bevy_pbr::{ + prepass_io::{VertexOutput, FragmentOutput}, + pbr_deferred_functions::deferred_output, +} +#else +#import bevy_pbr::{ + forward_io::{VertexOutput, FragmentOutput}, + pbr_functions::{apply_pbr_lighting, main_pass_post_lighting_processing}, +} +#endif + +const BLACKSITE_SURFACE_ABI_VERSION: u32 = 1u; +const BLACKSITE_SURFACE_UNLIT: u32 = 1u; +const STANDARD_MATERIAL_FLAGS_UNLIT_BIT: u32 = 1u << 5u; + +struct SurfaceUniform { + shader_id: u32, + flags: u32, + alpha_cutoff: f32, + abi_version: u32, + params: array, 16>, + uv_transforms: array, 8>, +} + +@group(#{MATERIAL_BIND_GROUP}) @binding(100) var surface_uniform: SurfaceUniform; +@group(#{MATERIAL_BIND_GROUP}) @binding(101) var surface_texture0: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(102) var surface_sampler0: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(103) var surface_texture1: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(104) var surface_sampler1: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(105) var surface_texture2: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(106) var surface_sampler2: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(107) var surface_texture3: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(108) var surface_sampler3: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(109) var surface_texture4: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(110) var surface_sampler4: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(111) var surface_texture5: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(112) var surface_sampler5: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(113) var surface_texture6: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(114) var surface_sampler6: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(115) var surface_texture7: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(116) var surface_sampler7: sampler; + +struct SurfaceInput { + uv0: vec2, + world_position: vec3, + world_normal: vec3, +} + +struct SurfaceParams { + lanes: array, 16>, +} + +struct SurfaceSamples { + values: array, 8>, +} + +struct Surface { + base_color: vec4, + normal_ts: vec3, + emissive: vec3, + metallic: f32, + perceptual_roughness: f32, + reflectance: f32, + occlusion: f32, + model: u32, +} + +fn surface_default() -> Surface { + var surface: Surface; + surface.base_color = vec4(1.0); + surface.normal_ts = vec3(0.0, 0.0, 1.0); + surface.emissive = vec3(0.0); + surface.metallic = 0.0; + surface.perceptual_roughness = 0.5; + surface.reflectance = 0.5; + surface.occlusion = 1.0; + surface.model = 0u; + return surface; +} + +fn surface_uv(index: u32, uv: vec2) -> vec2 { + let transform = surface_uniform.uv_transforms[index]; + return uv * transform.xy + transform.zw; +} + +fn surface_samples(uv: vec2) -> SurfaceSamples { + var samples: SurfaceSamples; + samples.values[0] = textureSample(surface_texture0, surface_sampler0, surface_uv(0u, uv)); + samples.values[1] = textureSample(surface_texture1, surface_sampler1, surface_uv(1u, uv)); + samples.values[2] = textureSample(surface_texture2, surface_sampler2, surface_uv(2u, uv)); + samples.values[3] = textureSample(surface_texture3, surface_sampler3, surface_uv(3u, uv)); + samples.values[4] = textureSample(surface_texture4, surface_sampler4, surface_uv(4u, uv)); + samples.values[5] = textureSample(surface_texture5, surface_sampler5, surface_uv(5u, uv)); + samples.values[6] = textureSample(surface_texture6, surface_sampler6, surface_uv(6u, uv)); + samples.values[7] = textureSample(surface_texture7, surface_sampler7, surface_uv(7u, uv)); + return samples; +} diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index f5a06b9..be885a7 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -33,6 +33,7 @@ egui_phosphor_icons.workspace = true bevy_ufbx.workspace = true ufbx = "0.9" game.workspace = true +polyanya.workspace = true game_hot.workspace = true hot-lib-reloader = { version = "0.8.2", optional = true } notify = { version = "6.1", optional = true } diff --git a/crates/editor/src/assets/animation.rs b/crates/editor/src/assets/animation.rs index 7e5d38d..d1b236d 100644 --- a/crates/editor/src/assets/animation.rs +++ b/crates/editor/src/assets/animation.rs @@ -210,6 +210,13 @@ fn build_gltf_manifest( }); } + let default_animation_clip_id = record.import_settings.default_animation_clip_id.clone(); + validate_default_animation_clip( + default_animation_clip_id.as_deref(), + &clips, + &mut diagnostics, + ); + let runtime_supported = animation_roots.len() <= 1; if !runtime_supported { let roots = animation_roots @@ -232,6 +239,7 @@ fn build_gltf_manifest( schema_version: ANIMATION_MANIFEST_SCHEMA_VERSION, asset_id: record.id.as_string(), label: record.label.clone(), + default_animation_clip_id, source: AnimationManifestSource { path: record.path.clone(), format, @@ -313,15 +321,22 @@ fn build_fbx_manifest( }) .collect::>(); - let (runtime_supported, diagnostics) = fbx_runtime_support( + let (runtime_supported, mut diagnostics) = fbx_runtime_support( scene.anim_stacks.as_ref().len(), scene.skin_deformers.as_ref().len(), ); + let default_animation_clip_id = record.import_settings.default_animation_clip_id.clone(); + validate_default_animation_clip( + default_animation_clip_id.as_deref(), + &clips, + &mut diagnostics, + ); Ok(AnimationManifest { schema_version: ANIMATION_MANIFEST_SCHEMA_VERSION, asset_id: record.id.as_string(), label: record.label.clone(), + default_animation_clip_id, source: AnimationManifestSource { path: record.path.clone(), format, @@ -335,6 +350,27 @@ fn build_fbx_manifest( }) } +fn validate_default_animation_clip( + default_clip_id: Option<&str>, + clips: &[AnimationClipRecord], + diagnostics: &mut Vec, +) { + let Some(default_clip_id) = default_clip_id.filter(|id| !id.trim().is_empty()) else { + return; + }; + if clips.iter().any(|clip| clip.id == default_clip_id) { + return; + } + diagnostics.push(AnimationImportDiagnostic { + severity: AnimationDiagnosticSeverity::Error, + code: "animation.default_clip_missing".into(), + message: format!( + "configured default animation clip `{default_clip_id}` no longer exists in the imported source" + ), + repair: "Choose an existing Default animation in Model Import Settings, or select Imported rest pose, then reimport.".into(), + }); +} + fn fbx_runtime_support( animation_count: usize, skin_count: usize, @@ -696,7 +732,8 @@ mod tests { let second = build_animation_manifest(&record).unwrap(); assert_eq!(first, second); - assert_eq!(first.schema_version, 2); + assert_eq!(first.schema_version, ANIMATION_MANIFEST_SCHEMA_VERSION); + assert!(first.default_animation_clip_id.is_none()); assert!(first.runtime_supported); assert_eq!(first.source.dependencies.len(), 1); assert_eq!(first.skeletons.len(), 1); @@ -717,6 +754,32 @@ mod tests { fs::remove_dir_all(root).unwrap(); } + #[test] + fn explicit_default_clip_is_stable_and_stale_ids_are_diagnostic() { + let root = + std::env::temp_dir().join(format!("blacksite-animation-default-{}", Uuid::new_v4())); + let path = write_animated_gltf(&root); + let mut record = fixture_record(&path); + record.import_settings.default_animation_clip_id = + Some("animation:clip:0:idle_loop".into()); + + let manifest = build_animation_manifest(&record).unwrap(); + assert_eq!( + manifest.default_animation_clip_id.as_deref(), + Some("animation:clip:0:idle_loop") + ); + assert!(manifest.diagnostics.is_empty()); + + record.import_settings.default_animation_clip_id = Some("animation:clip:99:removed".into()); + let stale = build_animation_manifest(&record).unwrap(); + assert!(stale.diagnostics.iter().any(|diagnostic| { + diagnostic.code == "animation.default_clip_missing" + && diagnostic.severity == AnimationDiagnosticSeverity::Error + })); + + fs::remove_dir_all(root).unwrap(); + } + #[test] fn exact_signature_changes_with_joint_path_or_bind_pose() { let identity = identity_matrix_bytes(); diff --git a/crates/editor/src/assets/asset_db.rs b/crates/editor/src/assets/asset_db.rs index 3f75e7d..63d1afb 100644 --- a/crates/editor/src/assets/asset_db.rs +++ b/crates/editor/src/assets/asset_db.rs @@ -27,8 +27,12 @@ impl Default for AssetId { #[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)] pub enum ModelPlacementMode { + /// Place renderable content through the appropriate normalized renderer. Imports containing + /// skins or animation route to `SkinnedMeshRenderer`; unrigged, non-animated content routes to + /// `StaticMeshRenderer`. #[default] StaticAsset, + /// Instantiate the complete source scene as a generic imported model. SceneInstance, } @@ -61,6 +65,10 @@ pub struct ImportSettings { pub static_mesh_manifest_path: Option, #[serde(default)] pub animation_manifest_path: Option, + /// Stable animation clip sub-asset ID used as this model's edit-mode rest presentation. + /// `None` preserves the imported node pose and never guesses a clip. + #[serde(default)] + pub default_animation_clip_id: Option, } impl Default for ImportSettings { @@ -74,6 +82,7 @@ impl Default for ImportSettings { material_policy: MaterialImportPolicy::default(), static_mesh_manifest_path: None, animation_manifest_path: None, + default_animation_clip_id: None, } } } @@ -336,6 +345,7 @@ mod tests { schema_version: ANIMATION_MANIFEST_SCHEMA_VERSION, asset_id: id.as_string(), label: "Moved Model".into(), + default_animation_clip_id: None, source: AnimationManifestSource { path: old_path.clone(), format: "glb".into(), diff --git a/crates/editor/src/assets/catalog.rs b/crates/editor/src/assets/catalog.rs index f49e961..baf8803 100644 --- a/crates/editor/src/assets/catalog.rs +++ b/crates/editor/src/assets/catalog.rs @@ -524,6 +524,7 @@ fn empty_snapshot_for_asset(asset: &EditorAsset, translation: Vec3) -> EditorEnt primitive: None, brush: None, static_mesh_renderer: None, + skinned_mesh_renderer: None, material: None, material_override: None, rigid_body: None, @@ -542,8 +543,18 @@ fn empty_snapshot_for_asset(asset: &EditorAsset, translation: Vec3) -> EditorEnt post_process_volume: None, team_spawn: None, objective: None, + + navigation_bounds: None, + + navigation_obstacle: None, + + navigation_area: None, + + navigation_link: None, hierarchy_sibling_index: 0, editor_visibility: EditorVisibility::default(), + inspector_order: None, + component_states: None, children: Vec::new(), } } @@ -672,17 +683,33 @@ pub fn spawn_asset_at(world: &mut World, asset: &EditorAsset, translation: Vec3) record.import_settings.placement_mode, ModelPlacementMode::StaticAsset ) { - if let Some(renderer) = static_mesh_renderer_for_asset(world, path) { - let collider = record - .import_settings - .generate_collider - .then(|| static_mesh_collider_for_renderer(&renderer)); - apply_static_mesh_placement_mode( - &mut snapshot, - renderer, - collider, - record.import_settings.hierarchy_mode, - ); + if let Some((renderer, requires_skinned_renderer)) = + static_mesh_renderer_for_asset(world, path) + { + if requires_skinned_renderer { + apply_skinned_mesh_placement_mode_with_materials( + &mut snapshot, + renderer.materials.clone(), + ); + match default_animation_controller_for_record(&record) { + Ok(controller) => snapshot.animation_controller = controller, + Err(error) => warn!( + "Default animation was not applied while placing {}: {error}", + record.path + ), + } + } else { + let collider = record + .import_settings + .generate_collider + .then(|| static_mesh_collider_for_renderer(&renderer)); + apply_static_mesh_placement_mode( + &mut snapshot, + renderer, + collider, + record.import_settings.hierarchy_mode, + ); + } } else { warn!( "Static mesh placement fell back to SceneRoot for {} because no renderer manifest was available", @@ -760,6 +787,11 @@ pub fn spawn_subasset_at( .parts .iter() .find(|part| part_effective_id_for_selection(part) == *sub_asset_id)?; + let part_requires_skinned_renderer = manifest.metadata.animation_count > 0 + || part.skinned + || (manifest.schema_version < 2 + && crate::assets::gltf_skinned_primitive_labels(parent_path) + .contains(&part.mesh_label)); let mesh_ref = shared::EditorAssetRef::new( manifest.asset_id.clone(), @@ -784,6 +816,10 @@ pub fn spawn_subasset_at( id: shared::ComponentInstanceId::new("slot:0"), name: label.clone(), mesh: mesh_ref.clone(), + material_slot_id: shared::ComponentInstanceId::new(format!( + "slot:{}", + part_effective_id_for_selection(part) + )), material, local_transform: part.local_transform, visible: true, @@ -800,6 +836,7 @@ pub fn spawn_subasset_at( primitive: None, brush: None, static_mesh_renderer: Some(shared::StaticMeshRenderer::single(slot)), + skinned_mesh_renderer: None, material: None, material_override: None, rigid_body: None, @@ -818,11 +855,41 @@ pub fn spawn_subasset_at( post_process_volume: None, team_spawn: None, objective: None, + + navigation_bounds: None, + + navigation_obstacle: None, + + navigation_area: None, + + navigation_link: None, hierarchy_sibling_index: 0, editor_visibility: EditorVisibility::default(), + inspector_order: None, + component_states: None, children: Vec::new(), }; snapshot.transform.scale *= record.import_settings.scale; + if part_requires_skinned_renderer { + snapshot.actor_kind = ActorKind::SkinnedMesh; + snapshot.static_mesh_renderer = None; + snapshot.skinned_mesh_renderer = Some(shared::SkinnedMeshRenderer { + materials: crate::assets::static_mesh::renderer_materials_from_manifest( + &manifest, + &record.import_settings, + ), + ..shared::SkinnedMeshRenderer::new(parent_path.clone()) + .with_asset_id(record.id.as_string()) + }); + match default_animation_controller_for_record(&record) { + Ok(controller) => snapshot.animation_controller = controller, + Err(error) => warn!( + "Default animation was not applied while placing {}: {error}", + record.path + ), + } + return Some(spawn_with_history(world, snapshot)); + } if record.import_settings.generate_collider { snapshot.rigid_body = Some(RigidBodyDesc::default()); snapshot.collider = Some(ColliderDesc::static_mesh(vec![mesh_ref])); @@ -840,6 +907,7 @@ pub struct AnimationClipAuthoringData { pub skeleton: shared::EditorAssetRef, pub skeleton_signature: shared::AnimationSkeletonSignature, pub state: shared::AnimationStateDesc, + pub renderer_materials: shared::RendererMaterialSet, } pub fn animation_clip_authoring_data( @@ -905,6 +973,18 @@ pub fn animation_clip_authoring_data( let clip_ref = shared::EditorAssetRef::new(asset_id, clip.id.clone(), clip.label.clone()) .with_source_path(parent_path); let state_id = animation_state_id(&clip.label, clip.source_index); + let renderer_materials = record + .import_settings + .static_mesh_manifest_path + .as_deref() + .and_then(|path| load_static_mesh_manifest(path).ok()) + .map(|render_manifest| { + crate::assets::static_mesh::renderer_materials_from_manifest( + &render_manifest, + &record.import_settings, + ) + }) + .unwrap_or_default(); Ok(AnimationClipAuthoringData { model_asset_id: record.id.as_string(), model_path: parent_path.clone(), @@ -920,6 +1000,7 @@ pub fn animation_clip_authoring_data( speed: 1.0, range: shared::AnimationPlaybackRange::default(), }, + renderer_materials, }) } @@ -957,7 +1038,7 @@ pub fn animation_skeleton_signature_for_ref( pub fn compatible_animation_skeleton_for_model( world: &World, - model: &ModelRef, + model: &shared::SkinnedMeshRenderer, signature: &shared::AnimationSkeletonSignature, ) -> Result { let record = world @@ -1017,6 +1098,82 @@ fn animation_state_id(label: &str, source_index: usize) -> String { } } +fn default_animation_controller_for_record( + record: &crate::asset_db::AssetRecord, +) -> Result, String> { + let Some(manifest_path) = record.import_settings.animation_manifest_path.as_deref() else { + return Ok(None); + }; + let manifest = load_animation_manifest(manifest_path)?; + if manifest.default_animation_clip_id != record.import_settings.default_animation_clip_id { + return Err( + "model import settings and generated animation manifest disagree; reimport the model" + .into(), + ); + } + let Some(default_clip_id) = manifest + .default_animation_clip_id + .as_deref() + .filter(|id| !id.trim().is_empty()) + else { + return Ok(None); + }; + if !manifest.runtime_supported { + return Err(format!( + "configured clip `{default_clip_id}` belongs to a model unsupported by the animation runtime" + )); + } + let clip = manifest + .clips + .iter() + .find(|clip| clip.id == default_clip_id) + .ok_or_else(|| { + format!( + "configured clip `{default_clip_id}` is missing from {manifest_path}; choose an existing clip or Imported rest pose" + ) + })?; + let signature = clip.target_skeleton_signature.as_ref().ok_or_else(|| { + format!( + "configured clip `{}` has no uniquely resolved target skeleton", + clip.label + ) + })?; + let skeleton = manifest + .skeletons + .iter() + .find(|skeleton| &skeleton.signature == signature) + .ok_or_else(|| { + format!( + "configured clip `{}` targets skeleton `{}` but no exact match exists", + clip.label, signature.0 + ) + })?; + let state_id = animation_state_id(&clip.label, clip.source_index); + let source_path = manifest.source.path.clone(); + let asset_id = manifest.asset_id.clone(); + Ok(Some(shared::AnimationControllerDesc { + skeleton: Some( + shared::EditorAssetRef::new( + asset_id.clone(), + skeleton.id.clone(), + skeleton.label.clone(), + ) + .with_source_path(&source_path), + ), + states: vec![shared::AnimationStateDesc { + id: state_id.clone(), + label: clip.label.clone(), + clip: shared::EditorAssetRef::new(asset_id, clip.id.clone(), clip.label.clone()) + .with_source_path(source_path), + looping: true, + speed: 1.0, + range: shared::AnimationPlaybackRange::default(), + }], + default_state: state_id, + ..default() + })) +} + fn spawn_animation_clip_at( world: &mut World, selection: &AssetSelection, @@ -1035,7 +1192,7 @@ fn spawn_animation_clip_at( let default_state = authored.state.id.clone(); let snapshot = EditorEntitySnapshot { actor_id: None, - actor_kind: ActorKind::ImportedModel, + actor_kind: ActorKind::SkinnedMesh, actor_name: None, name: Some(authored.model_label), transform: Transform { @@ -1046,6 +1203,11 @@ fn spawn_animation_clip_at( primitive: None, brush: None, static_mesh_renderer: None, + skinned_mesh_renderer: Some(shared::SkinnedMeshRenderer { + materials: authored.renderer_materials, + ..shared::SkinnedMeshRenderer::new(authored.model_path) + .with_asset_id(authored.model_asset_id) + }), material: None, material_override: None, rigid_body: None, @@ -1061,7 +1223,7 @@ fn spawn_animation_clip_at( audio_source: None, audio_listener: None, player_spawn: false, - model: Some(ModelRef::new(authored.model_path).with_asset_id(authored.model_asset_id)), + model: None, prefab: None, prefab_instance: None, weapon_spawn: None, @@ -1069,8 +1231,18 @@ fn spawn_animation_clip_at( post_process_volume: None, team_spawn: None, objective: None, + + navigation_bounds: None, + + navigation_obstacle: None, + + navigation_area: None, + + navigation_link: None, hierarchy_sibling_index: 0, editor_visibility: EditorVisibility::default(), + inspector_order: None, + component_states: None, children: Vec::new(), }; Some(spawn_with_history(world, snapshot)) @@ -1118,6 +1290,7 @@ fn apply_static_mesh_placement_mode( primitive: None, brush: None, static_mesh_renderer: Some(shared::StaticMeshRenderer::single(entry)), + skinned_mesh_renderer: None, material: None, material_override: None, rigid_body: collider.as_ref().map(|_| RigidBodyDesc::default()), @@ -1136,8 +1309,18 @@ fn apply_static_mesh_placement_mode( post_process_volume: None, team_spawn: None, objective: None, + + navigation_bounds: None, + + navigation_obstacle: None, + + navigation_area: None, + + navigation_link: None, hierarchy_sibling_index: index as i32, editor_visibility: EditorVisibility::default(), + inspector_order: None, + component_states: None, children: Vec::new(), } }) @@ -1146,6 +1329,35 @@ fn apply_static_mesh_placement_mode( } } +#[cfg(test)] +fn apply_skinned_mesh_placement_mode(snapshot: &mut EditorEntitySnapshot) { + apply_skinned_mesh_placement_mode_with_materials( + snapshot, + shared::RendererMaterialSet::default(), + ); +} + +fn apply_skinned_mesh_placement_mode_with_materials( + snapshot: &mut EditorEntitySnapshot, + materials: shared::RendererMaterialSet, +) { + let Some(model) = snapshot.model.take() else { + return; + }; + snapshot.actor_kind = ActorKind::SkinnedMesh; + snapshot.static_mesh_renderer = None; + snapshot.skinned_mesh_renderer = Some(shared::SkinnedMeshRenderer { + asset_id: model.asset_id, + path: model.path, + scene_index: model.scene_index, + materials, + }); + // Triangle-mesh colliders are a static-geometry concern. Animated collision must be authored + // explicitly (capsules, hitboxes, or another gameplay-specific representation). + snapshot.rigid_body = None; + snapshot.collider = None; +} + fn static_mesh_collider_for_renderer(renderer: &shared::StaticMeshRenderer) -> ColliderDesc { ColliderDesc::static_mesh( renderer @@ -1177,14 +1389,26 @@ fn part_effective_material_id_for_selection( fn static_mesh_renderer_for_asset( world: &mut World, path: &str, -) -> Option { +) -> Option<(shared::StaticMeshRenderer, bool)> { let mut manifest_path = None; if let Some(mut registry) = world.get_resource_mut::() { let mut renderer = None; if let Some(record) = find_asset_mut_by_path(&mut registry, path) { match super::refresh_model_artifacts(record) { Ok(manifest) => { - renderer = Some(renderer_from_manifest(&manifest, &record.import_settings)); + let requires_skinned_renderer = manifest.metadata.animation_count > 0 + || manifest.metadata.skin_count > 0 + || manifest.parts.iter().any(|part| part.skinned); + let mut authored_renderer = + renderer_from_manifest(&manifest, &record.import_settings); + if requires_skinned_renderer { + authored_renderer.materials = + crate::assets::static_mesh::renderer_materials_from_manifest( + &manifest, + &record.import_settings, + ); + } + renderer = Some((authored_renderer, requires_skinned_renderer)); registry.index_dirty = true; } Err(error) => { @@ -1207,7 +1431,17 @@ fn static_mesh_renderer_for_asset( let registry = world.get_resource::()?; let record = find_asset_by_path(registry, path)?; let manifest = load_static_mesh_manifest(&manifest_path).ok()?; - Some(renderer_from_manifest(&manifest, &record.import_settings)) + let requires_skinned_renderer = manifest.metadata.animation_count > 0 + || manifest.metadata.skin_count > 0 + || manifest.parts.iter().any(|part| part.skinned); + let mut renderer = renderer_from_manifest(&manifest, &record.import_settings); + if requires_skinned_renderer { + renderer.materials = crate::assets::static_mesh::renderer_materials_from_manifest( + &manifest, + &record.import_settings, + ); + } + Some((renderer, requires_skinned_renderer)) } pub fn import_external_assets(paths: &[PathBuf]) -> Result { @@ -1348,4 +1582,106 @@ mod tests { ); assert!(world.resource::().can_undo()); } + + #[test] + fn skinned_model_placement_replaces_static_and_generic_model_renderers() { + let asset = EditorAsset { + label: "Hero".into(), + path: Some("assets/models/hero.glb".into()), + folder_path: "assets/models".into(), + kind: EditorAssetKind::Model, + }; + let mut snapshot = snapshot_for_asset(&asset, Vec3::ZERO).unwrap(); + snapshot.model.as_mut().unwrap().asset_id = "hero-id".into(); + snapshot.static_mesh_renderer = Some(shared::StaticMeshRenderer::default()); + snapshot.rigid_body = Some(RigidBodyDesc::default()); + snapshot.collider = Some(ColliderDesc::static_cuboid(Vec3::ONE)); + + apply_skinned_mesh_placement_mode(&mut snapshot); + + assert_eq!(snapshot.actor_kind, ActorKind::SkinnedMesh); + assert!(snapshot.model.is_none()); + assert!(snapshot.static_mesh_renderer.is_none()); + assert!(snapshot.rigid_body.is_none()); + assert!(snapshot.collider.is_none()); + assert_eq!( + snapshot.skinned_mesh_renderer, + Some( + shared::SkinnedMeshRenderer::new("assets/models/hero.glb").with_asset_id("hero-id") + ) + ); + } + + #[test] + fn model_default_clip_builds_one_explicit_controller_state() { + let key = Uuid::new_v4(); + let manifest_path = + std::env::temp_dir().join(format!("blacksite-default-animation-{key}.animation.ron")); + let asset_id = key.to_string(); + let signature = shared::AnimationSkeletonSignature::new("rig-signature"); + let clip_id = shared::animation_clip_sub_asset_id(8, "Standing"); + let manifest = shared::AnimationManifest { + schema_version: shared::ANIMATION_MANIFEST_SCHEMA_VERSION, + asset_id: asset_id.clone(), + label: "Robot".into(), + default_animation_clip_id: Some(clip_id.clone()), + source: shared::AnimationManifestSource { + path: "assets/models/robot.glb".into(), + format: "glb".into(), + fingerprint: shared::AnimationSourceFingerprint { + byte_len: 1, + modified_unix_secs: 0, + content_hash: "fixture".into(), + }, + dependencies: Vec::new(), + }, + runtime_supported: true, + skeletons: vec![shared::AnimationSkeletonRecord { + id: shared::animation_skeleton_sub_asset_id(0, "Rig"), + label: "Rig".into(), + source_index: 0, + signature: signature.clone(), + joint_paths: vec!["Root".into()], + }], + clips: vec![shared::AnimationClipRecord { + id: clip_id.clone(), + label: "Standing".into(), + source_index: 8, + duration_seconds: 0.4, + target_skeleton_signature: Some(signature), + events: Vec::new(), + }], + diagnostics: Vec::new(), + }; + std::fs::write( + &manifest_path, + ron::ser::to_string_pretty(&manifest, ron::ser::PrettyConfig::default()).unwrap(), + ) + .unwrap(); + let record = AssetRecord { + id: AssetId(key), + path: "assets/models/robot.glb".into(), + label: "Robot".into(), + kind_tag: "Model".into(), + import_settings: crate::asset_db::ImportSettings { + animation_manifest_path: Some(manifest_path.to_string_lossy().into_owned()), + default_animation_clip_id: Some(clip_id.clone()), + ..Default::default() + }, + dependencies: Vec::new(), + }; + + let controller = default_animation_controller_for_record(&record) + .unwrap() + .expect("explicit default should produce a controller"); + assert_eq!(controller.default_state, "standing"); + assert_eq!(controller.states.len(), 1); + assert_eq!(controller.states[0].clip.sub_asset_id, clip_id); + assert_eq!( + controller.skeleton.unwrap().sub_asset_id, + shared::animation_skeleton_sub_asset_id(0, "Rig") + ); + + std::fs::remove_file(manifest_path).unwrap(); + } } diff --git a/crates/editor/src/assets/materials.rs b/crates/editor/src/assets/materials.rs index fffb3a5..362f95c 100644 --- a/crates/editor/src/assets/materials.rs +++ b/crates/editor/src/assets/materials.rs @@ -2,10 +2,13 @@ use bevy::prelude::*; use bevy_egui::egui; -use shared::{LevelObject, MaterialAsset, MaterialDesc}; +use shared::{ + ComponentInstanceId, LevelObject, MaterialAsset, MaterialDesc, MaterialRef, + RendererMaterialSlot, +}; use crate::assets::{EditorAsset, EditorAssetKind, EditorAssets}; -use crate::history::set_material_group_with_history; +use crate::history::{reflected_component_transaction, set_material_group_with_history}; use crate::ui::inspector::property_row; pub fn load_material_asset(catalog_path: &str) -> Result { @@ -28,18 +31,105 @@ pub fn apply_material_asset_to_selection( .path .clone() .ok_or_else(|| format!("Material has no source path: {}", asset.label))?; - let desc = material_desc_from_asset(&path)?; - let changes = crate::ui::helpers::selected_level_entities(world, selected) - .into_iter() - .filter(|entity| world.get::(*entity).is_some()) - .map(|entity| (entity, desc.clone())) - .collect::>(); - set_material_group_with_history(world, changes); + let record = world + .get_resource::() + .and_then(|registry| crate::asset_db::find_asset_by_path(registry, &path)) + .ok_or_else(|| format!("Material is missing from the asset registry: {path}"))?; + let sub_asset_id = if shared::MaterialInstanceAsset::load_from_path(&path).is_ok() { + "material:instance" + } else { + "material:source" + }; + let material_ref = MaterialRef::new( + shared::EditorAssetRef::new(record.id.as_string(), sub_asset_id, asset.label.clone()) + .with_source_path(path.clone()), + ); + let desc = material_desc_from_asset(&path).ok(); + let entities = crate::ui::helpers::selected_level_entities(world, selected); + let mut renderer_changes = 0usize; + let mut legacy_changes = Vec::new(); + for entity in entities { + if world.get::(entity).is_none() { + continue; + } + if let Some(mut renderer) = world.get::(entity).cloned() { + ensure_static_material_slots(&mut renderer); + for slot in &mut renderer.materials.slots { + slot.material = Some(material_ref.clone()); + } + reflected_component_transaction( + world, + entity, + "Assign Material Slots", + shared::AUTHORING_COMPONENT_STATIC_MESH_RENDERER, + shared::COMPONENT_STATIC_MESH_RENDERER, + move |world, entity| { + world.entity_mut(entity).insert(renderer); + Ok(()) + }, + )?; + renderer_changes += 1; + continue; + } + if let Some(mut renderer) = world.get::(entity).cloned() { + if renderer.materials.slots.is_empty() { + return Err(format!( + "Skinned renderer `{}` has no imported material slots; reimport its model", + renderer.path + )); + } + for slot in &mut renderer.materials.slots { + slot.material = Some(material_ref.clone()); + } + reflected_component_transaction( + world, + entity, + "Assign Material Slots", + shared::AUTHORING_COMPONENT_SKINNED_MESH_RENDERER, + shared::COMPONENT_SKINNED_MESH_RENDERER, + move |world, entity| { + world.entity_mut(entity).insert(renderer); + Ok(()) + }, + )?; + renderer_changes += 1; + continue; + } + if let Some(desc) = desc.as_ref() { + legacy_changes.push((entity, desc.clone())); + } + } + if !legacy_changes.is_empty() { + set_material_group_with_history(world, legacy_changes); + } + if renderer_changes == 0 && desc.is_none() { + return Err("No selected renderable actor accepts this material".into()); + } world.resource_mut::().status = - format!("Applied material {}", asset.label); + format!("Applied shared material {}", asset.label); Ok(()) } +fn ensure_static_material_slots(renderer: &mut shared::StaticMeshRenderer) { + for part in &mut renderer.slots { + if part.material_slot_id.0.trim().is_empty() { + part.material_slot_id = if part.id.0.trim().is_empty() { + ComponentInstanceId::new("slot:legacy") + } else { + ComponentInstanceId::new(format!("slot:{}", part.id.0)) + }; + } + if renderer.materials.slot(&part.material_slot_id).is_none() { + renderer.materials.slots.push(RendererMaterialSlot { + id: part.material_slot_id.clone(), + name: part.name.clone(), + source_material: part.material.clone().map(MaterialRef::new), + material: None, + }); + } + } +} + pub fn material_asset_picker_ui( world: &mut World, ui: &mut egui::Ui, diff --git a/crates/editor/src/assets/operators.rs b/crates/editor/src/assets/operators.rs index 0ff8253..03e0320 100644 --- a/crates/editor/src/assets/operators.rs +++ b/crates/editor/src/assets/operators.rs @@ -119,10 +119,10 @@ pub fn assign_animation_clip_operator( selection: AssetSelection, entity: Entity, ) -> bool { - let availability = if world.get::(entity).is_some() { + let availability = if world.get::(entity).is_some() { OperatorAvailability::Ready } else { - OperatorAvailability::Disabled("Select an authored imported-model actor".to_string()) + OperatorAvailability::Disabled("Select an authored skinned-mesh actor".to_string()) }; let label = format!("Assign Animation Clip {}", selection.display_label()); run_immediate_operator( @@ -133,9 +133,9 @@ pub fn assign_animation_clip_operator( move |world| { let authored = animation_clip_authoring_data(world, &selection)?; let model = world - .get::(entity) + .get::(entity) .cloned() - .ok_or_else(|| "Selected actor has no ModelRef".to_string())?; + .ok_or_else(|| "Selected actor has no SkinnedMeshRenderer".to_string())?; let mut controller = world .get::(entity) .cloned() @@ -197,7 +197,7 @@ mod tests { animation_clip_sub_asset_id, animation_skeleton_sub_asset_id, ActorKind, AnimationClipRecord, AnimationControllerDesc, AnimationManifest, AnimationManifestSource, AnimationSkeletonRecord, AnimationSkeletonSignature, AnimationSourceFingerprint, - AudioSourceDesc, LevelObject, MaterialDesc, ModelRef, PrimitiveShape, + AudioSourceDesc, LevelObject, MaterialDesc, PrimitiveShape, SkinnedMeshRenderer, ANIMATION_MANIFEST_SCHEMA_VERSION, }; @@ -222,6 +222,7 @@ mod tests { schema_version: ANIMATION_MANIFEST_SCHEMA_VERSION, asset_id: asset_id.as_string(), label: "Operator Fixture".into(), + default_animation_clip_id: None, source: AnimationManifestSource { path: model_path.clone(), format: "glb".into(), @@ -301,6 +302,43 @@ mod tests { assert_undo_redo_round_trip(&mut world, 0, 1, authored_count); } + #[test] + fn animation_clip_placement_creates_only_the_skinned_renderer_path() { + let mut world = World::new(); + world.init_resource::(); + world.init_resource::(); + world.init_resource::(); + world.init_resource::(); + let (manifest_path, record, selection) = animation_fixture(); + world.insert_resource(AssetRegistry { + records: vec![record], + index_dirty: false, + }); + let harness = OperatorInvariantHarness::capture(&mut world); + + assert!(place_subasset_operator( + &mut world, + selection, + Vec3::new(1.0, 2.0, 3.0), + )); + + let entity = world + .query_filtered::>() + .single(&world) + .unwrap(); + assert_eq!( + world.get::(entity), + Some(&ActorKind::SkinnedMesh) + ); + assert!(world.get::(entity).is_some()); + assert!(world.get::(entity).is_some()); + assert!(world.get::(entity).is_none()); + assert!(world.get::(entity).is_none()); + harness.assert_committed(&mut world, 1, 1); + assert_undo_redo_round_trip(&mut world, 0, 1, authored_count); + let _ = std::fs::remove_file(manifest_path); + } + #[test] fn audio_clip_assignment_is_one_undoable_operator() { let mut world = World::new(); @@ -363,8 +401,8 @@ mod tests { let entity = world .spawn(( LevelObject, - ActorKind::ImportedModel, - ModelRef::new(model_path), + ActorKind::SkinnedMesh, + SkinnedMeshRenderer::new(model_path), )) .id(); let harness = OperatorInvariantHarness::capture(&mut world); diff --git a/crates/editor/src/assets/static_mesh.rs b/crates/editor/src/assets/static_mesh.rs index db8d374..61aed72 100644 --- a/crates/editor/src/assets/static_mesh.rs +++ b/crates/editor/src/assets/static_mesh.rs @@ -14,9 +14,12 @@ use serde::{Deserialize, Serialize}; use crate::asset_db::{ AssetRecord, ImportSettings, MaterialImportPolicy, ModelHierarchyMode, ModelPlacementMode, }; -use shared::{ComponentInstanceId, EditorAssetRef, StaticMeshRenderer, StaticMeshRendererEntry}; +use shared::{ + ComponentInstanceId, EditorAssetRef, MaterialRef, RendererMaterialSet, RendererMaterialSlot, + StaticMeshRenderer, StaticMeshRendererEntry, +}; -pub const STATIC_MESH_MANIFEST_SCHEMA: u32 = 1; +pub const STATIC_MESH_MANIFEST_SCHEMA: u32 = 3; pub const STATIC_MESH_ARTIFACT_DIR: &str = "assets/meshes/generated"; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -80,6 +83,10 @@ pub struct StaticMeshPart { pub source_node: Option, pub source_mesh: Option, pub source_material: Option, + /// Whether the source primitive is bound to a skin and therefore must never become a static + /// renderer slot. + #[serde(default)] + pub skinned: bool, } pub fn static_mesh_manifest_path(asset_id: &str) -> String { @@ -94,6 +101,17 @@ pub fn material_id_from_label(label: &str) -> String { format!("material:{}", stable_sub_asset_slug(label)) } +fn gltf_draw_id(node_index: Option, mesh_index: usize, primitive_index: usize) -> String { + let node = node_index + .map(|index| index.to_string()) + .unwrap_or_else(|| "unbound".into()); + format!("draw:scene0:node{node}:mesh{mesh_index}:primitive{primitive_index}") +} + +fn fbx_draw_id(node_index: usize, material_index: usize) -> String { + format!("draw:scene0:node{node_index}:material{material_index}") +} + fn stable_sub_asset_slug(label: &str) -> String { let mut slug = String::new(); for ch in label.chars() { @@ -151,38 +169,106 @@ pub fn renderer_from_manifest( MaterialImportPolicy::SourceMaterials ); - StaticMeshRenderer { + let parts: Vec<_> = manifest + .parts + .iter() + .filter(|part| !part.skinned && manifest.metadata.animation_count == 0) + .map(|part| StaticMeshRendererEntry { + id: ComponentInstanceId::new(part_effective_id(part)), + name: part.name.clone(), + mesh: EditorAssetRef::new( + manifest.asset_id.clone(), + part_effective_id(part), + part.name.clone(), + ), + material_slot_id: ComponentInstanceId::new(material_slot_id(part)), + material: use_source_materials + .then(|| { + part_effective_material_id(part).map(|id| { + EditorAssetRef::new( + manifest.asset_id.clone(), + id, + part.material_slot_name.clone(), + ) + }) + }) + .flatten(), + local_transform: part.local_transform, + visible: true, + cast_shadows: true, + receive_shadows: true, + }) + .collect(); + let materials = RendererMaterialSet { slots: manifest .parts .iter() - .map(|part| StaticMeshRendererEntry { - id: ComponentInstanceId::new(part_effective_id(part)), - name: part.name.clone(), - mesh: EditorAssetRef::new( - manifest.asset_id.clone(), - part_effective_id(part), - part.name.clone(), - ), - material: use_source_materials + .filter(|part| !part.skinned && manifest.metadata.animation_count == 0) + .map(|part| RendererMaterialSlot { + id: ComponentInstanceId::new(material_slot_id(part)), + name: part.material_slot_name.clone(), + source_material: use_source_materials .then(|| { part_effective_material_id(part).map(|id| { - EditorAssetRef::new( + MaterialRef::new(EditorAssetRef::new( manifest.asset_id.clone(), id, part.material_slot_name.clone(), - ) + )) }) }) .flatten(), - local_transform: part.local_transform, - visible: true, - cast_shadows: true, - receive_shadows: true, + material: None, }) .collect(), + orphaned_assignments: Vec::new(), + }; + + StaticMeshRenderer { + slots: parts, + materials, } } +/// Builds the shared material slots for a full imported hierarchy. Unlike static renderer +/// construction this intentionally includes skin-bound and rigid animated draw bindings. +pub fn renderer_materials_from_manifest( + manifest: &StaticMeshManifest, + settings: &ImportSettings, +) -> RendererMaterialSet { + let use_source_materials = matches!( + settings.material_policy, + MaterialImportPolicy::SourceMaterials + ); + RendererMaterialSet { + slots: manifest + .parts + .iter() + .map(|part| RendererMaterialSlot { + id: ComponentInstanceId::new(material_slot_id(part)), + name: part.material_slot_name.clone(), + source_material: use_source_materials + .then(|| { + part_effective_material_id(part).map(|id| { + MaterialRef::new(EditorAssetRef::new( + manifest.asset_id.clone(), + id, + part.material_slot_name.clone(), + )) + }) + }) + .flatten(), + material: None, + }) + .collect(), + orphaned_assignments: Vec::new(), + } +} + +fn material_slot_id(part: &StaticMeshPart) -> String { + format!("slot:{}", part_effective_id(part)) +} + fn part_effective_id(part: &StaticMeshPart) -> String { if part.id.trim().is_empty() { part_id_from_label(&part.mesh_label) @@ -255,23 +341,23 @@ fn build_gltf_manifest( .or_else(|| gltf.document.scenes().next()) { for node in scene.nodes() { - collect_gltf_node_parts(node, Mat4::IDENTITY, &mut parts); + collect_gltf_node_parts(node, Mat4::IDENTITY, "", &mut parts); } } else { for mesh in gltf.document.meshes() { - collect_gltf_mesh_parts(None, None, mesh, Mat4::IDENTITY, &mut parts); + collect_gltf_mesh_parts(None, None, None, mesh, Mat4::IDENTITY, false, &mut parts); } } if gltf.document.animations().count() > 0 { warnings.push( - "Animations are recorded as metadata; use Scene Instance placement for playback." + "Animations are recorded as metadata; animated placement uses SkinnedMeshRenderer." .into(), ); } if gltf.document.skins().count() > 0 { warnings.push( - "Skins are recorded as metadata; skinned playback stays on the Scene Instance path." + "Skinned primitives are excluded from StaticMeshRenderer and use SkinnedMeshRenderer." .into(), ); } @@ -306,29 +392,44 @@ fn build_gltf_manifest( fn collect_gltf_node_parts( node: gltf::Node<'_>, parent_transform: Mat4, + parent_path: &str, parts: &mut Vec, ) { let local = Mat4::from_cols_array_2d(&node.transform().matrix()); let world_transform = parent_transform * local; + let segment = node + .name() + .map(str::to_string) + .unwrap_or_else(|| format!("Node{}", node.index())); + let node_path = if parent_path.is_empty() { + segment + } else { + format!("{parent_path}/{segment}") + }; + let skinned = node.skin().is_some(); if let Some(mesh) = node.mesh() { collect_gltf_mesh_parts( node.name().map(str::to_string), Some(node.index()), + Some(node_path.clone()), mesh, world_transform, + skinned, parts, ); } for child in node.children() { - collect_gltf_node_parts(child, world_transform, parts); + collect_gltf_node_parts(child, world_transform, &node_path, parts); } } fn collect_gltf_mesh_parts( node_name: Option, node_index: Option, + node_path: Option, mesh: gltf::Mesh<'_>, transform: Mat4, + skinned: bool, parts: &mut Vec, ) { let mesh_index = mesh.index(); @@ -359,9 +460,11 @@ fn collect_gltf_mesh_parts( .clone() .or_else(|| mesh_name.clone()) .unwrap_or_else(|| format!("Mesh {mesh_index}")); - let source_node = node_index.map(|index| format!("Node{index}")); + let source_node = node_path + .clone() + .or_else(|| node_index.map(|index| format!("Node{index}"))); parts.push(StaticMeshPart { - id: part_id_from_label(&mesh_label), + id: gltf_draw_id(node_index, mesh_index, primitive_index), name: format!("{name} / Primitive {primitive_index}"), material_id: material_label .as_ref() @@ -373,6 +476,7 @@ fn collect_gltf_mesh_parts( source_node, source_mesh: Some(format!("Mesh{mesh_index}")), source_material: Some(material_name), + skinned, }); } } @@ -405,6 +509,7 @@ fn build_fbx_manifest( if mesh.num_vertices == 0 || mesh.faces.as_ref().is_empty() { continue; } + let skinned = !mesh.skin_deformers.as_ref().is_empty(); let mut groups: Vec<(usize, Vec)> = group_faces_by_material(mesh).into_iter().collect(); @@ -428,7 +533,7 @@ fn build_fbx_manifest( }; let mesh_label = FbxAssetLabel::Mesh(node_index * 1000 + material_index).to_string(); parts.push(StaticMeshPart { - id: part_id_from_label(&mesh_label), + id: fbx_draw_id(node_index, material_index), name: format!("{node_name} / Material {material_index}"), material_id: material_label .as_ref() @@ -440,19 +545,20 @@ fn build_fbx_manifest( source_node: Some(format!("Node{node_index}")), source_mesh: Some(format!("Mesh{node_index}")), source_material: Some(material_name), + skinned, }); } } if !scene.anim_stacks.as_ref().is_empty() { warnings.push( - "Animations are recorded as metadata; use Scene Instance placement for playback." + "Animations are recorded as metadata; animated placement uses SkinnedMeshRenderer." .into(), ); } if !scene.skin_deformers.as_ref().is_empty() { warnings.push( - "Skins are recorded as metadata; skinned playback stays on the Scene Instance path." + "Skinned primitives are excluded from StaticMeshRenderer and use SkinnedMeshRenderer." .into(), ); } @@ -554,6 +660,7 @@ fn resolve_dependency(source_path: &str, uri: &str) -> String { #[cfg(test)] mod tests { use super::*; + use crate::asset_db::AssetId; fn test_manifest() -> StaticMeshManifest { StaticMeshManifest { @@ -597,6 +704,7 @@ mod tests { source_node: Some("Node0".into()), source_mesh: Some("Mesh0".into()), source_material: Some("Wood".into()), + skinned: false, }], warnings: Vec::new(), } @@ -645,4 +753,48 @@ mod tests { assert!(renderer.slots[0].material.is_none()); } + + #[test] + fn renderer_from_manifest_excludes_skinned_primitives() { + let mut manifest = test_manifest(); + manifest.parts[0].skinned = true; + + let renderer = renderer_from_manifest(&manifest, &ImportSettings::default()); + + assert!(renderer.slots.is_empty()); + } + + #[test] + fn renderer_from_manifest_excludes_node_animated_geometry() { + let mut manifest = test_manifest(); + manifest.metadata.animation_count = 1; + + let renderer = renderer_from_manifest(&manifest, &ImportSettings::default()); + + assert!(renderer.slots.is_empty()); + } + + #[test] + fn committed_rigged_fixture_never_builds_static_renderer_slots() { + let path = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../../assets/models/robot_expressive.glb") + .to_string_lossy() + .into_owned(); + let record = AssetRecord { + id: AssetId::new(), + path, + label: "Robot Expressive".into(), + kind_tag: "Model".into(), + import_settings: ImportSettings::default(), + dependencies: Vec::new(), + }; + + let manifest = build_static_mesh_manifest(&record).unwrap(); + let renderer = renderer_from_manifest(&manifest, &record.import_settings); + + assert!(manifest.metadata.skin_count > 0); + assert!(manifest.metadata.animation_count > 0); + assert!(manifest.parts.iter().any(|part| part.skinned)); + assert!(renderer.slots.is_empty()); + } } diff --git a/crates/editor/src/assets/thumbnails/studio.rs b/crates/editor/src/assets/thumbnails/studio.rs index 6792577..5c7f020 100644 --- a/crates/editor/src/assets/thumbnails/studio.rs +++ b/crates/editor/src/assets/thumbnails/studio.rs @@ -21,6 +21,10 @@ use crate::infra::EditorOnly; pub(crate) const THUMB_SIZE: u32 = 128; pub(crate) const THUMBNAIL_LAYER: usize = 31; +const THUMBNAIL_VERTICAL_FOV: f32 = 40.0_f32.to_radians(); +const THUMBNAIL_HALF_VERTICAL_FOV: f32 = THUMBNAIL_VERTICAL_FOV * 0.5; +const SCENE_FRAME_PADDING: f32 = 1.35; +const MATERIAL_SPHERE_FRAME_PADDING: f32 = 1.28; const MESH_WARMUP_FRAMES: u8 = 12; const POST_ATTACH_FRAMES: u8 = 8; const RENDER_FRAMES: u8 = 3; @@ -61,6 +65,25 @@ struct ActiveModelThumbnail { framed: bool, frames_remaining: u8, wait_frames: u32, + framing: ThumbnailFraming, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum ThumbnailFraming { + SceneBounds, + MaterialSphere, +} + +impl ThumbnailFraming { + fn for_source(source: &ThumbnailJobSource) -> Self { + match source { + ThumbnailJobSource::SourceMaterial { .. } + | ThumbnailJobSource::MaterialAsset { .. } => Self::MaterialSphere, + ThumbnailJobSource::Model { .. } | ThumbnailJobSource::MeshSubAsset { .. } => { + Self::SceneBounds + } + } + } } pub struct ThumbnailStudioPlugin; @@ -143,7 +166,7 @@ fn setup_thumbnail_studio(mut commands: Commands, mut images: ResMut, mesh_assets: &Assets, camera_transforms: &mut Query<&mut Transform, With>, + framing: ThumbnailFraming, ) -> bool { let mut entities = Vec::new(); collect_descendants(root, children, &mut entities); @@ -633,8 +661,7 @@ fn frame_thumbnail_scene( } let center = (min + max) * 0.5; - let radius = (max - min).length().max(0.25) * 0.5; - let distance = radius / (20.0_f32.to_radians()).tan() * 1.35; + let distance = thumbnail_camera_distance(min, max, framing); let eye = center + Vec3::new(1.0, 0.75, 1.0).normalize() * distance; if let Ok(mut transform) = camera_transforms.single_mut() { @@ -644,6 +671,20 @@ fn frame_thumbnail_scene( true } +fn thumbnail_camera_distance(min: Vec3, max: Vec3, framing: ThumbnailFraming) -> f32 { + let bounds_size = max - min; + let (radius, padding) = match framing { + ThumbnailFraming::SceneBounds => { + (bounds_size.length().max(0.25) * 0.5, SCENE_FRAME_PADDING) + } + ThumbnailFraming::MaterialSphere => ( + bounds_size.max_element().max(0.5) * 0.5, + MATERIAL_SPHERE_FRAME_PADDING, + ), + }; + radius / THUMBNAIL_HALF_VERTICAL_FOV.tan() * padding +} + fn collect_descendants(entity: Entity, children: &Query<&Children>, out: &mut Vec) { out.push(entity); let Ok(child_list) = children.get(entity) else { @@ -673,3 +714,24 @@ fn aabb_corners(aabb: &Aabb3d) -> [Vec3; 8] { pub fn model_file_exists(catalog_path: &str) -> bool { Path::new(catalog_path).is_file() } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn material_sphere_framing_fills_more_of_the_thumbnail_with_padding() { + let sphere_radius = 0.65; + let min = Vec3::splat(-sphere_radius); + let max = Vec3::splat(sphere_radius); + + let scene_distance = thumbnail_camera_distance(min, max, ThumbnailFraming::SceneBounds); + let material_distance = + thumbnail_camera_distance(min, max, ThumbnailFraming::MaterialSphere); + let projected_radius_fraction = + sphere_radius / (material_distance * THUMBNAIL_HALF_VERTICAL_FOV.tan()); + + assert!(material_distance < scene_distance * 0.6); + assert!((0.75..0.82).contains(&projected_radius_fraction)); + } +} diff --git a/crates/editor/src/history/commands.rs b/crates/editor/src/history/commands.rs index 392859f..464ba21 100644 --- a/crates/editor/src/history/commands.rs +++ b/crates/editor/src/history/commands.rs @@ -3,9 +3,10 @@ use std::path::PathBuf; use bevy::prelude::*; use shared::{ ActorId, ActorKind, ActorName, AnimationControllerDesc, AudioListenerDesc, AudioSourceDesc, - BrushDesc, ColliderDesc, EditorVisibility, InspectorOrder, LightDesc, MaterialDesc, - MaterialOverride, ModelRef, ObjectiveMarker, PhysicsBody, PostProcessVolumeDesc, - PrefabInstance, PrefabRef, Primitive, RigidBodyDesc, SceneComposition, StaticMeshRenderer, + AuthoringComponentStates, BrushDesc, ColliderDesc, EditorVisibility, InspectorOrder, LightDesc, + MaterialDesc, MaterialOverride, ModelRef, NavigationArea, NavigationBounds, NavigationLink, + NavigationObstacle, ObjectiveMarker, PhysicsBody, PostProcessVolumeDesc, PrefabInstance, + PrefabRef, Primitive, RigidBodyDesc, SceneComposition, SkinnedMeshRenderer, StaticMeshRenderer, TeamSpawn, TriggerVolume, WeaponSpawn, }; #[derive(Debug, Clone)] @@ -18,6 +19,7 @@ pub struct EditorEntitySnapshot { pub primitive: Option, pub brush: Option, pub static_mesh_renderer: Option, + pub skinned_mesh_renderer: Option, pub material: Option, pub material_override: Option, pub rigid_body: Option, @@ -36,11 +38,52 @@ pub struct EditorEntitySnapshot { pub post_process_volume: Option, pub team_spawn: Option, pub objective: Option, + pub navigation_bounds: Option, + pub navigation_obstacle: Option, + pub navigation_area: Option, + pub navigation_link: Option, pub hierarchy_sibling_index: i32, pub editor_visibility: EditorVisibility, + pub inspector_order: Option, + pub component_states: Option, pub children: Vec, } +/// One reflected component value used by registry-driven editor transactions. +/// `component_id` is stable; `type_path` is retained only to resolve Bevy's +/// reflection adapter for this build. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ReflectedComponentValue { + pub component_id: String, + pub type_path: String, + pub ron: String, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ComponentDelta { + pub component_id: String, + pub type_path: String, + pub before: Option, + pub after: Option, +} + +/// An atomic add/remove/reset/paste operation. Requirements, dependent removals, +/// and the derived ActorKind hint can be represented as additional deltas. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ComponentTransaction { + pub entity: Entity, + pub label: &'static str, + pub deltas: Vec, +} + +#[derive(Debug, Clone, Default, PartialEq)] +pub struct NavigationComponentState { + pub bounds: Option, + pub obstacle: Option, + pub area: Option, + pub link: Option, +} + #[derive(Debug, Clone)] pub struct SiblingChange { pub entity: Entity, @@ -174,6 +217,11 @@ pub enum EditorCommand { old: Option, new: PostProcessVolumeDesc, }, + SetNavigation { + entity: Entity, + old: NavigationComponentState, + new: NavigationComponentState, + }, SetTransformGroup { entities: Vec, olds: Vec, @@ -239,6 +287,7 @@ pub enum EditorCommand { entity: Entity, snapshot: EditorEntitySnapshot, }, + ComponentTransaction(ComponentTransaction), } impl EditorCommand { @@ -266,6 +315,7 @@ impl EditorCommand { EditorCommand::ApplyBrushCsg { .. } => "Brush CSG", EditorCommand::SetStaticMeshRenderer { .. } => "Set Static Mesh Renderer", EditorCommand::SetPostProcessVolume { .. } => "Set Post Process Volume", + EditorCommand::SetNavigation { .. } => "Set Navigation", EditorCommand::SetTransformGroup { .. } => "Move Selection", EditorCommand::Reparent { .. } => "Reparent", EditorCommand::SetActorKind { .. } => "Set Actor Kind", @@ -283,6 +333,7 @@ impl EditorCommand { } => "Unpack Prefab Layer", EditorCommand::AddComponent { .. } => "Add Component", EditorCommand::RemoveComponent { .. } => "Remove Component", + EditorCommand::ComponentTransaction(transaction) => transaction.label, } } } diff --git a/crates/editor/src/history/mod.rs b/crates/editor/src/history/mod.rs index 43a0901..cc3d391 100644 --- a/crates/editor/src/history/mod.rs +++ b/crates/editor/src/history/mod.rs @@ -1,14 +1,20 @@ use std::collections::HashSet; +use bevy::ecs::reflect::ReflectComponent; use bevy::prelude::*; +use bevy::reflect::serde::{TypedReflectDeserializer, TypedReflectSerializer}; +use bevy::reflect::std_traits::ReflectDefault; use bevy::world_serialization::WorldInstance; +use serde::de::DeserializeSeed; use shared::{ infer_actor_kind, ActorId, ActorKind, ActorName, AnimationControllerDesc, AudioListenerDesc, - AudioSourceDesc, BrushDesc, ColliderDesc, EditorVisibility, HierarchySiblingIndex, - HydratedPrefabMember, HydratedPrefabReady, InspectorOrder, LevelObject, LightDesc, - MaterialDesc, MaterialOverride, ModelRef, ObjectiveMarker, PhysicsBody, PlayerSpawn, + AudioSourceDesc, AuthoringComponentStates, BrushDesc, ColliderDesc, EditorVisibility, + HierarchySiblingIndex, HydratedPrefabMember, HydratedPrefabReady, InspectorOrder, LevelObject, + LightDesc, MaterialDesc, MaterialOverride, ModelRef, NavigationArea, NavigationBounds, + NavigationLink, NavigationObstacle, ObjectiveMarker, PhysicsBody, PlayerSpawn, PostProcessVolumeDesc, PrefabHydrationBlocked, PrefabInstance, PrefabRef, Primitive, - RigidBodyDesc, SceneComposition, StaticMeshRenderer, TeamSpawn, TriggerVolume, WeaponSpawn, + RigidBodyDesc, SceneComposition, SkinnedMeshRenderer, StaticMeshRenderer, TeamSpawn, + TriggerVolume, WeaponSpawn, }; use crate::scene_io::{SceneIo, SceneIoRequest}; @@ -22,7 +28,10 @@ use crate::ui::selection_ops::is_mutable_level_object; use crate::ui::{egui_captures_keyboard_from_world, UiState}; mod commands; -pub use commands::{EditorCommand, EditorEntitySnapshot, PrefabEditState, SiblingChange}; +pub use commands::{ + ComponentDelta, ComponentTransaction, EditorCommand, EditorEntitySnapshot, + NavigationComponentState, PrefabEditState, ReflectedComponentValue, SiblingChange, +}; #[derive(Resource, Debug, Default)] pub struct EditorHistory { @@ -138,6 +147,7 @@ pub fn snapshot_entity(world: &World, entity: Entity) -> Option().cloned(), brush: entity_ref.get::().cloned(), static_mesh_renderer: entity_ref.get::().cloned(), + skinned_mesh_renderer: entity_ref.get::().cloned(), material: entity_ref.get::().cloned(), material_override: entity_ref.get::().cloned(), rigid_body: entity_ref.get::().cloned(), @@ -156,6 +166,10 @@ pub fn snapshot_entity(world: &World, entity: Entity) -> Option().cloned(), team_spawn: entity_ref.get::().cloned(), objective: entity_ref.get::().cloned(), + navigation_bounds: entity_ref.get::().cloned(), + navigation_obstacle: entity_ref.get::().cloned(), + navigation_area: entity_ref.get::().cloned(), + navigation_link: entity_ref.get::().cloned(), hierarchy_sibling_index: entity_ref .get::() .map(|index| index.0) @@ -164,6 +178,8 @@ pub fn snapshot_entity(world: &World, entity: Entity) -> Option() .copied() .unwrap_or_default(), + inspector_order: entity_ref.get::().cloned(), + component_states: entity_ref.get::().cloned(), children, }) } @@ -204,6 +220,9 @@ fn spawn_snapshot_with_parent( if let Some(renderer) = &snapshot.static_mesh_renderer { entity_mut.insert(renderer.clone()); } + if let Some(renderer) = &snapshot.skinned_mesh_renderer { + entity_mut.insert(renderer.clone()); + } if let Some(material) = &snapshot.material { entity_mut.insert(material.clone()); } @@ -258,8 +277,26 @@ fn spawn_snapshot_with_parent( if let Some(objective) = &snapshot.objective { entity_mut.insert(objective.clone()); } + if let Some(bounds) = &snapshot.navigation_bounds { + entity_mut.insert(bounds.clone()); + } + if let Some(obstacle) = &snapshot.navigation_obstacle { + entity_mut.insert(obstacle.clone()); + } + if let Some(area) = &snapshot.navigation_area { + entity_mut.insert(area.clone()); + } + if let Some(link) = &snapshot.navigation_link { + entity_mut.insert(link.clone()); + } entity_mut.insert(HierarchySiblingIndex(snapshot.hierarchy_sibling_index)); entity_mut.insert(snapshot.editor_visibility); + if let Some(order) = &snapshot.inspector_order { + entity_mut.insert(order.clone()); + } + if let Some(states) = &snapshot.component_states { + entity_mut.insert(states.clone()); + } if let Some(parent) = parent { entity_mut.insert(ChildOf(parent)); } @@ -561,6 +598,31 @@ pub fn set_post_process_volume_with_history( ); } +pub fn navigation_component_state(world: &World, entity: Entity) -> NavigationComponentState { + NavigationComponentState { + bounds: world.get::(entity).cloned(), + obstacle: world.get::(entity).cloned(), + area: world.get::(entity).cloned(), + link: world.get::(entity).cloned(), + } +} + +pub fn set_navigation_with_history( + world: &mut World, + entity: Entity, + new: NavigationComponentState, +) { + if !is_mutable_level_object(world, entity) { + return; + } + let old = navigation_component_state(world, entity); + if old == new { + return; + } + apply_navigation_state(world, entity, &new); + push_history(world, EditorCommand::SetNavigation { entity, old, new }); +} + pub fn set_physics_with_history(world: &mut World, entity: Entity, new: PhysicsBody) { if !is_mutable_level_object(world, entity) { return; @@ -818,6 +880,7 @@ pub fn group_selection_with_history(world: &mut World, entities: &[Entity]) { primitive: None, brush: None, static_mesh_renderer: None, + skinned_mesh_renderer: None, material: None, material_override: None, rigid_body: None, @@ -836,8 +899,18 @@ pub fn group_selection_with_history(world: &mut World, entities: &[Entity]) { post_process_volume: None, team_spawn: None, objective: None, + + navigation_bounds: None, + + navigation_obstacle: None, + + navigation_area: None, + + navigation_link: None, hierarchy_sibling_index: next_sibling_index(world, None), editor_visibility: EditorVisibility::default(), + inspector_order: None, + component_states: None, children: Vec::new(), }; let group = spawn_snapshot(world, &snapshot); @@ -868,6 +941,7 @@ pub fn create_empty_child_with_history(world: &mut World, parent: Entity) { primitive: None, brush: None, static_mesh_renderer: None, + skinned_mesh_renderer: None, material: None, material_override: None, rigid_body: None, @@ -886,8 +960,18 @@ pub fn create_empty_child_with_history(world: &mut World, parent: Entity) { post_process_volume: None, team_spawn: None, objective: None, + + navigation_bounds: None, + + navigation_obstacle: None, + + navigation_area: None, + + navigation_link: None, hierarchy_sibling_index: next_sibling_index(world, Some(parent)), editor_visibility: EditorVisibility::default(), + inspector_order: None, + component_states: None, children: Vec::new(), }; let entity = spawn_snapshot_with_parent(world, &snapshot, Some(parent)); @@ -1235,6 +1319,215 @@ pub fn apply_actor_kind(world: &mut World, entity: Entity, kind: ActorKind) { } } +pub const ACTOR_KIND_COMPONENT_ID: &str = "core.actor_kind"; +pub const ACTOR_KIND_TYPE_PATH: &str = "shared::components::ActorKind"; + +/// Captures any reflected component without adding a typed history branch. +pub fn capture_reflected_component( + world: &World, + entity: Entity, + component_id: &str, + type_path: &str, +) -> Result, String> { + let entity_ref = world + .get_entity(entity) + .map_err(|_| format!("entity {entity:?} no longer exists"))?; + let app_registry = world + .get_resource::() + .ok_or_else(|| "AppTypeRegistry is unavailable".to_string())?; + let type_registry = app_registry.read(); + let registration = type_registry + .get_with_type_path(type_path) + .ok_or_else(|| format!("component type `{type_path}` is not registered"))?; + let reflect_component = registration + .data::() + .ok_or_else(|| format!("component `{type_path}` has no ReflectComponent adapter"))?; + let Some(value) = reflect_component.reflect(entity_ref) else { + return Ok(None); + }; + let ron = ron::ser::to_string(&TypedReflectSerializer::new(value, &type_registry)) + .map_err(|error| format!("could not serialize `{type_path}`: {error}"))?; + Ok(Some(ReflectedComponentValue { + component_id: component_id.to_string(), + type_path: type_path.to_string(), + ron, + })) +} + +/// Applies or removes one reflected component value. +pub fn apply_reflected_component( + world: &mut World, + entity: Entity, + type_path: &str, + value: Option<&ReflectedComponentValue>, +) -> Result<(), String> { + if world.get_entity(entity).is_err() { + return Err(format!("entity {entity:?} no longer exists")); + } + world.resource_scope(|world, app_registry: Mut| { + let type_registry = app_registry.read(); + let registration = type_registry + .get_with_type_path(type_path) + .ok_or_else(|| format!("component type `{type_path}` is not registered"))?; + let reflect_component = registration + .data::() + .cloned() + .ok_or_else(|| format!("component `{type_path}` has no ReflectComponent adapter"))?; + match value { + Some(value) => { + if value.type_path != type_path { + return Err(format!( + "clipboard type `{}` does not match `{type_path}`", + value.type_path + )); + } + let mut deserializer = ron::de::Deserializer::from_str(&value.ron) + .map_err(|error| error.to_string())?; + let reflected = TypedReflectDeserializer::new(registration, &type_registry) + .deserialize(&mut deserializer) + .map_err(|error| error.to_string())?; + let exists = reflect_component.reflect(world.entity(entity)).is_some(); + if exists { + reflect_component.apply(world.entity_mut(entity), reflected.as_ref()); + } else { + reflect_component.insert( + &mut world.entity_mut(entity), + reflected.as_ref(), + &type_registry, + ); + } + } + None => reflect_component.remove(&mut world.entity_mut(entity)), + } + Ok(()) + }) +} + +/// Inserts the reflected `Default` for a registered authoring component. +pub fn apply_reflected_default( + world: &mut World, + entity: Entity, + type_path: &str, +) -> Result<(), String> { + world.resource_scope(|world, app_registry: Mut| { + let type_registry = app_registry.read(); + let registration = type_registry + .get_with_type_path(type_path) + .ok_or_else(|| format!("component type `{type_path}` is not registered"))?; + let reflect_component = registration + .data::() + .cloned() + .ok_or_else(|| format!("component `{type_path}` has no ReflectComponent adapter"))?; + let default = registration + .data::() + .ok_or_else(|| format!("component `{type_path}` has no reflected Default"))? + .default(); + let exists = reflect_component.reflect(world.entity(entity)).is_some(); + if exists { + reflect_component.apply(world.entity_mut(entity), default.as_ref()); + } else { + reflect_component.insert( + &mut world.entity_mut(entity), + default.as_ref(), + &type_registry, + ); + } + Ok(()) + }) +} + +pub fn sync_actor_kind_hint(world: &mut World, entity: Entity) { + let kind = world + .get_entity(entity) + .ok() + .and_then(infer_actor_kind) + .unwrap_or(ActorKind::Empty); + if let Ok(mut entity_mut) = world.get_entity_mut(entity) { + entity_mut.insert(kind); + } +} + +/// Executes one atomic reflected component edit and records all changed values, +/// including the derived ActorKind presentation hint, as a single history item. +pub fn reflected_component_transaction( + world: &mut World, + entity: Entity, + label: &'static str, + component_id: &str, + type_path: &str, + edit: impl FnOnce(&mut World, Entity) -> Result<(), String>, +) -> Result<(), String> { + if !is_mutable_level_object(world, entity) { + return Err("actor is not editable".to_string()); + } + let tracked = [ + (component_id, type_path), + (ACTOR_KIND_COMPONENT_ID, ACTOR_KIND_TYPE_PATH), + ]; + let before = tracked + .iter() + .map(|(id, path)| capture_reflected_component(world, entity, id, path)) + .collect::, _>>()?; + if let Err(error) = edit(world, entity) { + for ((_, path), value) in tracked.iter().zip(before.iter()) { + let _ = apply_reflected_component(world, entity, path, value.as_ref()); + } + return Err(error); + } + sync_actor_kind_hint(world, entity); + let after = tracked + .iter() + .map(|(id, path)| capture_reflected_component(world, entity, id, path)) + .collect::, _>>()?; + let deltas = tracked + .iter() + .zip(before) + .zip(after) + .filter_map(|(((id, path), before), after)| { + (before != after).then(|| ComponentDelta { + component_id: (*id).to_string(), + type_path: (*path).to_string(), + before, + after, + }) + }) + .collect::>(); + if !deltas.is_empty() { + push_history( + world, + EditorCommand::ComponentTransaction(ComponentTransaction { + entity, + label, + deltas, + }), + ); + } + Ok(()) +} + +fn apply_component_transaction( + world: &mut World, + transaction: &ComponentTransaction, + use_after: bool, +) { + for delta in &transaction.deltas { + let value = if use_after { + delta.after.as_ref() + } else { + delta.before.as_ref() + }; + if let Err(error) = + apply_reflected_component(world, transaction.entity, &delta.type_path, value) + { + world.resource_mut::().status = format!( + "{} partially failed for {}: {error}", + transaction.label, delta.component_id + ); + break; + } + } +} + pub fn set_scene_composition_with_history(world: &mut World, new: SceneComposition) { let old = world.resource::().clone(); if old == new { @@ -1372,6 +1665,9 @@ fn undo_command(world: &mut World, command: &mut EditorCommand) { EditorCommand::SetPostProcessVolume { entity, old, .. } => { apply_post_process_volume(world, *entity, old); } + EditorCommand::SetNavigation { entity, old, .. } => { + apply_navigation_state(world, *entity, old); + } EditorCommand::SetPhysics { entity, old, .. } => { apply_physics(world, *entity, old); } @@ -1485,6 +1781,9 @@ fn undo_command(world: &mut World, command: &mut EditorCommand) { EditorCommand::RemoveComponent { entity, snapshot } => { apply_authored_components(world, *entity, snapshot); } + EditorCommand::ComponentTransaction(transaction) => { + apply_component_transaction(world, transaction, false); + } } } @@ -1573,6 +1872,9 @@ fn redo_command(world: &mut World, command: &mut EditorCommand) { entity_mut.insert(new.clone()); } } + EditorCommand::SetNavigation { entity, new, .. } => { + apply_navigation_state(world, *entity, new); + } EditorCommand::SetPhysics { entity, new, .. } => { if let Ok(mut entity_mut) = world.get_entity_mut(*entity) { entity_mut.insert(new.clone()); @@ -1706,6 +2008,9 @@ fn redo_command(world: &mut World, command: &mut EditorCommand) { EditorCommand::RemoveComponent { entity, snapshot } => { remove_authored_components(world, *entity, snapshot); } + EditorCommand::ComponentTransaction(transaction) => { + apply_component_transaction(world, transaction, true); + } } } @@ -1862,6 +2167,45 @@ fn apply_post_process_volume( } } +fn apply_navigation_state(world: &mut World, entity: Entity, state: &NavigationComponentState) { + let has_navigation = state.bounds.is_some() + || state.obstacle.is_some() + || state.area.is_some() + || state.link.is_some(); + if let Ok(mut entity_mut) = world.get_entity_mut(entity) { + entity_mut + .remove::() + .remove::() + .remove::() + .remove::(); + if let Some(value) = &state.bounds { + entity_mut.insert(value.clone()); + } + if let Some(value) = &state.obstacle { + entity_mut.insert(value.clone()); + } + if let Some(value) = &state.area { + entity_mut.insert(value.clone()); + } + if let Some(value) = &state.link { + entity_mut.insert(value.clone()); + } + if has_navigation { + entity_mut.insert(ActorKind::Navigation); + } + } + if !has_navigation { + let fallback = world + .get_entity(entity) + .ok() + .and_then(infer_actor_kind) + .unwrap_or(ActorKind::Empty); + if let Ok(mut entity_mut) = world.get_entity_mut(entity) { + entity_mut.insert(fallback); + } + } +} + fn apply_physics(world: &mut World, entity: Entity, physics: &Option) { if let Ok(mut entity_mut) = world.get_entity_mut(entity) { match physics { @@ -1986,6 +2330,9 @@ fn apply_authored_components(world: &mut World, entity: Entity, snapshot: &Edito if let Some(v) = &snapshot.static_mesh_renderer { entity_mut.insert(v.clone()); } + if let Some(v) = &snapshot.skinned_mesh_renderer { + entity_mut.insert(v.clone()); + } if let Some(v) = &snapshot.material { entity_mut.insert(v.clone()); } @@ -2040,6 +2387,24 @@ fn apply_authored_components(world: &mut World, entity: Entity, snapshot: &Edito if let Some(v) = &snapshot.objective { entity_mut.insert(v.clone()); } + if let Some(v) = &snapshot.navigation_bounds { + entity_mut.insert(v.clone()); + } + if let Some(v) = &snapshot.navigation_obstacle { + entity_mut.insert(v.clone()); + } + if let Some(v) = &snapshot.navigation_area { + entity_mut.insert(v.clone()); + } + if let Some(v) = &snapshot.navigation_link { + entity_mut.insert(v.clone()); + } + if let Some(v) = &snapshot.inspector_order { + entity_mut.insert(v.clone()); + } + if let Some(v) = &snapshot.component_states { + entity_mut.insert(v.clone()); + } } } @@ -2061,6 +2426,9 @@ fn remove_authored_components(world: &mut World, entity: Entity, snapshot: &Edit if snapshot.static_mesh_renderer.is_some() { entity_mut.remove::(); } + if snapshot.skinned_mesh_renderer.is_some() { + entity_mut.remove::(); + } if snapshot.material.is_some() { entity_mut.remove::(); } @@ -2115,6 +2483,24 @@ fn remove_authored_components(world: &mut World, entity: Entity, snapshot: &Edit if snapshot.objective.is_some() { entity_mut.remove::(); } + if snapshot.navigation_bounds.is_some() { + entity_mut.remove::(); + } + if snapshot.navigation_obstacle.is_some() { + entity_mut.remove::(); + } + if snapshot.navigation_area.is_some() { + entity_mut.remove::(); + } + if snapshot.navigation_link.is_some() { + entity_mut.remove::(); + } + if snapshot.inspector_order.is_some() { + entity_mut.remove::(); + } + if snapshot.component_states.is_some() { + entity_mut.remove::(); + } } if removed_dedicated_audio_kind { let fallback = world @@ -2383,6 +2769,88 @@ mod tests { use crate::operators::test_harness::{assert_undo_redo_round_trip, OperatorInvariantHarness}; use crate::operators::{ActiveOperator, OperatorPhase, OperatorStatus}; + #[derive(Component, Reflect, Default, Debug, Clone, PartialEq)] + #[reflect(Component, Default)] + struct StaticExtensionFixture { + value: u32, + } + + #[test] + fn reflected_extension_component_transaction_round_trips_atomically() { + let mut app = App::new(); + app.register_type::() + .register_type::(); + let world = app.world_mut(); + world.init_resource::(); + world.init_resource::(); + let entity = world + .spawn((LevelObject, ActorKind::Light, Transform::default())) + .id(); + let type_path = std::any::type_name::(); + + reflected_component_transaction( + world, + entity, + "Add Fixture", + "game.static_extension_fixture", + type_path, + |world, entity| { + world + .entity_mut(entity) + .insert(StaticExtensionFixture { value: 42 }); + Ok(()) + }, + ) + .unwrap(); + + assert_eq!( + world.get::(entity).unwrap().value, + 42 + ); + assert_eq!(world.get::(entity), Some(&ActorKind::Empty)); + assert_eq!(world.resource::().undo_depth(), 1); + + apply_command_undo(world); + assert!(world.get::(entity).is_none()); + assert_eq!(world.get::(entity), Some(&ActorKind::Light)); + + apply_command_redo(world); + assert_eq!( + world.get::(entity).unwrap().value, + 42 + ); + assert_eq!(world.get::(entity), Some(&ActorKind::Empty)); + } + + #[test] + fn snapshots_preserve_component_order_and_independent_active_state() { + let mut world = World::new(); + let mut order = InspectorOrder::default(); + order.component_ids = vec![shared::AUTHORING_COMPONENT_LIGHT.to_string()]; + let mut states = AuthoringComponentStates::default(); + states.set_component_active(shared::COMPONENT_LIGHT_DESC, false); + let entity = world + .spawn(( + LevelObject, + ActorKind::Light, + Transform::default(), + LightDesc::default(), + order.clone(), + states.clone(), + )) + .id(); + + let snapshot = snapshot_entity(&world, entity).unwrap(); + world.entity_mut(entity).despawn(); + let restored = spawn_snapshot(&mut world, &snapshot); + + assert_eq!(world.get::(restored), Some(&order)); + assert_eq!( + world.get::(restored), + Some(&states) + ); + } + fn animation_controller(state_id: &str, crossfade_seconds: f32) -> AnimationControllerDesc { AnimationControllerDesc { skeleton: Some( @@ -2420,7 +2888,7 @@ mod tests { let entity = world .spawn(( LevelObject, - ActorKind::ImportedModel, + ActorKind::SkinnedMesh, Transform::IDENTITY, old.clone(), )) @@ -2439,6 +2907,36 @@ mod tests { assert_eq!(world.get::(entity), Some(&new)); } + #[test] + fn navigation_component_history_round_trips_and_preserves_kind() { + let mut world = World::new(); + world.init_resource::(); + world.init_resource::(); + let entity = world + .spawn((LevelObject, ActorKind::Empty, Transform::IDENTITY)) + .id(); + let bounds = NavigationBounds::default(); + + set_navigation_with_history( + &mut world, + entity, + NavigationComponentState { + bounds: Some(bounds.clone()), + ..Default::default() + }, + ); + assert_eq!(world.get::(entity), Some(&bounds)); + assert_eq!(world.get::(entity), Some(&ActorKind::Navigation)); + + apply_command_undo(&mut world); + assert!(world.get::(entity).is_none()); + assert_eq!(world.get::(entity), Some(&ActorKind::Empty)); + + apply_command_redo(&mut world); + assert_eq!(world.get::(entity), Some(&bounds)); + assert_eq!(world.get::(entity), Some(&ActorKind::Navigation)); + } + #[test] fn undoing_a_new_animation_controller_removes_the_component() { let mut world = World::new(); @@ -2446,7 +2944,7 @@ mod tests { world.init_resource::(); let controller = animation_controller("idle", 0.2); let entity = world - .spawn((LevelObject, ActorKind::ImportedModel, Transform::IDENTITY)) + .spawn((LevelObject, ActorKind::SkinnedMesh, Transform::IDENTITY)) .id(); set_animation_controller_with_history(&mut world, entity, controller.clone()); @@ -2467,9 +2965,10 @@ mod tests { let source = world .spawn(( LevelObject, - ActorKind::ImportedModel, + ActorKind::SkinnedMesh, Name::new("Animated Robot"), Transform::from_xyz(1.0, 2.0, 3.0), + SkinnedMeshRenderer::new("assets/models/robot.glb").with_asset_id("robot-asset"), controller.clone(), )) .id(); @@ -2486,6 +2985,10 @@ mod tests { world.get::(respawned).map(Name::as_str), Some("Animated Robot") ); + assert_eq!( + world.get::(respawned), + Some(&SkinnedMeshRenderer::new("assets/models/robot.glb").with_asset_id("robot-asset")) + ); } #[test] diff --git a/crates/editor/src/play/session.rs b/crates/editor/src/play/session.rs index 1b44049..40f0447 100644 --- a/crates/editor/src/play/session.rs +++ b/crates/editor/src/play/session.rs @@ -6,7 +6,10 @@ use bevy::window::WindowRef; use game::player::PlayerCamera; use protocol::PlayerInputIntent; use settings::SimTuning; -use shared::{inspector_component_active, InspectorOrder, PlayerSpawn, COMPONENT_PLAYER_SPAWN}; +use shared::{ + authoring_component_active, AuthoringComponentStates, InspectorOrder, PlayerSpawn, + COMPONENT_PLAYER_SPAWN, +}; use sim::{Crouching, Grounded, JumpState, Player, PlayerVelocity}; use crate::render_target::ViewportRenderTarget; @@ -66,10 +69,16 @@ fn bootstrap_player_on_play(world: &mut World) { .unwrap_or_else(|| SimTuning::from_physics(&settings::PhysicsSettings::default())); let spawn = world - .query_filtered::<(&Transform, Option<&InspectorOrder>), With>() + .query_filtered::<( + &Transform, + Option<&AuthoringComponentStates>, + Option<&InspectorOrder>, + ), With>() .iter(world) - .filter(|(_, order)| inspector_component_active(*order, COMPONENT_PLAYER_SPAWN)) - .map(|(transform, _)| transform) + .filter(|(_, states, legacy_order)| { + authoring_component_active(*states, *legacy_order, COMPONENT_PLAYER_SPAWN) + }) + .map(|(transform, _, _)| transform) .next() .copied() .unwrap_or_else(default_player_spawn); diff --git a/crates/editor/src/project/settings_ui.rs b/crates/editor/src/project/settings_ui.rs index 2c02b3b..8977b55 100644 --- a/crates/editor/src/project/settings_ui.rs +++ b/crates/editor/src/project/settings_ui.rs @@ -377,7 +377,11 @@ fn apply_project_settings_changes( settings: Res, tuning: ResMut, scene_suns: Query< - (&shared::LightDesc, Option<&shared::InspectorOrder>), + ( + &shared::LightDesc, + Option<&shared::AuthoringComponentStates>, + Option<&shared::InspectorOrder>, + ), With, >, project_suns: Query<(&mut DirectionalLight, &mut Visibility), With>, diff --git a/crates/editor/src/scene/scene_io.rs b/crates/editor/src/scene/scene_io.rs index bbf60af..4714f98 100644 --- a/crates/editor/src/scene/scene_io.rs +++ b/crates/editor/src/scene/scene_io.rs @@ -6,17 +6,19 @@ use bevy::ecs::system::SystemState; use bevy::prelude::*; use bevy::window::PrimaryWindow; use bevy::world_serialization::serde::WorldDeserializer; -use bevy::world_serialization::{DynamicWorld, DynamicWorldBuilder, WorldInstance}; +use bevy::world_serialization::{DynamicWorld, DynamicWorldBuilder, WorldFilter, WorldInstance}; use scene::{document::SceneDocument, strip_schema_version, validate_level_text}; use serde::de::DeserializeSeed; use shared::{ infer_actor_kind, validate_actor, ActorId, ActorKind, ActorName, ActorValidationError, - AnimationControllerDesc, AudioListenerDesc, AudioSourceDesc, BrushDesc, ColliderDesc, - EditorVisibility, HierarchySiblingIndex, HydratedPrefabMember, HydratedPrefabReady, - InspectorOrder, LevelObject, LightDesc, MaterialDesc, MaterialOverride, ModelRef, - ObjectiveMarker, PhysicsBody, PlayerSpawn, PostProcessVolumeDesc, PrefabHydrationBlocked, - PrefabInstance, PrefabRef, Primitive, RigidBodyDesc, SceneComposition, StaticMeshRenderer, - TeamSpawn, TriggerVolume, WeaponSpawn, + AuthoringComponentStates, EditorVisibility, HierarchySiblingIndex, HydratedPrefabMember, + HydratedPrefabReady, InspectorOrder, LevelObject, LightDesc, PrefabHydrationBlocked, + PrefabInstance, PrefabRef, SceneComposition, +}; + +#[cfg(test)] +use shared::{ + AnimationControllerDesc, AudioSourceDesc, BrushDesc, ColliderDesc, SkinnedMeshRenderer, }; use crate::assets::{import_external_assets, EditorAssets, IMPORTABLE_ASSET_EXTENSIONS}; @@ -1006,6 +1008,27 @@ fn format_actor_validation(err: ActorValidationError) -> String { ActorValidationError::ImportedModelHasStaticMeshRenderer => { "Save failed: ImportedModel actor cannot have StaticMeshRenderer".into() } + ActorValidationError::SkinnedMeshMissingRenderer => { + "Save failed: SkinnedMesh actor requires SkinnedMeshRenderer".into() + } + ActorValidationError::SkinnedMeshInvalidRenderer => { + "Save failed: SkinnedMeshRenderer requires a model source path".into() + } + ActorValidationError::SkinnedMeshHasPrimitive => { + "Save failed: SkinnedMesh actor cannot have Primitive".into() + } + ActorValidationError::SkinnedMeshHasStaticMeshRenderer => { + "Save failed: SkinnedMesh actor cannot have StaticMeshRenderer".into() + } + ActorValidationError::SkinnedMeshHasModelRef => { + "Save failed: SkinnedMesh actor cannot have ModelRef".into() + } + ActorValidationError::SkinnedMeshRendererActorKindMismatch => { + "Save failed: SkinnedMeshRenderer requires ActorKind::SkinnedMesh".into() + } + ActorValidationError::ConflictingGeometrySources => { + "Save failed: actor has more than one primary geometry source component".into() + } ActorValidationError::LightMissingLightDesc => { "Save failed: Light actor requires LightDesc".into() } @@ -1048,8 +1071,9 @@ fn format_actor_validation(err: ActorValidationError) -> String { ActorValidationError::AudioListenerInvalidEarGap => { "Save failed: audio listener ear gap must be positive and finite".into() } - ActorValidationError::AnimationControllerMissingModelRef => { - "Save failed: animation controller requires ModelRef on the same actor".into() + ActorValidationError::AnimationControllerMissingSkinnedMeshRenderer => { + "Save failed: animation controller requires SkinnedMeshRenderer on the same actor" + .into() } ActorValidationError::AnimationControllerMissingSkeleton => { "Save failed: animation controller requires an assigned skeleton".into() @@ -1093,6 +1117,9 @@ fn format_actor_validation(err: ActorValidationError) -> String { ActorValidationError::PostProcessVolumeInvalidOverrideScalar => { "Save failed: post-process volume override contains invalid scalar".into() } + ActorValidationError::InvalidNavigation(message) => { + format!("Save failed: invalid navigation authoring: {message}") + } } } @@ -1179,39 +1206,26 @@ fn serialize_entities_inner_with_resources( let result = (|| { let ron = { let registry = world.resource::().read(); + let mut component_filter = WorldFilter::deny_all() + .allow::() + .allow::() + .allow::() + .allow::() + .allow::() + .allow::() + .allow::() + .allow::(); + let component_registry = world + .get_resource::() + .cloned() + .unwrap_or_default(); + for descriptor in &component_registry.descriptors { + if let Some(registration) = registry.get_with_type_path(descriptor.type_name) { + component_filter = component_filter.allow_by_id(registration.type_id()); + } + } let builder = DynamicWorldBuilder::from_world(world, ®istry) - .deny_all() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() + .with_component_filter(component_filter) .extract_entities(entities.into_iter()); let scene = if include_scene_resources { builder @@ -1781,7 +1795,14 @@ fn finalize_scene_load(world: &mut World) { let mut state: SystemState<( Res, - Query<(&LightDesc, Option<&InspectorOrder>), With>, + Query< + ( + &LightDesc, + Option<&AuthoringComponentStates>, + Option<&InspectorOrder>, + ), + With, + >, Query<(&mut DirectionalLight, &mut Visibility), With>, )> = SystemState::new(world); @@ -1801,10 +1822,8 @@ fn backfill_missing_actor_kinds(world: &mut World) { let Ok(entity_ref) = world.get_entity(entity) else { continue; }; - if entity_ref.get::().is_some() { - continue; - } - if let Some(kind) = infer_actor_kind(entity_ref) { + let current = entity_ref.get::().copied(); + if let Some(kind) = infer_actor_kind(entity_ref).filter(|kind| current != Some(*kind)) { world.entity_mut(entity).insert(kind); } } @@ -1856,6 +1875,12 @@ mod tests { struct NoAssetLoads; + #[derive(Component, Reflect, Default)] + #[reflect(Component, Default)] + struct RegisteredSceneExtension { + value: u32, + } + impl LoadFromPath for NoAssetLoads { fn load_from_path_erased( &mut self, @@ -1866,6 +1891,43 @@ mod tests { } } + #[test] + fn registry_registered_extension_is_included_in_scene_persistence() { + let mut app = App::new(); + app.register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::(); + let mut component_registry = + crate::ui::component_registry::EditorComponentRegistry::default(); + let mut descriptor = component_registry.descriptors[0].clone(); + descriptor.id = "game.registered_scene_extension"; + descriptor.type_name = std::any::type_name::(); + descriptor.display_name = "Registered Scene Extension"; + descriptor.recommended = &[]; + descriptor.conflicts_with = &[]; + component_registry.register(descriptor).unwrap(); + app.insert_resource(component_registry); + app.insert_resource(SceneComposition::default()); + let entity = app + .world_mut() + .spawn(( + Name::new("Extension"), + Transform::default(), + LevelObject, + ActorKind::Empty, + RegisteredSceneExtension { value: 73 }, + )) + .id(); + + let text = serialize_entities_inner(app.world_mut(), vec![entity]).unwrap(); + + assert!(text.contains(std::any::type_name::())); + assert!(text.contains("value: 73") || text.contains("value:73")); + } + #[test] fn restored_recovery_remains_available_until_save_or_discard() { let authored = PathBuf::from("assets/levels/authored.scn.ron"); @@ -2152,8 +2214,8 @@ mod tests { Transform::IDENTITY, LevelObject, ActorId::new("animated-model"), - ActorKind::ImportedModel, - ModelRef::new(source_path), + ActorKind::SkinnedMesh, + SkinnedMeshRenderer::new(source_path), controller.clone(), AnimationPlayer::default(), AnimationGraphHandle::default(), @@ -2171,6 +2233,8 @@ mod tests { let decoded: AnimationControllerDesc = ron::from_str(&component.ron).unwrap(); assert_eq!(decoded, controller); + assert!(text.contains("shared::animation::SkinnedMeshRenderer")); + assert!(!text.contains("shared::components::ModelRef")); assert!(!text.contains("AnimationPlayer")); assert!(!text.contains("AnimationGraphHandle")); assert!(!text.contains("AnimationTransitions")); diff --git a/crates/editor/src/ui/actor_inspector/mod.rs b/crates/editor/src/ui/actor_inspector/mod.rs index a1cd786..c9b1e86 100644 --- a/crates/editor/src/ui/actor_inspector/mod.rs +++ b/crates/editor/src/ui/actor_inspector/mod.rs @@ -5,7 +5,7 @@ mod transform; use bevy::prelude::*; use bevy_egui::egui; use egui_phosphor_icons::icons; -use shared::{ActorId, ActorKind, ActorName, HydratedPrefabMember, LevelObject}; +use shared::{infer_actor_kind, ActorId, ActorKind, ActorName, HydratedPrefabMember, LevelObject}; use crate::ext::extensibility::ActorInspectorSectionRegistry; use crate::ui::inspector; @@ -29,9 +29,8 @@ pub fn draw_actor_inspector(world: &mut World, ui: &mut egui::Ui, entity: Entity return; } - let kind = entity_ref - .get::() - .copied() + let kind = infer_actor_kind(entity_ref) + .or_else(|| entity_ref.get::().copied()) .unwrap_or(ActorKind::Empty); egui::Frame::new() @@ -111,6 +110,7 @@ pub fn draw_actor_inspector(world: &mut World, ui: &mut egui::Ui, entity: Entity match kind { ActorKind::StaticMesh + | ActorKind::SkinnedMesh | ActorKind::Brush | ActorKind::ImportedModel | ActorKind::Light @@ -123,7 +123,8 @@ pub fn draw_actor_inspector(world: &mut World, ui: &mut egui::Ui, entity: Entity | ActorKind::TeamSpawn | ActorKind::Objective | ActorKind::AudioSource - | ActorKind::AudioListener => { + | ActorKind::AudioListener + | ActorKind::Navigation => { inspector::authoring_inspector_ui(world, ui, entity); } } @@ -139,7 +140,10 @@ pub fn draw_actor_inspector(world: &mut World, ui: &mut egui::Ui, entity: Entity fn actor_icon(kind: ActorKind) -> egui_phosphor_icons::Icon { match kind { - ActorKind::StaticMesh | ActorKind::Brush | ActorKind::ImportedModel => icons::CUBE, + ActorKind::StaticMesh + | ActorKind::SkinnedMesh + | ActorKind::Brush + | ActorKind::ImportedModel => icons::CUBE, ActorKind::Light => icons::LIGHTBULB, ActorKind::PrefabAnchor => icons::PACKAGE, ActorKind::PlayerSpawn => icons::PERSON_SIMPLE_RUN, @@ -149,6 +153,7 @@ fn actor_icon(kind: ActorKind) -> egui_phosphor_icons::Icon { ActorKind::TeamSpawn | ActorKind::Objective => icons::FLAG, ActorKind::AudioSource => icons::SPEAKER_HIGH, ActorKind::AudioListener => icons::HEADPHONES, + ActorKind::Navigation => icons::PATH, ActorKind::Empty => icons::CIRCLE, } } diff --git a/crates/editor/src/ui/animation_inspector.rs b/crates/editor/src/ui/animation_inspector.rs index bbe7bf9..46db655 100644 --- a/crates/editor/src/ui/animation_inspector.rs +++ b/crates/editor/src/ui/animation_inspector.rs @@ -7,7 +7,7 @@ use bevy_egui::egui; use egui_phosphor_icons::icons; use shared::{ AnimationClipRecord, AnimationControllerDesc, AnimationManifest, AnimationSkeletonRecord, - AnimationStateDesc, EditorAssetRef, ModelRef, COMPONENT_ANIMATION_CONTROLLER_DESC, + AnimationStateDesc, EditorAssetRef, SkinnedMeshRenderer, COMPONENT_ANIMATION_CONTROLLER_DESC, }; use crate::assets::animation::load_animation_manifest; @@ -656,8 +656,8 @@ fn manifest_summary(ui: &mut egui::Ui, manifest: &Result Result { let model = world - .get::(entity) - .ok_or_else(|| "actor has no ModelRef".to_string())?; + .get::(entity) + .ok_or_else(|| "actor has no SkinnedMeshRenderer".to_string())?; let registry = world .get_resource::() .ok_or_else(|| "asset registry is unavailable".to_string())?; @@ -878,6 +878,7 @@ mod tests { schema_version: 1, asset_id: "model-id".into(), label: "Robot".into(), + default_animation_clip_id: None, source: AnimationManifestSource { path: "assets/models/robot.glb".into(), format: "glb".into(), diff --git a/crates/editor/src/ui/asset_browser/panel.rs b/crates/editor/src/ui/asset_browser/panel.rs index eef2988..0d7fabb 100644 --- a/crates/editor/src/ui/asset_browser/panel.rs +++ b/crates/editor/src/ui/asset_browser/panel.rs @@ -10,9 +10,10 @@ use bevy_egui::egui; use bevy_inspector_egui::bevy_inspector::hierarchy::SelectedEntities; use egui_phosphor_icons::{icons, Icon}; use shared::{ - ColorDesc, EditorAssetRef, MaterialAsset, MaterialDesc, MaterialParameter, - MaterialParameterValue, MaterialShaderKind, MaterialTextureBinding, ShaderPropertyDesc, - ShaderPropertyType, ShaderSchemaAsset, + ColorDesc, EditorAssetRef, MaterialAlphaMode, MaterialAsset, MaterialDesc, + MaterialInstanceAsset, MaterialParameter, MaterialParameterValue, MaterialRef, + MaterialShaderKind, MaterialTextureBinding, ShaderPropertyDesc, ShaderPropertyType, + ShaderSchemaAsset, }; use crate::assets::{ @@ -23,11 +24,12 @@ use crate::scene_io::{SceneIo, SceneIoRequest}; use super::state::{ AssetBrowserUiState, AssetBrowserView, AssetDeleteRequest, AssetKindFilter, AssetSort, - ImportSettingsDraft, MaterialAssetDraft, + ImportSettingsDraft, MaterialAssetDraft, MaterialInstanceAssetDraft, }; use crate::asset_db::{ - find_asset_by_path, find_asset_mut_by_path, save_registry, update_import_settings, - ImportSettings, MaterialImportPolicy, ModelHierarchyMode, ModelPlacementMode, + ensure_asset_record, find_asset_by_path, find_asset_mut_by_path, save_registry, + update_import_settings, ImportSettings, MaterialImportPolicy, ModelHierarchyMode, + ModelPlacementMode, }; use crate::assets::animation::load_animation_manifest; use crate::assets::operators::{ @@ -83,7 +85,29 @@ struct EmbeddedAsset { thumbnail_key: String, mesh_label: Option, material_label: Option, - requires_scene_instance: bool, + requires_skinned_hierarchy: bool, +} + +#[derive(Clone, Debug)] +struct MaterialTextureCandidate { + label: String, + detail: String, + path: String, + reference: EditorAssetRef, + selection: AssetSelection, +} + +#[derive(Clone, Copy)] +enum TextureSlotSelection<'a> { + Path(Option<&'a str>), + Reference(Option<&'a EditorAssetRef>), +} + +#[derive(Default)] +struct TextureSlotResponse { + selected: Option, + clear: bool, + accepted_drop: bool, } fn fit_width(ui: &egui::Ui, min: f32, max: f32) -> f32 { @@ -352,12 +376,18 @@ fn embedded_assets_for_asset(world: &World, asset: &EditorAsset) -> Vec 0 + || part.skinned + || legacy_skinned_primitives.contains(&part.mesh_label); embedded.push(EmbeddedAsset { selection: AssetSelection::SubAsset { parent_path: parent_path.clone(), @@ -368,8 +398,8 @@ fn embedded_assets_for_asset(world: &World, asset: &EditorAsset) -> Vec Vec Vec Vec Vec Vec { if selected_embedded .as_ref() - .is_some_and(|embedded| embedded.requires_scene_instance) + .is_some_and(|embedded| embedded.requires_skinned_hierarchy) { - ui.small( - egui::RichText::new("Place the model or an animation clip").color(TEXT_DIM), - ); + if ui.button("Place Skinned Model").clicked() { + if let Some(selection) = selection.as_ref() { + place_subasset_operator(world, selection.clone(), Vec3::ZERO); + } + } } else if ui.button("Place At Origin").clicked() { if let Some(selection) = selection.as_ref() { place_subasset_operator(world, selection.clone(), Vec3::ZERO); @@ -834,7 +866,7 @@ fn selection_footer(world: &mut World, ui: &mut egui::Ui, selected_entities: &Se .as_slice() .iter() .copied() - .find(|entity| world.get::(*entity).is_some()); + .find(|entity| world.get::(*entity).is_some()); let action = if animated_actor.is_some() { "Assign To Selected Actor" } else { @@ -1257,7 +1289,7 @@ fn prefetch_embedded_thumbnails( parent_path.clone(), mesh_label, embedded_asset.material_label.clone(), - embedded_asset.requires_scene_instance, + embedded_asset.requires_skinned_hierarchy, &mut studio, ); } @@ -1317,9 +1349,9 @@ fn draw_embedded_asset_cell( let thumb_size = thumbnail_size.clamp(44.0, 64.0); let cell_size = egui::vec2(104.0, thumb_size + 50.0); let (rect, response) = ui.allocate_exact_size(cell_size, egui::Sense::click_and_drag()); - let response = if embedded.requires_scene_instance { + let response = if embedded.requires_skinned_hierarchy { response.on_hover_text( - "Skinned primitives require their model hierarchy. Place the model or an animation clip instead.", + "Skinned primitives place through the dedicated renderer so their joint hierarchy remains intact.", ) } else { response @@ -1400,10 +1432,7 @@ fn draw_embedded_asset_cell( .resource_mut::() .select(embedded.selection.clone()); } - if response.drag_started() - && embedded.kind != AssetSubAssetKind::Skeleton - && !embedded.requires_scene_instance - { + if response.drag_started() && embedded.kind != AssetSubAssetKind::Skeleton { world .resource_mut::() .start_drag(embedded.selection.clone()); @@ -1872,7 +1901,11 @@ fn top_level_asset_details_panel( if matches!(asset.kind, EditorAssetKind::Material) { ui.separator(); ui.label(panel_heading("Material")); - material_asset_editor(world, ui, asset, path); + if MaterialInstanceAsset::load_from_path(path).is_ok() { + material_instance_asset_editor(world, ui, asset, path); + } else { + material_asset_editor(world, ui, asset, path); + } } if matches!(asset.kind, EditorAssetKind::AudioClip) { detail_row(ui, "Format", audio_format_label(path)); @@ -1970,13 +2003,10 @@ fn subasset_details_panel( ui.separator(); match embedded.kind { AssetSubAssetKind::Mesh => { - if embedded.requires_scene_instance { - ui.small( - egui::RichText::new( - "This skinned primitive needs its source hierarchy. Place the model or an animation clip instead.", - ) - .color(TEXT_DIM), - ); + if embedded.requires_skinned_hierarchy { + if ui.button("Place Skinned Model At Origin").clicked() { + place_subasset_operator(world, selection.clone(), Vec3::ZERO); + } } else if ui.button("Place At Origin").clicked() { place_subasset_operator(world, selection.clone(), Vec3::ZERO); } @@ -2009,7 +2039,7 @@ fn subasset_details_panel( .as_slice() .iter() .copied() - .find(|entity| world.get::(*entity).is_some()); + .find(|entity| world.get::(*entity).is_some()); let action = if animated_actor.is_some() { "Assign To Selected Actor" } else { @@ -2090,6 +2120,18 @@ fn import_settings_editor( path: &str, current: &ImportSettings, ) { + let animation_clip_options = current + .animation_manifest_path + .as_deref() + .and_then(|manifest_path| load_animation_manifest(manifest_path).ok()) + .map(|manifest| { + manifest + .clips + .into_iter() + .map(|clip| (clip.id, clip.label)) + .collect::>() + }) + .unwrap_or_default(); { let mut state = world.resource_mut::(); let reset = state @@ -2116,14 +2158,14 @@ fn import_settings_editor( ui.checkbox(&mut draft.settings.lod0_only, "LOD0 only"); egui::ComboBox::from_id_salt("model_placement_mode") .selected_text(match draft.settings.placement_mode { - ModelPlacementMode::StaticAsset => "Static Asset", + ModelPlacementMode::StaticAsset => "Renderable Asset (Auto)", ModelPlacementMode::SceneInstance => "Scene Instance", }) .show_ui(ui, |ui| { ui.selectable_value( &mut draft.settings.placement_mode, ModelPlacementMode::StaticAsset, - "Static Asset", + "Renderable Asset (Auto)", ); ui.selectable_value( &mut draft.settings.placement_mode, @@ -2165,6 +2207,42 @@ fn import_settings_editor( "Authoring Override", ); }); + if !animation_clip_options.is_empty() { + let selected_default = draft + .settings + .default_animation_clip_id + .as_deref() + .and_then(|id| { + animation_clip_options + .iter() + .find(|(clip_id, _)| clip_id == id) + .map(|(_, label)| label.as_str()) + }) + .unwrap_or_else(|| { + if draft.settings.default_animation_clip_id.is_some() { + "Missing clip" + } else { + "Imported rest pose" + } + }); + egui::ComboBox::from_id_salt("model_default_animation_clip") + .selected_text(selected_default) + .show_ui(ui, |ui| { + ui.selectable_value( + &mut draft.settings.default_animation_clip_id, + None, + "Imported rest pose", + ); + for (clip_id, label) in &animation_clip_options { + ui.selectable_value( + &mut draft.settings.default_animation_clip_id, + Some(clip_id.clone()), + label, + ); + } + }); + ui.small("Default animation is sampled and paused in edit mode; no clip is guessed."); + } ui.horizontal(|ui| { if ui.button("Apply").clicked() { apply = Some(draft.settings.clone()); @@ -2217,9 +2295,20 @@ fn apply_import_settings( fn material_asset_editor(world: &mut World, ui: &mut egui::Ui, asset: &EditorAsset, path: &str) { ensure_material_draft(world, path, &asset.label); let shader_schemas = shader_schema_assets(world); - let texture_assets = texture_asset_refs(world); + let texture_assets = material_texture_candidates(world); + let texture_drop_candidate = world + .resource::() + .dragging_selection() + .and_then(|selection| { + texture_assets + .iter() + .find(|candidate| candidate.selection == *selection) + }) + .cloned(); let mut apply = false; let mut revert = false; + let mut create_instance = false; + let mut accepted_texture_drop = false; { let mut state = world.resource_mut::(); let Some(draft) = state.material_draft.as_mut() else { @@ -2229,7 +2318,43 @@ fn material_asset_editor(world: &mut World, ui: &mut egui::Ui, asset: &EditorAss ui.colored_label(egui::Color32::YELLOW, error); } compact_text_edit(ui, "Label", &mut draft.asset.label); - material_shader_interface(ui, &mut draft.asset, &shader_schemas, &texture_assets); + material_shader_interface( + ui, + &mut draft.asset, + &shader_schemas, + &texture_assets, + texture_drop_candidate.as_ref(), + &mut accepted_texture_drop, + ); + ui.separator(); + ui.label(panel_heading("Render State")); + ui.horizontal(|ui| { + ui.add_sized([92.0, 20.0], egui::Label::new("Alpha mode")); + egui::ComboBox::from_id_salt("material_alpha_mode") + .selected_text(match draft.asset.render_state.alpha_mode { + MaterialAlphaMode::Opaque => "Opaque", + MaterialAlphaMode::Cutout => "Cutout", + }) + .show_ui(ui, |ui| { + ui.selectable_value( + &mut draft.asset.render_state.alpha_mode, + MaterialAlphaMode::Opaque, + "Opaque", + ); + ui.selectable_value( + &mut draft.asset.render_state.alpha_mode, + MaterialAlphaMode::Cutout, + "Cutout", + ); + }); + }); + if draft.asset.render_state.alpha_mode == MaterialAlphaMode::Cutout { + ui.add( + egui::Slider::new(&mut draft.asset.render_state.alpha_cutoff, 0.0..=1.0) + .text("Alpha cutoff"), + ); + } + ui.checkbox(&mut draft.asset.render_state.double_sided, "Double sided"); ui.separator(); ui.label(panel_heading("Standard Surface")); color_desc_edit(ui, "Base color", &mut draft.asset.material.base_color); @@ -2240,25 +2365,37 @@ fn material_asset_editor(world: &mut World, ui: &mut egui::Ui, asset: &EditorAss egui::Slider::new(&mut draft.asset.material.emissive_intensity, 0.0..=50_000.0) .text("Emissive nits"), ); - optional_path_edit( + accepted_texture_drop |= material_texture_path_ui( ui, "Base texture", + "standard_base_color", &mut draft.asset.material.base_color_texture, + &texture_assets, + texture_drop_candidate.as_ref(), ); - optional_path_edit( + accepted_texture_drop |= material_texture_path_ui( ui, "Emissive texture", + "standard_emissive", &mut draft.asset.material.emissive_texture, + &texture_assets, + texture_drop_candidate.as_ref(), ); - optional_path_edit( + accepted_texture_drop |= material_texture_path_ui( ui, "Normal map", + "standard_normal_map", &mut draft.asset.material.normal_map_texture, + &texture_assets, + texture_drop_candidate.as_ref(), ); - optional_path_edit( + accepted_texture_drop |= material_texture_path_ui( ui, "Metal/rough", + "standard_metallic_roughness", &mut draft.asset.material.metallic_roughness_texture, + &texture_assets, + texture_drop_candidate.as_ref(), ); ui.horizontal(|ui| { if ui.button("Apply").clicked() { @@ -2267,6 +2404,9 @@ fn material_asset_editor(world: &mut World, ui: &mut egui::Ui, asset: &EditorAss if ui.button("Revert").clicked() { revert = true; } + if ui.button("Create Instance").clicked() { + create_instance = true; + } }); } @@ -2277,6 +2417,12 @@ fn material_asset_editor(world: &mut World, ui: &mut egui::Ui, asset: &EditorAss if apply { save_material_draft(world); } + if create_instance { + create_material_instance_from_base(world, path, &asset.label); + } + if accepted_texture_drop { + world.resource_mut::().clear_drag(); + } } fn ensure_material_draft(world: &mut World, path: &str, fallback_label: &str) { @@ -2297,8 +2443,11 @@ fn ensure_material_draft(world: &mut World, path: &str, fallback_label: &str) { Err(error) => MaterialAssetDraft { path: path.to_string(), asset: MaterialAsset { + schema_version: shared::MATERIAL_ASSET_SCHEMA_VERSION, label: fallback_label.to_string(), shader: None, + shader_ref: None, + render_state: shared::MaterialRenderState::default(), material: MaterialDesc::default(), }, error: Some(error), @@ -2347,43 +2496,642 @@ fn save_material_draft(world: &mut World) { } } -fn shader_schema_assets(world: &World) -> Vec<(String, String)> { - let mut schemas: Vec<(String, String)> = world +fn create_material_instance_from_base(world: &mut World, base_path: &str, base_label: &str) { + let base_reference = world + .get_resource_mut::() + .ok_or_else(|| "Asset registry is unavailable".to_string()) + .and_then(|mut registry| { + ensure_asset_record( + &mut registry, + base_path.to_string(), + base_label.to_string(), + "Material", + ) + .map(|record| { + MaterialRef::new( + EditorAssetRef::new( + record.id.as_string(), + "material:source", + base_label.to_string(), + ) + .with_source_path(base_path), + ) + }) + }); + let base_reference = match base_reference { + Ok(reference) => reference, + Err(error) => { + world.resource_mut::().status = error; + return; + } + }; + let base = Path::new(base_path); + let parent = base + .parent() + .unwrap_or_else(|| Path::new("assets/materials")); + let stem = base + .file_stem() + .and_then(|value| value.to_str()) + .unwrap_or("material"); + let mut index = 1usize; + let path = loop { + let suffix = if index == 1 { + "instance".to_string() + } else { + format!("instance_{index}") + }; + let candidate = parent.join(format!("{stem}_{suffix}.ron")); + if !candidate.exists() { + break candidate; + } + index += 1; + }; + let instance = MaterialInstanceAsset { + schema_version: shared::MATERIAL_INSTANCE_SCHEMA_VERSION, + label: format!("{base_label} Instance"), + base: base_reference, + parameters: Vec::new(), + textures: Vec::new(), + }; + let result = ron::ser::to_string_pretty(&instance, ron::ser::PrettyConfig::default()) + .map_err(|error| format!("could not serialize material instance: {error}")) + .and_then(|text| { + fs::write(&path, text) + .map_err(|error| format!("could not write {}: {error}", path.display())) + }); + if let Err(error) = result { + world.resource_mut::().status = error; + return; + } + let normalized = path.to_string_lossy().replace('\\', "/"); + let registry_error = + if let Some(mut registry) = world.get_resource_mut::() { + ensure_asset_record( + &mut registry, + normalized.clone(), + instance.label.clone(), + "Material", + ) + .map(|_| ()) + .and_then(|_| save_registry(®istry)) + .err() + } else { + Some("Asset registry is unavailable".to_string()) + }; + if let Some(error) = registry_error { + world.resource_mut::().status = + format!("Created {normalized}, but registry update failed: {error}"); + return; + } + world.resource_mut::().refresh(); + world + .resource_mut::() + .select(AssetSelection::File(normalized.clone())); + invalidate_on_catalog_refresh(world); + world.resource_mut::().status = format!("Created material instance {normalized}"); +} + +fn material_base_candidates(world: &World) -> Vec<(String, MaterialRef)> { + let registry = world.get_resource::(); + let mut candidates = world + .resource::() + .assets + .iter() + .filter(|asset| matches!(asset.kind, EditorAssetKind::Material)) + .filter_map(|asset| { + let path = asset.path.as_deref()?; + MaterialAsset::load_from_path(path).ok()?; + let record = registry.and_then(|registry| find_asset_by_path(registry, path))?; + Some(( + asset.label.clone(), + MaterialRef::new( + EditorAssetRef::new( + record.id.as_string(), + "material:source", + asset.label.clone(), + ) + .with_source_path(path), + ), + )) + }) + .collect::>(); + candidates.sort_by(|left, right| natural_cmp(&left.0, &right.0)); + candidates +} + +fn material_instance_asset_editor( + world: &mut World, + ui: &mut egui::Ui, + asset: &EditorAsset, + path: &str, +) { + ensure_material_instance_draft(world, path, &asset.label); + let base_candidates = material_base_candidates(world); + let texture_assets = material_texture_candidates(world); + let texture_drop_candidate = world + .resource::() + .dragging_selection() + .and_then(|selection| { + texture_assets + .iter() + .find(|candidate| candidate.selection == *selection) + }) + .cloned(); + let base_asset = world + .resource::() + .material_instance_draft + .as_ref() + .and_then(|draft| draft.asset.base.0.source_path.as_deref()) + .and_then(|path| MaterialAsset::load_from_path(path).ok()); + let schema = base_asset.as_ref().and_then(material_schema_for_asset); + let mut apply = false; + let mut revert = false; + let mut accepted_texture_drop = false; + { + let mut state = world.resource_mut::(); + let Some(draft) = state.material_instance_draft.as_mut() else { + return; + }; + if let Some(error) = draft.error.as_ref() { + ui.colored_label(egui::Color32::YELLOW, error); + } + compact_text_edit(ui, "Label", &mut draft.asset.label); + ui.horizontal(|ui| { + ui.add_sized([92.0, 20.0], egui::Label::new("Base material")); + let selected = base_candidates + .iter() + .find(|(_, candidate)| candidate.0.asset_id == draft.asset.base.0.asset_id) + .map(|(label, _)| label.as_str()) + .unwrap_or("Missing base"); + egui::ComboBox::from_id_salt("material_instance_base") + .selected_text(selected) + .show_ui(ui, |ui| { + for (label, reference) in &base_candidates { + if ui + .selectable_label( + reference.0.asset_id == draft.asset.base.0.asset_id, + label, + ) + .clicked() + { + draft.asset.base = reference.clone(); + } + } + }); + }); + ui.small("Overrides are sparse; unchecked properties continue to inherit the base asset."); + ui.separator(); + ui.label(panel_heading("Standard Overrides")); + let base_desc = base_asset + .as_ref() + .map(|asset| &asset.material) + .cloned() + .unwrap_or_default(); + material_instance_parameter_override_ui( + ui, + "Base color", + "base_color", + ShaderPropertyType::Color, + MaterialParameterValue::Color(base_desc.base_color), + &mut draft.asset.parameters, + ); + material_instance_parameter_override_ui( + ui, + "Metallic", + "metallic", + ShaderPropertyType::Float { + min: Some(0.0), + max: Some(1.0), + }, + MaterialParameterValue::Float(base_desc.metallic), + &mut draft.asset.parameters, + ); + material_instance_parameter_override_ui( + ui, + "Roughness", + "roughness", + ShaderPropertyType::Float { + min: Some(0.0), + max: Some(1.0), + }, + MaterialParameterValue::Float(base_desc.roughness), + &mut draft.asset.parameters, + ); + material_instance_parameter_override_ui( + ui, + "Emissive", + "emissive_color", + ShaderPropertyType::Color, + MaterialParameterValue::Color(base_desc.emissive_color), + &mut draft.asset.parameters, + ); + material_instance_parameter_override_ui( + ui, + "Emissive nits", + "emissive_intensity", + ShaderPropertyType::Float { + min: Some(0.0), + max: Some(50_000.0), + }, + MaterialParameterValue::Float(base_desc.emissive_intensity), + &mut draft.asset.parameters, + ); + if let Some(schema) = schema.as_ref() { + let custom = schema + .parameters + .iter() + .filter(|property| { + !matches!(property.property_type, ShaderPropertyType::Texture) + && !matches!( + property.name.as_str(), + "base_color" + | "metallic" + | "roughness" + | "emissive_color" + | "emissive_intensity" + ) + }) + .collect::>(); + if !custom.is_empty() { + ui.separator(); + ui.label(panel_heading("Shader Overrides")); + for property in custom { + let inherited = base_desc + .parameters + .iter() + .find(|value| value.name == property.name) + .or_else(|| { + schema + .default_values + .iter() + .find(|value| value.name == property.name) + }) + .map(|value| value.value.clone()) + .unwrap_or_else(|| { + default_value_for_shader_property(&property.property_type) + }); + material_instance_parameter_override_ui( + ui, + &property.display_name, + &property.name, + property.property_type.clone(), + inherited, + &mut draft.asset.parameters, + ); + } + } + } + ui.separator(); + ui.label(panel_heading("Texture Overrides")); + let mut texture_properties = vec![ + ("Base color", "base_color_texture"), + ("Normal map", "normal_map_texture"), + ("Metallic / roughness", "metallic_roughness_texture"), + ("Emissive", "emissive_texture"), + ]; + if let Some(schema) = schema.as_ref() { + for property in schema + .parameters + .iter() + .filter(|property| matches!(property.property_type, ShaderPropertyType::Texture)) + { + if !texture_properties + .iter() + .any(|(_, name)| *name == property.name) + { + texture_properties.push((&property.display_name, &property.name)); + } + } + } + for (label, name) in texture_properties { + accepted_texture_drop |= material_instance_texture_override_ui( + ui, + label, + name, + &mut draft.asset.textures, + &texture_assets, + texture_drop_candidate.as_ref(), + ); + } + ui.horizontal(|ui| { + if ui.button("Apply").clicked() { + apply = true; + } + if ui.button("Revert").clicked() { + revert = true; + } + }); + } + if revert { + world + .resource_mut::() + .material_instance_draft = None; + ensure_material_instance_draft(world, path, &asset.label); + } + if apply { + save_material_instance_draft(world); + } + if accepted_texture_drop { + world.resource_mut::().clear_drag(); + } +} + +fn ensure_material_instance_draft(world: &mut World, path: &str, fallback_label: &str) { + let needs_load = world + .resource::() + .material_instance_draft + .as_ref() + .is_none_or(|draft| draft.path != path); + if !needs_load { + return; + } + let draft = match MaterialInstanceAsset::load_from_path(path) { + Ok(asset) => MaterialInstanceAssetDraft { + path: path.to_string(), + asset, + error: None, + }, + Err(error) => MaterialInstanceAssetDraft { + path: path.to_string(), + asset: MaterialInstanceAsset { + schema_version: shared::MATERIAL_INSTANCE_SCHEMA_VERSION, + label: fallback_label.to_string(), + base: MaterialRef::default(), + parameters: Vec::new(), + textures: Vec::new(), + }, + error: Some(error), + }, + }; + world + .resource_mut::() + .material_instance_draft = Some(draft); +} + +fn save_material_instance_draft(world: &mut World) { + let Some(draft) = world + .resource::() + .material_instance_draft + .clone() + else { + return; + }; + let result = if !draft.asset.base.is_resolved() + || draft.asset.base.0.sub_asset_id != "material:source" + { + Err("Material Instance base must resolve directly to a Material asset".to_string()) + } else { + ron::ser::to_string_pretty(&draft.asset, ron::ser::PrettyConfig::default()) + .map_err(|error| format!("could not serialize material instance: {error}")) + .and_then(|text| { + fs::write(&draft.path, text) + .map_err(|error| format!("could not write {}: {error}", draft.path)) + }) + }; + match result { + Ok(()) => { + if let Some(current) = world + .resource_mut::() + .material_instance_draft + .as_mut() + { + current.error = None; + } + world.resource_mut::().refresh(); + invalidate_on_catalog_refresh(world); + world.resource_mut::().status = + format!("Saved material instance {}", draft.path); + } + Err(error) => { + if let Some(current) = world + .resource_mut::() + .material_instance_draft + .as_mut() + { + current.error = Some(error.clone()); + } + world.resource_mut::().status = error; + } + } +} + +fn material_schema_for_asset(asset: &MaterialAsset) -> Option { + asset + .shader_ref + .as_ref() + .and_then(|reference| reference.source_path.as_deref()) + .or(asset.material.shader.schema_path.as_deref()) + .or(asset + .shader + .as_deref() + .filter(|path| path.ends_with(".ron"))) + .and_then(|path| ShaderSchemaAsset::load_from_path(path).ok()) +} + +fn material_instance_parameter_override_ui( + ui: &mut egui::Ui, + label: &str, + name: &str, + property_type: ShaderPropertyType, + inherited: MaterialParameterValue, + parameters: &mut Vec, +) { + let index = parameters.iter().position(|value| value.name == name); + let mut enabled = index.is_some(); + if ui + .checkbox(&mut enabled, format!("Override {label}")) + .changed() + { + if enabled { + parameters.push(MaterialParameter { + name: name.to_string(), + value: inherited, + }); + } else { + parameters.retain(|value| value.name != name); + } + } + if let Some(value) = parameters.iter_mut().find(|value| value.name == name) { + material_parameter_value_ui(ui, label, &property_type, &mut value.value); + } +} + +fn material_instance_texture_override_ui( + ui: &mut egui::Ui, + label: &str, + name: &str, + textures: &mut Vec, + texture_assets: &[MaterialTextureCandidate], + drop_candidate: Option<&MaterialTextureCandidate>, +) -> bool { + let mut enabled = textures.iter().any(|value| value.name == name); + if ui + .checkbox(&mut enabled, format!("Override {label}")) + .changed() + { + if enabled { + textures.push(MaterialTextureBinding { + name: name.to_string(), + texture: None, + }); + } else { + textures.retain(|value| value.name != name); + } + } + + let current = textures + .iter() + .find(|value| value.name == name) + .and_then(|binding| binding.texture.as_ref()) + .cloned(); + let response = texture_slot_ui( + ui, + label, + format!("instance_{name}"), + TextureSlotSelection::Reference(current.as_ref()), + if enabled { "(none)" } else { "(inherits base)" }, + texture_assets, + drop_candidate, + ); + let accepted_drop = response.accepted_drop; + if response.clear { + textures.retain(|value| value.name != name); + } else if let Some(candidate) = response.selected { + set_material_instance_texture_override(textures, name, Some(candidate.reference)); + } + accepted_drop +} + +fn set_material_instance_texture_override( + textures: &mut Vec, + name: &str, + texture: Option, +) { + match texture { + Some(texture) => { + if let Some(binding) = textures.iter_mut().find(|value| value.name == name) { + binding.texture = Some(texture); + } else { + textures.push(MaterialTextureBinding { + name: name.to_string(), + texture: Some(texture), + }); + } + } + None => textures.retain(|value| value.name != name), + } +} + +fn shader_schema_assets(world: &World) -> Vec<(String, String, EditorAssetRef)> { + let registry = world.get_resource::(); + let mut schemas: Vec<(String, String, EditorAssetRef)> = world .resource::() .assets .iter() .filter(|asset| matches!(asset.kind, EditorAssetKind::ShaderSchema)) - .filter_map(|asset| Some((asset.label.clone(), asset.path.clone()?))) + .filter_map(|asset| { + let path = asset.path.clone()?; + let record = registry.and_then(|registry| find_asset_by_path(registry, &path))?; + Some(( + asset.label.clone(), + path.clone(), + EditorAssetRef::new(record.id.as_string(), "shader:schema", asset.label.clone()) + .with_source_path(path), + )) + }) .collect(); schemas.sort_by(|a, b| natural_cmp(&a.0, &b.0)); schemas } -fn texture_asset_refs(world: &World) -> Vec<(String, EditorAssetRef)> { - let registry = world.get_resource::(); - let mut textures: Vec<(String, EditorAssetRef)> = world - .resource::() - .assets +fn material_texture_candidates(world: &World) -> Vec { + let Some(registry) = world.get_resource::() else { + return Vec::new(); + }; + let assets = &world.resource::().assets; + let mut textures = Vec::new(); + let mut seen = HashSet::new(); + + for asset in assets .iter() .filter(|asset| matches!(asset.kind, EditorAssetKind::Texture)) - .filter_map(|asset| { - let path = asset.path.as_deref()?; - let record = registry.and_then(|registry| find_asset_by_path(registry, path))?; - Some(( - asset.label.clone(), - EditorAssetRef::new(record.id.as_string(), "texture:source", asset.label.clone()), - )) - }) - .collect(); - textures.sort_by(|a, b| natural_cmp(&a.0, &b.0)); + { + let Some(path) = asset.path.as_deref() else { + continue; + }; + let Some(record) = find_asset_by_path(registry, path) else { + continue; + }; + let reference = + EditorAssetRef::new(record.id.as_string(), "texture:source", asset.label.clone()) + .with_source_path(path); + if !seen.insert((reference.asset_id.clone(), reference.sub_asset_id.clone())) { + continue; + } + textures.push(MaterialTextureCandidate { + label: asset.label.clone(), + detail: path.to_string(), + path: path.to_string(), + reference, + selection: AssetSelection::File(path.to_string()), + }); + } + + for asset in assets + .iter() + .filter(|asset| matches!(asset.kind, EditorAssetKind::Model)) + { + let Some(parent_path) = asset.path.as_deref() else { + continue; + }; + let Some(record) = find_asset_by_path(registry, parent_path) else { + continue; + }; + for embedded in embedded_assets_for_asset(world, asset) + .into_iter() + .filter(|embedded| embedded.kind == AssetSubAssetKind::Texture) + { + let AssetSelection::SubAsset { + sub_asset_id, + source_path: Some(source_path), + .. + } = &embedded.selection + else { + continue; + }; + let reference = EditorAssetRef::new( + record.id.as_string(), + sub_asset_id.clone(), + embedded.label.clone(), + ) + .with_source_path(source_path); + if !seen.insert((reference.asset_id.clone(), reference.sub_asset_id.clone())) { + continue; + } + textures.push(MaterialTextureCandidate { + label: embedded.label, + detail: format!("{} | {source_path}", asset.label), + path: source_path.clone(), + reference, + selection: embedded.selection, + }); + } + } + + textures.sort_by(|left, right| { + natural_cmp(&left.label, &right.label) + .then_with(|| natural_cmp(&left.detail, &right.detail)) + }); textures } fn material_shader_interface( ui: &mut egui::Ui, asset: &mut MaterialAsset, - shader_schemas: &[(String, String)], - texture_assets: &[(String, EditorAssetRef)], + shader_schemas: &[(String, String, EditorAssetRef)], + texture_assets: &[MaterialTextureCandidate], + drop_candidate: Option<&MaterialTextureCandidate>, + accepted_texture_drop: &mut bool, ) { shader_kind_picker(ui, &mut asset.material.shader.kind); if matches!(asset.material.shader.kind, MaterialShaderKind::Custom) { @@ -2397,8 +3145,8 @@ fn material_shader_interface( .and_then(|path| { shader_schemas .iter() - .find(|(_, schema_path)| schema_path == path) - .map(|(label, _)| label.as_str()) + .find(|(_, schema_path, _)| schema_path == path) + .map(|(label, _, _)| label.as_str()) }) .unwrap_or("(none)"), ) @@ -2409,7 +3157,7 @@ fn material_shader_interface( { selected_schema = None; } - for (label, path) in shader_schemas { + for (label, path, _) in shader_schemas { if ui .selectable_label(selected_schema.as_deref() == Some(path), label) .clicked() @@ -2422,9 +3170,15 @@ fn material_shader_interface( if selected_schema != asset.material.shader.schema_path { asset.material.shader.schema_path = selected_schema.clone(); if let Some(path) = selected_schema.as_deref() { + asset.shader_ref = shader_schemas + .iter() + .find(|(_, schema_path, _)| schema_path == path) + .map(|(_, _, reference)| reference.clone()); if let Err(error) = apply_shader_schema(asset, path) { ui.colored_label(egui::Color32::YELLOW, error); } + } else { + asset.shader_ref = None; } } optional_path_edit(ui, "WGSL shader", &mut asset.material.shader.shader_path); @@ -2439,6 +3193,7 @@ fn material_shader_interface( }); } } else { + asset.shader_ref = None; asset.material.shader.schema_path = None; asset.material.shader.shader_path = None; } @@ -2458,12 +3213,20 @@ fn material_shader_interface( &schema.parameters, &mut asset.material.textures, texture_assets, + drop_candidate, + accepted_texture_drop, ); } else if !asset.material.parameters.is_empty() || !asset.material.textures.is_empty() { ui.separator(); ui.label(panel_heading("Stored Shader Values")); raw_material_parameter_editor(ui, &mut asset.material.parameters); - raw_material_texture_editor(ui, &mut asset.material.textures, texture_assets); + raw_material_texture_editor( + ui, + &mut asset.material.textures, + texture_assets, + drop_candidate, + accepted_texture_drop, + ); } } @@ -2543,7 +3306,9 @@ fn shader_schema_texture_editor( ui: &mut egui::Ui, properties: &[ShaderPropertyDesc], textures: &mut Vec, - texture_assets: &[(String, EditorAssetRef)], + texture_assets: &[MaterialTextureCandidate], + drop_candidate: Option<&MaterialTextureCandidate>, + accepted_texture_drop: &mut bool, ) { let texture_props: Vec<&ShaderPropertyDesc> = properties .iter() @@ -2556,7 +3321,13 @@ fn shader_schema_texture_editor( ui.label(panel_heading("Shader Textures")); for property in texture_props { let texture = ensure_material_texture_binding(textures, &property.name); - texture_binding_ui(ui, &property.display_name, texture, texture_assets); + *accepted_texture_drop |= texture_binding_ui( + ui, + &property.display_name, + texture, + texture_assets, + drop_candidate, + ); } } @@ -2570,7 +3341,9 @@ fn raw_material_parameter_editor(ui: &mut egui::Ui, parameters: &mut [MaterialPa fn raw_material_texture_editor( ui: &mut egui::Ui, textures: &mut [MaterialTextureBinding], - texture_assets: &[(String, EditorAssetRef)], + texture_assets: &[MaterialTextureCandidate], + drop_candidate: Option<&MaterialTextureCandidate>, + accepted_texture_drop: &mut bool, ) { if textures.is_empty() { return; @@ -2579,7 +3352,8 @@ fn raw_material_texture_editor( ui.label(panel_heading("Stored Shader Textures")); for texture in textures { let label = texture.name.clone(); - texture_binding_ui(ui, &label, texture, texture_assets); + *accepted_texture_drop |= + texture_binding_ui(ui, &label, texture, texture_assets, drop_candidate); } } @@ -2690,42 +3464,189 @@ fn texture_binding_ui( ui: &mut egui::Ui, label: &str, binding: &mut MaterialTextureBinding, - texture_assets: &[(String, EditorAssetRef)], -) { - ui.horizontal(|ui| { - ui.add_sized([92.0, 20.0], egui::Label::new(label)); - let selected = binding - .texture - .as_ref() - .and_then(|current| { - texture_assets - .iter() - .find(|(_, candidate)| *candidate == *current) - .map(|(label, _)| label.as_str()) - }) - .unwrap_or("(none)"); - egui::ComboBox::from_id_salt(format!("texture_binding_{}", binding.name)) - .selected_text(selected) - .show_ui(ui, |ui| { - if ui - .selectable_label(binding.texture.is_none(), "(none)") - .clicked() - { - binding.texture = None; - } - for (label, reference) in texture_assets { - if ui - .selectable_label(binding.texture.as_ref() == Some(reference), label) - .clicked() - { - binding.texture = Some(reference.clone()); - } - } + texture_assets: &[MaterialTextureCandidate], + drop_candidate: Option<&MaterialTextureCandidate>, +) -> bool { + let response = texture_slot_ui( + ui, + label, + format!("binding_{}", binding.name), + TextureSlotSelection::Reference(binding.texture.as_ref()), + "(none)", + texture_assets, + drop_candidate, + ); + let accepted_drop = response.accepted_drop; + if response.clear { + binding.texture = None; + } else if let Some(candidate) = response.selected { + binding.texture = Some(candidate.reference); + } + accepted_drop +} + +fn material_texture_path_ui( + ui: &mut egui::Ui, + label: &str, + slot_id: &str, + value: &mut Option, + texture_assets: &[MaterialTextureCandidate], + drop_candidate: Option<&MaterialTextureCandidate>, +) -> bool { + let response = texture_slot_ui( + ui, + label, + slot_id, + TextureSlotSelection::Path(value.as_deref()), + "(none)", + texture_assets, + drop_candidate, + ); + let accepted_drop = response.accepted_drop; + if response.clear { + *value = None; + } else if let Some(candidate) = response.selected { + *value = Some(candidate.path); + } + accepted_drop +} + +fn texture_slot_ui( + ui: &mut egui::Ui, + label: &str, + slot_id: impl std::hash::Hash, + current: TextureSlotSelection<'_>, + empty_label: &str, + candidates: &[MaterialTextureCandidate], + drop_candidate: Option<&MaterialTextureCandidate>, +) -> TextureSlotResponse { + let mut result = TextureSlotResponse::default(); + ui.push_id(slot_id, |ui| { + ui.horizontal(|ui| { + ui.add_sized([92.0, 20.0], egui::Label::new(label)); + let width = ui.available_width().max(1.0); + let (rect, hover) = + ui.allocate_exact_size(egui::vec2(width, 30.0), egui::Sense::hover()); + let valid_drag = drop_candidate.is_some(); + let drop_hovered = valid_drag && ui.rect_contains_pointer(rect); + let row_hovered = hover.hovered(); + let stroke = if drop_hovered { + egui::Stroke::new(2.0, egui::Color32::from_rgb(125, 198, 255)) + } else if valid_drag { + egui::Stroke::new(1.0, egui::Color32::from_rgb(58, 88, 122)) + } else if row_hovered { + egui::Stroke::new(1.0, egui::Color32::from_rgb(92, 102, 118)) + } else { + egui::Stroke::new(1.0, BORDER) + }; + let fill = if drop_hovered { + egui::Color32::from_rgb(29, 57, 86) + } else if row_hovered { + WIDGET_BG.linear_multiply(1.05) + } else { + WIDGET_BG.linear_multiply(0.75) + }; + ui.painter() + .rect(rect, 4.0, fill, stroke, egui::StrokeKind::Inside); + + let selected_candidate = candidates.iter().find(|candidate| match current { + TextureSlotSelection::Path(path) => path == Some(candidate.path.as_str()), + TextureSlotSelection::Reference(reference) => reference.is_some_and(|reference| { + reference.asset_id == candidate.reference.asset_id + && reference.sub_asset_id == candidate.reference.sub_asset_id + }), }); - if ui.button("Clear").clicked() { - binding.texture = None; - } + let current_present = match current { + TextureSlotSelection::Path(path) => path.is_some(), + TextureSlotSelection::Reference(reference) => reference.is_some(), + }; + let display_label = selected_candidate + .map(|candidate| candidate.label.as_str()) + .or_else(|| match current { + TextureSlotSelection::Path(path) => path, + TextureSlotSelection::Reference(reference) => { + reference.map(|reference| reference.label.as_str()) + } + }) + .unwrap_or(empty_label); + + let mut child = ui.new_child( + egui::UiBuilder::new() + .max_rect(rect.shrink2(egui::vec2(5.0, 3.0))) + .layout(egui::Layout::left_to_right(egui::Align::Center)), + ); + child.set_clip_rect(rect); + child.add_sized( + [18.0, 20.0], + egui::Label::new(icon_text(icons::IMAGE, 13.0).color(TEXT_DIM)), + ); + let action_width = 56.0; + let text_width = + (child.available_width() - action_width - child.spacing().item_spacing.x).max(1.0); + child.allocate_ui_with_layout( + egui::vec2(text_width, 22.0), + egui::Layout::left_to_right(egui::Align::Center), + |ui| { + ui.add_sized( + [text_width, 20.0], + egui::Label::new(display_label).truncate(), + ); + }, + ); + child.allocate_ui_with_layout( + egui::vec2(action_width, 24.0), + egui::Layout::right_to_left(egui::Align::Center), + |ui| { + let clear = ui.add_enabled( + current_present, + egui::Button::new(icon_text(icons::X, 14.0)) + .frame(false) + .min_size(egui::vec2(20.0, 20.0)), + ); + if clear.on_hover_text("Clear texture").clicked() { + result.clear = true; + } + if candidates.is_empty() { + ui.add_enabled( + false, + egui::Button::new(icon_text(icons::FOLDER_OPEN, 14.0)) + .frame(false) + .min_size(egui::vec2(20.0, 20.0)), + ) + .on_hover_text("No project textures available"); + } else { + let menu = ui.menu_button(icon_text(icons::FOLDER_OPEN, 14.0), |ui| { + ui.set_min_width(240.0); + for candidate in candidates { + let selected = selected_candidate.is_some_and(|selected| { + selected.reference.asset_id == candidate.reference.asset_id + && selected.reference.sub_asset_id + == candidate.reference.sub_asset_id + }); + if ui + .selectable_label(selected, candidate.label.as_str()) + .on_hover_text(candidate.detail.as_str()) + .clicked() + { + result.selected = Some(candidate.clone()); + ui.close(); + } + } + }); + menu.response.on_hover_text("Select a project texture"); + } + }, + ); + + if drop_hovered && ui.input(|input| input.pointer.any_released()) { + if let Some(candidate) = drop_candidate { + result.selected = Some(candidate.clone()); + result.accepted_drop = true; + } + } + }); }); + result } fn default_value_for_shader_property(property_type: &ShaderPropertyType) -> MaterialParameterValue { @@ -3119,11 +4040,11 @@ fn subasset_context_menu( } match embedded.kind { AssetSubAssetKind::Mesh => { - if embedded.requires_scene_instance { - ui.label( - egui::RichText::new("Skinned mesh: place parent model or animation clip") - .color(TEXT_DIM), - ); + if embedded.requires_skinned_hierarchy { + if ui.button("Place Skinned Model at Origin").clicked() { + place_subasset_operator(world, embedded.selection.clone(), Vec3::ZERO); + ui.close(); + } } else if ui.button("Place at Origin").clicked() { place_subasset_operator(world, embedded.selection.clone(), Vec3::ZERO); ui.close(); @@ -3150,7 +4071,7 @@ fn subasset_context_menu( .as_slice() .iter() .copied() - .find(|entity| world.get::(*entity).is_some()); + .find(|entity| world.get::(*entity).is_some()); let action = if animated_actor.is_some() { "Assign To Selected Actor" } else { @@ -3232,7 +4153,7 @@ fn regenerate_subasset_thumbnail(world: &mut World, embedded: &EmbeddedAsset) { parent_path.clone(), mesh_label, embedded.material_label.clone(), - embedded.requires_scene_instance, + embedded.requires_skinned_hierarchy, &mut studio, ); }); @@ -3489,4 +4410,27 @@ mod tests { assert_eq!(audio_format_label("assets/audio/source.mp3"), "MP3"); assert_eq!(audio_format_label("assets/audio/source.flac"), "FLAC"); } + + #[test] + fn material_instance_texture_override_stays_sparse() { + let first = EditorAssetRef::new("texture-a", "texture:source", "First") + .with_source_path("assets/textures/first.png"); + let second = EditorAssetRef::new("texture-b", "texture:source", "Second") + .with_source_path("assets/textures/second.png"); + let mut textures = Vec::new(); + + set_material_instance_texture_override(&mut textures, "base_color_texture", Some(first)); + set_material_instance_texture_override( + &mut textures, + "base_color_texture", + Some(second.clone()), + ); + + assert_eq!(textures.len(), 1); + assert_eq!(textures[0].name, "base_color_texture"); + assert_eq!(textures[0].texture.as_ref(), Some(&second)); + + set_material_instance_texture_override(&mut textures, "base_color_texture", None); + assert!(textures.is_empty()); + } } diff --git a/crates/editor/src/ui/asset_browser/state.rs b/crates/editor/src/ui/asset_browser/state.rs index f0b2be4..2c7e197 100644 --- a/crates/editor/src/ui/asset_browser/state.rs +++ b/crates/editor/src/ui/asset_browser/state.rs @@ -3,7 +3,7 @@ use std::collections::HashSet; use bevy::prelude::*; -use shared::MaterialAsset; +use shared::{MaterialAsset, MaterialInstanceAsset}; use crate::asset_db::ImportSettings; use crate::assets::{AssetSelection, ASSETS_ROOT, BUILTINS_FOLDER}; @@ -48,6 +48,7 @@ pub struct AssetBrowserUiState { pub(crate) pending_delete: Option, pub(crate) import_draft: Option, pub(crate) material_draft: Option, + pub(crate) material_instance_draft: Option, } #[derive(Debug, Clone)] @@ -71,6 +72,13 @@ pub(crate) struct MaterialAssetDraft { pub(crate) error: Option, } +#[derive(Debug, Clone)] +pub(crate) struct MaterialInstanceAssetDraft { + pub(crate) path: String, + pub(crate) asset: MaterialInstanceAsset, + pub(crate) error: Option, +} + impl Default for AssetBrowserUiState { fn default() -> Self { let mut expanded_folders = HashSet::new(); @@ -89,6 +97,7 @@ impl Default for AssetBrowserUiState { pending_delete: None, import_draft: None, material_draft: None, + material_instance_draft: None, } } } diff --git a/crates/editor/src/ui/component_registry.rs b/crates/editor/src/ui/component_registry.rs index 51087a1..e7ea2f9 100644 --- a/crates/editor/src/ui/component_registry.rs +++ b/crates/editor/src/ui/component_registry.rs @@ -1,6 +1,12 @@ //! Editor-visible component registry for actor inspector palettes. +use std::collections::{HashMap, HashSet}; + +use bevy::ecs::reflect::ReflectComponent; use bevy::prelude::*; +use bevy::reflect::std_traits::ReflectDefault; +use bevy::reflect::GetTypeRegistration; +use bevy_egui::egui; use egui_phosphor_icons::icons; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -9,6 +15,7 @@ pub enum EditorComponentCategory { Rendering, Animation, Audio, + Navigation, Physics, Gameplay, Volumes, @@ -17,6 +24,8 @@ pub enum EditorComponentCategory { #[derive(Debug, Clone)] pub struct EditorComponentDescriptor { + /// Stable editor/protocol identity. This must not be a Rust type path. + pub id: &'static str, pub type_name: &'static str, pub display_name: &'static str, pub category: EditorComponentCategory, @@ -35,6 +44,266 @@ pub struct EditorComponentDescriptor { #[derive(Resource, Debug, Clone)] pub struct EditorComponentRegistry { pub descriptors: Vec, + inspectors: HashMap<&'static str, ComponentInspectorFn>, +} + +pub type ComponentInspectorFn = fn(&mut World, &mut egui::Ui, Entity); + +impl EditorComponentRegistry { + /// Registers a statically linked component extension. + /// + /// Stable ids and reflected type paths are both unique. Returning an error + /// here prevents a game/editor extension from silently shadowing a built-in. + pub fn register(&mut self, descriptor: EditorComponentDescriptor) -> Result<(), String> { + if self + .descriptors + .iter() + .any(|existing| existing.id == descriptor.id) + { + return Err(format!( + "duplicate authoring component id `{}`", + descriptor.id + )); + } + if self + .descriptors + .iter() + .any(|existing| existing.type_name == descriptor.type_name) + { + return Err(format!( + "component type `{}` is already registered", + descriptor.type_name + )); + } + self.descriptors.push(descriptor); + Ok(()) + } + + pub fn register_with_inspector( + &mut self, + descriptor: EditorComponentDescriptor, + inspector: ComponentInspectorFn, + ) -> Result<(), String> { + let type_name = descriptor.type_name; + self.register(descriptor)?; + self.inspectors.insert(type_name, inspector); + Ok(()) + } + + pub fn inspector(&self, type_name: &str) -> Option { + self.inspectors.get(type_name).copied() + } + + /// Hard lifecycle requirements keyed by stable component IDs. Keeping this + /// lookup in the registry removes requirement policy from inspector code. + pub fn required_component_ids( + &self, + descriptor: &EditorComponentDescriptor, + ) -> &'static [&'static str] { + match descriptor.id { + shared::AUTHORING_COMPONENT_ANIMATION_CONTROLLER => { + &[shared::AUTHORING_COMPONENT_SKINNED_MESH_RENDERER] + } + _ => &[], + } + } + + pub fn present_dependents( + &self, + world: &World, + entity: Entity, + required_id: &str, + ) -> Vec<&EditorComponentDescriptor> { + self.descriptors + .iter() + .filter(|descriptor| { + self.required_component_ids(descriptor) + .contains(&required_id) + && self.component_present(world, entity, descriptor.type_name) + }) + .collect() + } + + pub fn by_id(&self, id: &str) -> Option<&EditorComponentDescriptor> { + self.descriptors + .iter() + .find(|descriptor| descriptor.id == id) + } + + pub fn by_type_name(&self, type_name: &str) -> Option<&EditorComponentDescriptor> { + self.descriptors + .iter() + .find(|descriptor| descriptor.type_name == type_name) + } + + pub fn stable_id_for_type<'a>(&self, type_name: &'a str) -> &'a str { + self.by_type_name(type_name) + .map(|descriptor| descriptor.id) + .unwrap_or(type_name) + } + + pub fn ensure_component_order(&self, order: &mut shared::InspectorOrder, present: &[&str]) { + for type_name in present { + let component_id = self.stable_id_for_type(type_name); + if !order + .component_ids + .iter() + .any(|existing| existing == component_id) + { + order.component_ids.push(component_id.to_string()); + } + } + order.component_type_names.clear(); + } + + pub fn ordered_components<'a>( + &self, + order: &shared::InspectorOrder, + present: &[&'a str], + ) -> Vec<&'a str> { + let mut ordered = Vec::new(); + for component_id in &order.component_ids { + if let Some(type_name) = present + .iter() + .find(|type_name| self.stable_id_for_type(type_name) == component_id) + { + ordered.push(*type_name); + } + } + for legacy_type_name in &order.component_type_names { + if let Some(type_name) = present + .iter() + .find(|type_name| **type_name == legacy_type_name) + { + if !ordered.contains(type_name) { + ordered.push(*type_name); + } + } + } + for type_name in present { + if !ordered.contains(type_name) { + ordered.push(*type_name); + } + } + ordered + } + + pub fn move_component( + &self, + order: &mut shared::InspectorOrder, + type_name: &str, + offset: isize, + present: &[&str], + ) -> bool { + self.ensure_component_order(order, present); + let component_id = self.stable_id_for_type(type_name); + let Some(index) = order + .component_ids + .iter() + .position(|existing| existing == component_id) + else { + return false; + }; + let target = + (index as isize + offset).clamp(0, order.component_ids.len() as isize - 1) as usize; + if index == target { + return false; + } + let item = order.component_ids.remove(index); + order.component_ids.insert(target, item); + true + } + + pub fn component_present(&self, world: &World, entity: Entity, type_name: &str) -> bool { + let Ok(entity_ref) = world.get_entity(entity) else { + return false; + }; + let Some(app_registry) = world.get_resource::() else { + return false; + }; + let type_registry = app_registry.read(); + type_registry + .get_with_type_path(type_name) + .and_then(|registration| registration.data::()) + .is_some_and(|component| component.contains(entity_ref)) + } + + /// Validates registry metadata against Bevy reflection before the first + /// inspector frame. This is also the contract test used by extensions. + pub fn validate(&self, world: &World) -> Result<(), Vec> { + let Some(app_registry) = world.get_resource::() else { + return Err(vec!["AppTypeRegistry is unavailable".to_string()]); + }; + let type_registry = app_registry.read(); + let mut errors = Vec::new(); + let mut ids = HashSet::new(); + let mut type_names = HashSet::new(); + for descriptor in &self.descriptors { + if descriptor.id.trim().is_empty() { + errors.push(format!( + "{} has an empty stable id", + descriptor.display_name + )); + } else if !ids.insert(descriptor.id) { + errors.push(format!("duplicate stable id `{}`", descriptor.id)); + } + if !type_names.insert(descriptor.type_name) { + errors.push(format!( + "duplicate reflected type `{}`", + descriptor.type_name + )); + } + match type_registry.get_with_type_path(descriptor.type_name) { + Some(registration) if registration.data::().is_none() => errors + .push(format!( + "`{}` is registered without ReflectComponent", + descriptor.type_name + )), + None => errors.push(format!( + "reflected component `{}` is not registered", + descriptor.type_name + )), + Some(registration) + if descriptor.addable && registration.data::().is_none() => + { + errors.push(format!( + "addable component `{}` has no reflected Default", + descriptor.type_name + )); + } + Some(_) => {} + } + for relation in descriptor + .recommended + .iter() + .chain(descriptor.conflicts_with.iter()) + { + if !self + .descriptors + .iter() + .any(|candidate| candidate.type_name == *relation) + { + errors.push(format!( + "{} references unregistered component `{relation}`", + descriptor.id + )); + } + } + for required_id in self.required_component_ids(descriptor) { + if self.by_id(required_id).is_none() { + errors.push(format!( + "{} requires unknown stable component id `{required_id}`", + descriptor.id + )); + } + } + } + if errors.is_empty() { + Ok(()) + } else { + Err(errors) + } + } } impl Default for EditorComponentRegistry { @@ -42,6 +311,7 @@ impl Default for EditorComponentRegistry { Self { descriptors: vec![ EditorComponentDescriptor { + id: shared::AUTHORING_COMPONENT_STATIC_MESH_RENDERER, type_name: "shared::components::StaticMeshRenderer", display_name: "Static Mesh Renderer", category: EditorComponentCategory::Rendering, @@ -56,11 +326,35 @@ impl Default for EditorComponentRegistry { conflicts_with: &[ "shared::components::Primitive", "shared::components::BrushDesc", + "shared::animation::SkinnedMeshRenderer", ], hydration_effect: "Hydrates into generated mesh children and material bindings.", }, EditorComponentDescriptor { + id: shared::AUTHORING_COMPONENT_SKINNED_MESH_RENDERER, + type_name: "shared::animation::SkinnedMeshRenderer", + display_name: "Skinned Mesh Renderer", + category: EditorComponentCategory::Rendering, + addable: false, + removable: true, + reorderable: true, + hidden: false, + icon: icons::PERSON_SIMPLE_RUN.as_str(), + description: + "Renders rigged geometry while preserving its imported joints and skin bindings.", + search_terms: &["mesh", "renderer", "skinned", "skeletal", "rigged", "joints"], + recommended: &["shared::animation::AnimationControllerDesc"], + conflicts_with: &[ + "shared::components::Primitive", + "shared::components::BrushDesc", + "shared::components::StaticMeshRenderer", + ], + hydration_effect: + "Hydrates through a dedicated scene root containing Bevy skinned meshes and joints.", + }, + EditorComponentDescriptor { + id: shared::AUTHORING_COMPONENT_BRUSH, type_name: "shared::components::BrushDesc", display_name: "Brush", category: EditorComponentCategory::Authoring, @@ -75,10 +369,12 @@ impl Default for EditorComponentRegistry { conflicts_with: &[ "shared::components::Primitive", "shared::components::StaticMeshRenderer", + "shared::animation::SkinnedMeshRenderer", ], hydration_effect: "Hydrates into generated brush mesh children.", }, EditorComponentDescriptor { + id: shared::AUTHORING_COMPONENT_MATERIAL, type_name: "shared::components::MaterialDesc", display_name: "Authoring Material", category: EditorComponentCategory::Rendering, @@ -94,6 +390,7 @@ impl Default for EditorComponentRegistry { hydration_effect: "Hydrates into Bevy material assets for renderable actors.", }, EditorComponentDescriptor { + id: shared::AUTHORING_COMPONENT_RIGID_BODY, type_name: "shared::components::RigidBodyDesc", display_name: "Rigid Body", category: EditorComponentCategory::Physics, @@ -109,6 +406,7 @@ impl Default for EditorComponentRegistry { hydration_effect: "Hydrates into runtime Avian rigid-body state.", }, EditorComponentDescriptor { + id: shared::AUTHORING_COMPONENT_COLLIDER, type_name: "shared::components::ColliderDesc", display_name: "Collider", category: EditorComponentCategory::Physics, @@ -124,6 +422,7 @@ impl Default for EditorComponentRegistry { hydration_effect: "Hydrates into runtime Avian collider state.", }, EditorComponentDescriptor { + id: shared::AUTHORING_COMPONENT_LIGHT, type_name: "shared::components::LightDesc", display_name: "Light", category: EditorComponentCategory::Rendering, @@ -139,6 +438,7 @@ impl Default for EditorComponentRegistry { hydration_effect: "Hydrates into runtime Bevy light components.", }, EditorComponentDescriptor { + id: shared::AUTHORING_COMPONENT_ANIMATION_CONTROLLER, type_name: "shared::animation::AnimationControllerDesc", display_name: "Animation Controller", category: EditorComponentCategory::Animation, @@ -149,12 +449,13 @@ impl Default for EditorComponentRegistry { icon: icons::FILM_SLATE.as_str(), description: "Authors named animation states for an imported model rig.", search_terms: &["animation", "controller", "state", "clip", "skeleton", "rig"], - recommended: &[], + recommended: &["shared::animation::SkinnedMeshRenderer"], conflicts_with: &[], hydration_effect: "Hydrates into a Bevy animation graph and player bound to the imported model.", }, EditorComponentDescriptor { + id: shared::AUTHORING_COMPONENT_AUDIO_SOURCE, type_name: "shared::components::AudioSourceDesc", display_name: "Audio Source", category: EditorComponentCategory::Audio, @@ -171,6 +472,7 @@ impl Default for EditorComponentRegistry { "Hydrates into runtime Bevy audio voices with bus and attenuation control.", }, EditorComponentDescriptor { + id: shared::AUTHORING_COMPONENT_AUDIO_LISTENER, type_name: "shared::components::AudioListenerDesc", display_name: "Audio Listener", category: EditorComponentCategory::Audio, @@ -187,6 +489,7 @@ impl Default for EditorComponentRegistry { "The highest-priority enabled listener becomes the runtime listener.", }, EditorComponentDescriptor { + id: shared::AUTHORING_COMPONENT_PRIMITIVE, type_name: "shared::components::Primitive", display_name: "Primitive", category: EditorComponentCategory::Authoring, @@ -201,11 +504,13 @@ impl Default for EditorComponentRegistry { conflicts_with: &[ "shared::components::StaticMeshRenderer", "shared::components::BrushDesc", + "shared::animation::SkinnedMeshRenderer", ], hydration_effect: "Hydrates into generated primitive render and collision data.", }, EditorComponentDescriptor { + id: shared::AUTHORING_COMPONENT_PLAYER_SPAWN, type_name: "shared::components::PlayerSpawn", display_name: "Player Spawn", category: EditorComponentCategory::Gameplay, @@ -222,6 +527,7 @@ impl Default for EditorComponentRegistry { "Used by PIE/session startup; no saved runtime player is created.", }, EditorComponentDescriptor { + id: shared::AUTHORING_COMPONENT_WEAPON_SPAWN, type_name: "shared::components::WeaponSpawn", display_name: "Weapon Spawn", category: EditorComponentCategory::Gameplay, @@ -237,6 +543,7 @@ impl Default for EditorComponentRegistry { hydration_effect: "Hydrates into gameplay spawn metadata.", }, EditorComponentDescriptor { + id: shared::AUTHORING_COMPONENT_TRIGGER_VOLUME, type_name: "shared::components::TriggerVolume", display_name: "Trigger Volume", category: EditorComponentCategory::Volumes, @@ -252,6 +559,7 @@ impl Default for EditorComponentRegistry { hydration_effect: "Hydrates into gameplay trigger queries and visualizers.", }, EditorComponentDescriptor { + id: shared::AUTHORING_COMPONENT_TEAM_SPAWN, type_name: "shared::components::TeamSpawn", display_name: "Team Spawn", category: EditorComponentCategory::Gameplay, @@ -267,6 +575,7 @@ impl Default for EditorComponentRegistry { hydration_effect: "Hydrates into gameplay spawn metadata.", }, EditorComponentDescriptor { + id: shared::AUTHORING_COMPONENT_OBJECTIVE, type_name: "shared::components::ObjectiveMarker", display_name: "Objective Marker", category: EditorComponentCategory::Gameplay, @@ -282,6 +591,7 @@ impl Default for EditorComponentRegistry { hydration_effect: "Hydrates into gameplay objective metadata.", }, EditorComponentDescriptor { + id: shared::AUTHORING_COMPONENT_POST_PROCESS_VOLUME, type_name: "shared::components::PostProcessVolumeDesc", display_name: "Post-Process Volume", category: EditorComponentCategory::Volumes, @@ -298,6 +608,215 @@ impl Default for EditorComponentRegistry { "Affects active camera rendering when the camera is inside the volume.", }, EditorComponentDescriptor { + id: shared::AUTHORING_COMPONENT_NAVIGATION_BOUNDS, + type_name: "shared::navigation::NavigationBounds", + display_name: "Navigation Bounds", + category: EditorComponentCategory::Navigation, + addable: true, + removable: true, + reorderable: true, + hidden: false, + icon: icons::PATH.as_str(), + description: "Defines the world region and agent profile for a navigation bake.", + search_terms: &["navigation", "navmesh", "bounds", "agent", "bake"], + recommended: &[], + conflicts_with: &[ + "shared::navigation::NavigationObstacle", + "shared::navigation::NavigationArea", + "shared::navigation::NavigationLink", + ], + hydration_effect: "Feeds the generated navigation artifact; no render component is hydrated.", + }, + EditorComponentDescriptor { + id: shared::AUTHORING_COMPONENT_NAVIGATION_OBSTACLE, + type_name: "shared::navigation::NavigationObstacle", + display_name: "Navigation Obstacle", + category: EditorComponentCategory::Navigation, + addable: true, + removable: true, + reorderable: true, + hidden: false, + icon: icons::SELECTION_SLASH.as_str(), + description: "Carves an axis-aligned blocked volume from overlapping bakes.", + search_terms: &["navigation", "navmesh", "obstacle", "block", "carve"], + recommended: &[], + conflicts_with: &[ + "shared::navigation::NavigationBounds", + "shared::navigation::NavigationArea", + "shared::navigation::NavigationLink", + ], + hydration_effect: "Contributes a deterministic unwalkable volume during bake.", + }, + EditorComponentDescriptor { + id: shared::AUTHORING_COMPONENT_NAVIGATION_AREA, + type_name: "shared::navigation::NavigationArea", + display_name: "Navigation Area", + category: EditorComponentCategory::Navigation, + addable: true, + removable: true, + reorderable: true, + hidden: false, + icon: icons::POLYGON.as_str(), + description: "Labels a walkable or blocked navigation area and traversal cost.", + search_terms: &["navigation", "navmesh", "area", "cost", "walkable"], + recommended: &[], + conflicts_with: &[ + "shared::navigation::NavigationBounds", + "shared::navigation::NavigationObstacle", + "shared::navigation::NavigationLink", + ], + hydration_effect: "Stores area metadata and blocked-volume contribution in the bake artifact.", + }, + EditorComponentDescriptor { + id: shared::AUTHORING_COMPONENT_NAVIGATION_LINK, + type_name: "shared::navigation::NavigationLink", + display_name: "Navigation Link", + category: EditorComponentCategory::Navigation, + addable: true, + removable: true, + reorderable: true, + hidden: false, + icon: icons::LINK.as_str(), + description: "Connects otherwise isolated navigation regions with an explicit traversal.", + search_terms: &["navigation", "navmesh", "link", "jump", "door", "ladder"], + recommended: &[], + conflicts_with: &[ + "shared::navigation::NavigationBounds", + "shared::navigation::NavigationObstacle", + "shared::navigation::NavigationArea", + ], + hydration_effect: "Adds a validated directed or bidirectional runtime path connection.", + }, + EditorComponentDescriptor { + id: shared::AUTHORING_COMPONENT_LEGACY_PHYSICS_BODY, + type_name: "shared::components::PhysicsBody", + display_name: "Legacy Physics Body", + category: EditorComponentCategory::Physics, + addable: false, + removable: true, + reorderable: true, + hidden: false, + icon: icons::SPHERE.as_str(), + description: "Legacy combined physics authoring retained for scene compatibility.", + search_terms: &["legacy", "physics", "body"], + recommended: &[], + conflicts_with: &[], + hydration_effect: "Hydrates through the compatibility physics path.", + }, + EditorComponentDescriptor { + id: "prefab.instance", + type_name: "shared::components::PrefabInstance", + display_name: "Prefab Instance", + category: EditorComponentCategory::Authoring, + addable: false, + removable: false, + reorderable: true, + hidden: false, + icon: icons::PACKAGE.as_str(), + description: "Stores a linked prefab instance and its overrides.", + search_terms: &["prefab", "instance", "link"], + recommended: &[], + conflicts_with: &[], + hydration_effect: "Hydrates linked prefab members beneath this anchor.", + }, + EditorComponentDescriptor { + id: "prefab.reference", + type_name: "shared::components::PrefabRef", + display_name: "Prefab Reference", + category: EditorComponentCategory::Authoring, + addable: false, + removable: false, + reorderable: false, + hidden: true, + icon: icons::PACKAGE.as_str(), + description: "Stable source reference owned by a prefab anchor.", + search_terms: &["prefab", "reference"], + recommended: &[], + conflicts_with: &[], + hydration_effect: "Resolves the prefab source asset.", + }, + EditorComponentDescriptor { + id: "render.imported_model", + type_name: "shared::components::ModelRef", + display_name: "Imported Model Reference", + category: EditorComponentCategory::Rendering, + addable: false, + removable: false, + reorderable: false, + hidden: true, + icon: icons::CUBE_TRANSPARENT.as_str(), + description: "Legacy full-scene imported model reference.", + search_terms: &["model", "imported", "scene"], + recommended: &[], + conflicts_with: &[], + hydration_effect: "Hydrates the referenced imported model scene.", + }, + EditorComponentDescriptor { + id: "render.material_override_legacy", + type_name: "shared::components::MaterialOverride", + display_name: "Legacy Material Override", + category: EditorComponentCategory::Rendering, + addable: false, + removable: false, + reorderable: false, + hidden: true, + icon: icons::PALETTE.as_str(), + description: "Legacy renderer material slot overrides retained for migration.", + search_terms: &["material", "override", "legacy"], + recommended: &[], + conflicts_with: &[], + hydration_effect: "Applies compatibility material overrides.", + }, + EditorComponentDescriptor { + id: "render.project_sun", + type_name: "shared::components::ProjectSun", + display_name: "Project Sun", + category: EditorComponentCategory::Rendering, + addable: false, + removable: false, + reorderable: true, + hidden: false, + icon: icons::SUN.as_str(), + description: "Marks the scene light that overrides the project sun.", + search_terms: &["sun", "directional", "project"], + recommended: &[], + conflicts_with: &[], + hydration_effect: "Selects the authored directional project light.", + }, + EditorComponentDescriptor { + id: "core.actor_kind", + type_name: "shared::components::ActorKind", + display_name: "Actor Kind Hint", + category: EditorComponentCategory::Editor, + addable: false, + removable: false, + reorderable: false, + hidden: true, + icon: icons::TAG.as_str(), + description: "Compatibility and presentation hint derived from primary components.", + search_terms: &["actor", "kind", "hint"], + recommended: &[], + conflicts_with: &[], + hydration_effect: "No runtime effect; validation uses component composition.", + }, + EditorComponentDescriptor { + id: "editor.component_states", + type_name: "shared::components::AuthoringComponentStates", + display_name: "Authoring Component States", + category: EditorComponentCategory::Editor, + addable: false, + removable: false, + reorderable: false, + hidden: true, + icon: icons::TOGGLE_LEFT.as_str(), + description: "Stores persisted component enable state independently of inspector layout.", + search_terms: &["editor", "active", "enabled"], + recommended: &[], + conflicts_with: &[], + hydration_effect: "Controls whether opted-in authoring components hydrate.", + }, + EditorComponentDescriptor { + id: "editor.inspector_order", type_name: "shared::components::InspectorOrder", display_name: "Inspector Order", category: EditorComponentCategory::Editor, @@ -306,14 +825,62 @@ impl Default for EditorComponentRegistry { reorderable: false, hidden: true, icon: icons::LIST_BULLETS.as_str(), - description: "Stores editor-only component ordering and active-state metadata.", + description: "Stores editor-only component card ordering.", search_terms: &["editor", "order"], recommended: &[], conflicts_with: &[], - hydration_effect: - "Controls editor presentation and authoring component active state.", + hydration_effect: "Controls editor presentation only; runtime active state is stored separately.", }, ], + inspectors: HashMap::new(), } } } + +/// Registers one statically linked game/editor component with reflection, +/// lifecycle metadata, and its editor inspector in a single call. +pub fn register_authoring_component( + app: &mut App, + descriptor: EditorComponentDescriptor, + inspector: ComponentInspectorFn, +) -> Result<(), String> +where + T: Component + Reflect + GetTypeRegistration, +{ + app.register_type::(); + if !app.world().contains_resource::() { + app.init_resource::(); + } + app.world_mut() + .resource_mut::() + .register_with_inspector(descriptor, inspector) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn built_in_registry_is_unique_and_reflectable() { + let mut app = App::new(); + app.add_plugins(shared::SharedTypesPlugin) + .init_resource::(); + + app.world() + .resource::() + .validate(app.world()) + .unwrap(); + } + + #[test] + fn static_extensions_cannot_shadow_stable_ids_or_types() { + let mut registry = EditorComponentRegistry::default(); + let mut duplicate = registry.descriptors[0].clone(); + duplicate.type_name = "game::editor_ext::StaticFixture"; + assert!(registry.register(duplicate).is_err()); + + let mut duplicate = registry.descriptors[0].clone(); + duplicate.id = "game.static_fixture"; + assert!(registry.register(duplicate).is_err()); + } +} diff --git a/crates/editor/src/ui/helpers.rs b/crates/editor/src/ui/helpers.rs index 3c7428e..a1fa265 100644 --- a/crates/editor/src/ui/helpers.rs +++ b/crates/editor/src/ui/helpers.rs @@ -91,6 +91,7 @@ pub fn light_snapshot(name: &str, translation: Vec3) -> EditorEntitySnapshot { primitive: None, brush: None, static_mesh_renderer: None, + skinned_mesh_renderer: None, material: None, material_override: None, rigid_body: None, @@ -109,8 +110,18 @@ pub fn light_snapshot(name: &str, translation: Vec3) -> EditorEntitySnapshot { post_process_volume: None, team_spawn: None, objective: None, + + navigation_bounds: None, + + navigation_obstacle: None, + + navigation_area: None, + + navigation_link: None, hierarchy_sibling_index: 0, editor_visibility: shared::EditorVisibility::default(), + inspector_order: None, + component_states: None, children: Vec::new(), } } @@ -142,6 +153,7 @@ pub fn ensure_player_spawn_for_edit(world: &mut World) -> Entity { primitive: None, brush: None, static_mesh_renderer: None, + skinned_mesh_renderer: None, material: None, material_override: None, rigid_body: None, @@ -160,8 +172,18 @@ pub fn ensure_player_spawn_for_edit(world: &mut World) -> Entity { post_process_volume: None, team_spawn: None, objective: None, + + navigation_bounds: None, + + navigation_obstacle: None, + + navigation_area: None, + + navigation_link: None, hierarchy_sibling_index: 0, editor_visibility: shared::EditorVisibility::default(), + inspector_order: None, + component_states: None, children: Vec::new(), }, ) @@ -233,6 +255,7 @@ pub fn create_scene_sun_override_from_project_settings(world: &mut World) -> Ent primitive: None, brush: None, static_mesh_renderer: None, + skinned_mesh_renderer: None, material: None, material_override: None, rigid_body: None, @@ -251,8 +274,18 @@ pub fn create_scene_sun_override_from_project_settings(world: &mut World) -> Ent post_process_volume: None, team_spawn: None, objective: None, + + navigation_bounds: None, + + navigation_obstacle: None, + + navigation_area: None, + + navigation_link: None, hierarchy_sibling_index: 0, editor_visibility: shared::EditorVisibility::default(), + inspector_order: None, + component_states: None, children: Vec::new(), }, ) diff --git a/crates/editor/src/ui/hierarchy_ops.rs b/crates/editor/src/ui/hierarchy_ops.rs index 3706160..3337b3c 100644 --- a/crates/editor/src/ui/hierarchy_ops.rs +++ b/crates/editor/src/ui/hierarchy_ops.rs @@ -4,6 +4,7 @@ use bevy::prelude::*; use shared::{ ActorKind, AuthoringLightKind, EditorVisibility, HierarchySiblingIndex, HydratedPrefabMember, LevelObject, LightDesc, ModelRef, PhysicsBody, PlayerSpawn, PrefabInstance, + SkinnedMeshRenderer, }; use super::helpers::{entity_name, is_level_object, HierarchyNodeKind}; @@ -203,7 +204,10 @@ fn actor_kind_sort_key(world: &World, entity: Entity) -> u8 { .copied() .map(|kind| match kind { ActorKind::Empty => 0, - ActorKind::Brush | ActorKind::StaticMesh | ActorKind::ImportedModel => 1, + ActorKind::Brush + | ActorKind::StaticMesh + | ActorKind::SkinnedMesh + | ActorKind::ImportedModel => 1, ActorKind::Light => 2, ActorKind::AudioSource | ActorKind::AudioListener => 3, ActorKind::PrefabAnchor => 3, @@ -213,6 +217,7 @@ fn actor_kind_sort_key(world: &World, entity: Entity) -> u8 { ActorKind::PostProcessVolume => 6, ActorKind::TeamSpawn => 7, ActorKind::Objective => 8, + ActorKind::Navigation => 6, }) .unwrap_or(9) } @@ -464,6 +469,7 @@ pub fn actor_kind_icon(kind: ActorKind) -> &'static str { ActorKind::Empty => icons::FOLDER.as_str(), ActorKind::Brush => icons::CUBE.as_str(), ActorKind::StaticMesh => icons::CUBE.as_str(), + ActorKind::SkinnedMesh => icons::PERSON_SIMPLE_RUN.as_str(), ActorKind::ImportedModel => icons::CUBE_TRANSPARENT.as_str(), ActorKind::Light => icons::LIGHTBULB.as_str(), ActorKind::AudioSource => icons::SPEAKER_HIGH.as_str(), @@ -475,6 +481,7 @@ pub fn actor_kind_icon(kind: ActorKind) -> &'static str { ActorKind::PostProcessVolume => icons::CAMERA.as_str(), ActorKind::TeamSpawn => icons::FLAG.as_str(), ActorKind::Objective => icons::TARGET.as_str(), + ActorKind::Navigation => icons::PATH.as_str(), } } @@ -534,6 +541,10 @@ pub fn entity_matches_filter(world: &World, entity: Entity, filter_lower: &str) .is_some() .then_some("spawn"), world.get::(entity).is_some().then_some("model"), + world + .get::(entity) + .is_some() + .then_some("skinned model"), world .get::(entity) .is_some() diff --git a/crates/editor/src/ui/inspector.rs b/crates/editor/src/ui/inspector.rs index 9313c64..cee4923 100644 --- a/crates/editor/src/ui/inspector.rs +++ b/crates/editor/src/ui/inspector.rs @@ -7,30 +7,35 @@ use bevy::prelude::*; use bevy_egui::egui; use egui_phosphor_icons::{icons, Icon}; use shared::{ + authoring_component_active, brush_math::{validate_brush, BrushDiagnosticSeverity}, - infer_actor_kind, inspector_component_active, ActorKind, AnimationControllerDesc, - AudioListenerDesc, AudioSourceDesc, AuthoringLightKind, AuthoringRigidBody, BrushDesc, - BrushKind, ColliderDesc, ColliderShapeDesc, ColorDesc, ComponentInstanceId, EditorAssetRef, + infer_actor_kind, ActorKind, AnimationControllerDesc, AudioListenerDesc, AudioSourceDesc, + AuthoringComponentStates, AuthoringLightKind, AuthoringRigidBody, BrushDesc, BrushKind, + ColliderDesc, ColliderShapeDesc, ColorDesc, ComponentInstanceId, EditorAssetRef, InspectorOrder, LevelObject, LightDesc, MaterialDesc, MaterialParameter, - MaterialParameterValue, MaterialShaderKind, ModelRef, ObjectiveMarker, PhysicsBody, - PlayerSpawn, PostProcessVolumeDesc, PrefabInstance, Primitive, PrimitiveShape, ProjectSun, - RigidBodyDesc, StaticMeshRenderer, StaticMeshRendererEntry, TeamSpawn, TriggerVolume, + MaterialParameterValue, MaterialRef, MaterialShaderKind, NavigationArea, NavigationBounds, + NavigationLink, NavigationObstacle, ObjectiveMarker, PhysicsBody, PlayerSpawn, + PostProcessVolumeDesc, PrefabInstance, Primitive, PrimitiveShape, ProjectSun, RigidBodyDesc, + SkinnedMeshRenderer, StaticMeshRenderer, StaticMeshRendererEntry, TeamSpawn, TriggerVolume, WeaponSpawn, AUTHORING_DIRECTIONAL_LUX_MAX, AUTHORING_POINT_SPOT_LUMENS_MAX, COMPONENT_ANIMATION_CONTROLLER_DESC, COMPONENT_AUDIO_LISTENER_DESC, COMPONENT_AUDIO_SOURCE_DESC, COMPONENT_BRUSH_DESC, COMPONENT_COLLIDER_DESC, - COMPONENT_LIGHT_DESC, COMPONENT_MATERIAL_DESC, COMPONENT_OBJECTIVE_MARKER, - COMPONENT_PHYSICS_BODY, COMPONENT_PLAYER_SPAWN, COMPONENT_POST_PROCESS_VOLUME, - COMPONENT_PREFAB_INSTANCE, COMPONENT_PRIMITIVE, COMPONENT_PROJECT_SUN, - COMPONENT_RIGID_BODY_DESC, COMPONENT_STATIC_MESH_RENDERER, COMPONENT_TEAM_SPAWN, - COMPONENT_TRIGGER_VOLUME, COMPONENT_WEAPON_SPAWN, + COMPONENT_LIGHT_DESC, COMPONENT_MATERIAL_DESC, COMPONENT_NAVIGATION_AREA, + COMPONENT_NAVIGATION_BOUNDS, COMPONENT_NAVIGATION_LINK, COMPONENT_NAVIGATION_OBSTACLE, + COMPONENT_OBJECTIVE_MARKER, COMPONENT_PHYSICS_BODY, COMPONENT_PLAYER_SPAWN, + COMPONENT_POST_PROCESS_VOLUME, COMPONENT_PREFAB_INSTANCE, COMPONENT_PRIMITIVE, + COMPONENT_PROJECT_SUN, COMPONENT_RIGID_BODY_DESC, COMPONENT_SKINNED_MESH_RENDERER, + COMPONENT_STATIC_MESH_RENDERER, COMPONENT_TEAM_SPAWN, COMPONENT_TRIGGER_VOLUME, + COMPONENT_WEAPON_SPAWN, }; use crate::history::{ - material_eq, set_animation_controller_with_history, set_audio_listener_with_history, - set_audio_source_with_history, set_brush_with_history, set_collider_with_history, - set_inspector_order_with_history, set_light_with_history, set_material_with_history, - set_physics_with_history, set_post_process_volume_with_history, set_primitive_with_history, - set_rigid_body_with_history, set_static_mesh_renderer_with_history, EditorEntitySnapshot, + material_eq, reflected_component_transaction, set_animation_controller_with_history, + set_audio_listener_with_history, set_audio_source_with_history, set_brush_with_history, + set_collider_with_history, set_inspector_order_with_history, set_light_with_history, + set_material_with_history, set_physics_with_history, set_post_process_volume_with_history, + set_primitive_with_history, set_rigid_body_with_history, set_static_mesh_renderer_with_history, + EditorEntitySnapshot, }; use crate::selection::SelectedEntity; use crate::ui::theme::{ @@ -87,7 +92,6 @@ struct AssetRefCandidate { detail: String, selection: AssetSelection, folder_path: String, - source_material: Option, texture_id: Option, } @@ -138,6 +142,7 @@ enum AddComponentShelfDirection { #[derive(Debug, Clone)] enum CopiedComponent { + Reflected(crate::history::ReflectedComponentValue), AnimationControllerDesc(AnimationControllerDesc), Primitive(Primitive), BrushDesc(BrushDesc), @@ -155,11 +160,16 @@ enum CopiedComponent { ObjectiveMarker(ObjectiveMarker), PostProcessVolume(PostProcessVolumeDesc), PrefabInstance(PrefabInstance), + NavigationBounds(NavigationBounds), + NavigationObstacle(NavigationObstacle), + NavigationArea(NavigationArea), + NavigationLink(NavigationLink), } impl CopiedComponent { - fn type_name(&self) -> &'static str { + fn type_name(&self) -> &str { match self { + Self::Reflected(value) => &value.type_path, Self::AnimationControllerDesc(_) => COMPONENT_ANIMATION_CONTROLLER_DESC, Self::Primitive(_) => COMPONENT_PRIMITIVE, Self::BrushDesc(_) => COMPONENT_BRUSH_DESC, @@ -177,6 +187,10 @@ impl CopiedComponent { Self::ObjectiveMarker(_) => COMPONENT_OBJECTIVE_MARKER, Self::PostProcessVolume(_) => COMPONENT_POST_PROCESS_VOLUME, Self::PrefabInstance(_) => COMPONENT_PREFAB_INSTANCE, + Self::NavigationBounds(_) => COMPONENT_NAVIGATION_BOUNDS, + Self::NavigationObstacle(_) => COMPONENT_NAVIGATION_OBSTACLE, + Self::NavigationArea(_) => COMPONENT_NAVIGATION_AREA, + Self::NavigationLink(_) => COMPONENT_NAVIGATION_LINK, } } } @@ -251,9 +265,13 @@ pub(crate) fn component_card_context( ) -> ComponentCardContext { let present = present_component_type_names(world, entity); let order = world.get::(entity); - let active = order - .map(|order| order.is_component_active(options.type_name)) - .unwrap_or(true); + let registry = world.resource::(); + let state_key = registry.stable_id_for_type(options.type_name); + let active = authoring_component_active( + world.get::(entity), + order, + state_key, + ); let collapsed = world .get_resource::() .map(|state| { @@ -268,7 +286,7 @@ pub(crate) fn component_card_context( .and_then(|clipboard| clipboard.component.as_ref()) .is_some_and(|component| component.type_name() == options.type_name); let ordered = order - .map(|order| order.ordered_components(&present)) + .map(|order| registry.ordered_components(order, &present)) .unwrap_or_else(|| present.clone()); let order_index = ordered .iter() @@ -315,14 +333,30 @@ fn apply_component_card_response_for_type( } if let Some(active) = response.active { - let mut order = world - .get::(entity) + let mut states = world + .get::(entity) .cloned() - .unwrap_or_default(); - let present = present_component_type_names(world, entity); - order.ensure_component_order(&present); - order.set_component_active(type_name, active); - set_inspector_order_with_history(world, entity, order); + .unwrap_or_else(|| AuthoringComponentStates { + states: world + .get::(entity) + .map(|order| order.component_states.clone()) + .unwrap_or_default(), + }); + let component_id = world + .resource::() + .stable_id_for_type(type_name); + states.set_component_active(component_id, active); + let _ = crate::history::reflected_component_transaction( + world, + entity, + "Set Component Active", + "editor.component_states", + "shared::components::AuthoringComponentStates", + move |world, entity| { + world.entity_mut(entity).insert(states); + Ok(()) + }, + ); } if response.move_up || response.move_down { @@ -332,7 +366,10 @@ fn apply_component_card_response_for_type( .unwrap_or_default(); let present = present_component_type_names(world, entity); let offset = if response.move_up { -1 } else { 1 }; - if order.move_component(type_name, offset, &present) { + let moved = world + .resource::() + .move_component(&mut order, type_name, offset, &present); + if moved { set_inspector_order_with_history(world, entity, order); } } @@ -366,6 +403,17 @@ fn component_card_key(world: &World, entity: Entity, type_name: &str) -> String } fn present_component_type_names(world: &World, entity: Entity) -> Vec<&'static str> { + if let Some(registry) = world.get_resource::() { + return registry + .descriptors + .iter() + .filter(|descriptor| { + !descriptor.hidden + && registry.component_present(world, entity, descriptor.type_name) + }) + .map(|descriptor| descriptor.type_name) + .collect(); + } let mut present = Vec::new(); if world.get::(entity).is_some() { present.push(COMPONENT_ANIMATION_CONTROLLER_DESC); @@ -373,6 +421,9 @@ fn present_component_type_names(world: &World, entity: Entity) -> Vec<&'static s if world.get::(entity).is_some() { present.push(COMPONENT_STATIC_MESH_RENDERER); } + if world.get::(entity).is_some() { + present.push(COMPONENT_SKINNED_MESH_RENDERER); + } if world.get::(entity).is_some() { present.push(COMPONENT_PRIMITIVE); } @@ -424,10 +475,38 @@ fn present_component_type_names(world: &World, entity: Entity) -> Vec<&'static s if world.get::(entity).is_some() { present.push(COMPONENT_PROJECT_SUN); } + if world.get::(entity).is_some() { + present.push(COMPONENT_NAVIGATION_BOUNDS); + } + if world.get::(entity).is_some() { + present.push(COMPONENT_NAVIGATION_OBSTACLE); + } + if world.get::(entity).is_some() { + present.push(COMPONENT_NAVIGATION_AREA); + } + if world.get::(entity).is_some() { + present.push(COMPONENT_NAVIGATION_LINK); + } present } fn copy_component(world: &mut World, entity: Entity, type_name: &str) { + let descriptor = world + .resource::() + .by_type_name(type_name) + .cloned(); + if let Some(descriptor) = descriptor { + if let Ok(Some(value)) = crate::history::capture_reflected_component( + world, + entity, + descriptor.id, + descriptor.type_name, + ) { + world.resource_mut::().component = + Some(CopiedComponent::Reflected(value)); + return; + } + } let component = match type_name { COMPONENT_ANIMATION_CONTROLLER_DESC => world .get::(entity) @@ -497,6 +576,22 @@ fn copy_component(world: &mut World, entity: Entity, type_name: &str) { .get::(entity) .cloned() .map(CopiedComponent::PrefabInstance), + COMPONENT_NAVIGATION_BOUNDS => world + .get::(entity) + .cloned() + .map(CopiedComponent::NavigationBounds), + COMPONENT_NAVIGATION_OBSTACLE => world + .get::(entity) + .cloned() + .map(CopiedComponent::NavigationObstacle), + COMPONENT_NAVIGATION_AREA => world + .get::(entity) + .cloned() + .map(CopiedComponent::NavigationArea), + COMPONENT_NAVIGATION_LINK => world + .get::(entity) + .cloned() + .map(CopiedComponent::NavigationLink), _ => None, }; if let Some(component) = component { @@ -508,7 +603,30 @@ fn paste_component(world: &mut World, entity: Entity, type_name: &str) { let component = world .get_resource::() .and_then(|clipboard| clipboard.component.clone()); + if let Some(CopiedComponent::Reflected(value)) = component.as_ref() { + if value.type_path == type_name { + let value = value.clone(); + let component_id = value.component_id.clone(); + let _ = crate::history::reflected_component_transaction( + world, + entity, + "Paste Component", + &component_id, + type_name, + move |world, entity| { + crate::history::apply_reflected_component( + world, + entity, + type_name, + Some(&value), + ) + }, + ); + } + return; + } match component { + Some(CopiedComponent::Reflected(_)) => {} Some(CopiedComponent::AnimationControllerDesc(value)) if type_name == COMPONENT_ANIMATION_CONTROLLER_DESC => { @@ -572,11 +690,90 @@ fn paste_component(world: &mut World, entity: Entity, type_name: &str) { Some(CopiedComponent::PrefabInstance(value)) if type_name == COMPONENT_PREFAB_INSTANCE => { insert_direct_component(world, entity, value); } + Some(CopiedComponent::NavigationBounds(value)) + if type_name == COMPONENT_NAVIGATION_BOUNDS => + { + crate::history::set_navigation_with_history( + world, + entity, + crate::history::NavigationComponentState { + bounds: Some(value), + ..Default::default() + }, + ); + } + Some(CopiedComponent::NavigationObstacle(value)) + if type_name == COMPONENT_NAVIGATION_OBSTACLE => + { + crate::history::set_navigation_with_history( + world, + entity, + crate::history::NavigationComponentState { + obstacle: Some(value), + ..Default::default() + }, + ); + } + Some(CopiedComponent::NavigationArea(value)) if type_name == COMPONENT_NAVIGATION_AREA => { + crate::history::set_navigation_with_history( + world, + entity, + crate::history::NavigationComponentState { + area: Some(value), + ..Default::default() + }, + ); + } + Some(CopiedComponent::NavigationLink(value)) if type_name == COMPONENT_NAVIGATION_LINK => { + crate::history::set_navigation_with_history( + world, + entity, + crate::history::NavigationComponentState { + link: Some(value), + ..Default::default() + }, + ); + } _ => {} } } fn reset_component(world: &mut World, entity: Entity, type_name: &str) { + let descriptor = world + .resource::() + .by_type_name(type_name) + .cloned(); + if let Some(descriptor) = descriptor { + let component_id = descriptor.id; + let type_path = descriptor.type_name; + if type_path == COMPONENT_ANIMATION_CONTROLLER_DESC { + let controller = + crate::ui::animation_inspector::default_controller_for_actor(world, entity); + let _ = crate::history::reflected_component_transaction( + world, + entity, + "Reset Component", + component_id, + type_path, + move |world, entity| { + world.entity_mut(entity).insert(controller); + Ok(()) + }, + ); + } else { + let _ = crate::history::reflected_component_transaction( + world, + entity, + "Reset Component", + component_id, + type_path, + move |world, entity| { + crate::history::apply_reflected_default(world, entity, type_path) + }, + ); + } + return; + } match type_name { COMPONENT_ANIMATION_CONTROLLER_DESC => { let controller = @@ -608,6 +805,38 @@ fn reset_component(world: &mut World, entity: Entity, type_name: &str) { COMPONENT_POST_PROCESS_VOLUME => { set_post_process_volume_with_history(world, entity, PostProcessVolumeDesc::default()); } + COMPONENT_NAVIGATION_BOUNDS => crate::history::set_navigation_with_history( + world, + entity, + crate::history::NavigationComponentState { + bounds: Some(NavigationBounds::default()), + ..Default::default() + }, + ), + COMPONENT_NAVIGATION_OBSTACLE => crate::history::set_navigation_with_history( + world, + entity, + crate::history::NavigationComponentState { + obstacle: Some(NavigationObstacle::default()), + ..Default::default() + }, + ), + COMPONENT_NAVIGATION_AREA => crate::history::set_navigation_with_history( + world, + entity, + crate::history::NavigationComponentState { + area: Some(NavigationArea::default()), + ..Default::default() + }, + ), + COMPONENT_NAVIGATION_LINK => crate::history::set_navigation_with_history( + world, + entity, + crate::history::NavigationComponentState { + link: Some(NavigationLink::default()), + ..Default::default() + }, + ), COMPONENT_WEAPON_SPAWN => insert_direct_component( world, entity, @@ -640,6 +869,44 @@ fn insert_direct_component(world: &mut World, entity: Entity, comp } fn remove_registered_component(world: &mut World, entity: Entity, type_name: &str) { + let descriptor = world + .resource::() + .by_type_name(type_name) + .cloned(); + if let Some(descriptor) = descriptor { + if !descriptor.removable { + return; + } + let dependents = world + .resource::() + .present_dependents(world, entity, descriptor.id) + .iter() + .map(|dependent| dependent.display_name) + .collect::>(); + if !dependents.is_empty() { + world + .resource_mut::() + .set_status(format!( + "Remove blocked: required by {}. Remove dependent components first.", + dependents.join(", ") + )); + return; + } + let result = crate::history::reflected_component_transaction( + world, + entity, + "Remove Component", + descriptor.id, + descriptor.type_name, + |world, entity| { + crate::history::apply_reflected_component(world, entity, descriptor.type_name, None) + }, + ); + if result.is_ok() && type_name == COMPONENT_ANIMATION_CONTROLLER_DESC { + crate::ui::animation_inspector::stop_preview_if_actor(world, entity); + } + return; + } let Some(before) = crate::history::snapshot_entity(world, entity) else { return; }; @@ -648,6 +915,16 @@ fn remove_registered_component(world: &mut World, entity: Entity, type_name: &st (COMPONENT_AUDIO_SOURCE_DESC, ActorKind::AudioSource) | (COMPONENT_AUDIO_LISTENER_DESC, ActorKind::AudioListener) ); + let removed_dedicated_navigation_kind = before.actor_kind == ActorKind::Navigation + && matches!( + type_name, + COMPONENT_NAVIGATION_BOUNDS + | COMPONENT_NAVIGATION_OBSTACLE + | COMPONENT_NAVIGATION_AREA + | COMPONENT_NAVIGATION_LINK + ); + let removed_dedicated_skinned_kind = + type_name == COMPONENT_SKINNED_MESH_RENDERER && before.actor_kind == ActorKind::SkinnedMesh; let removed = if let Ok(mut entity_mut) = world.get_entity_mut(entity) { match type_name { COMPONENT_ANIMATION_CONTROLLER_DESC if before.animation_controller.is_some() => { @@ -666,6 +943,10 @@ fn remove_registered_component(world: &mut World, entity: Entity, type_name: &st entity_mut.remove::(); true } + COMPONENT_SKINNED_MESH_RENDERER if before.skinned_mesh_renderer.is_some() => { + entity_mut.remove::(); + true + } COMPONENT_MATERIAL_DESC if before.material.is_some() => { entity_mut.remove::(); true @@ -722,6 +1003,22 @@ fn remove_registered_component(world: &mut World, entity: Entity, type_name: &st entity_mut.remove::(); true } + COMPONENT_NAVIGATION_BOUNDS if before.navigation_bounds.is_some() => { + entity_mut.remove::(); + true + } + COMPONENT_NAVIGATION_OBSTACLE if before.navigation_obstacle.is_some() => { + entity_mut.remove::(); + true + } + COMPONENT_NAVIGATION_AREA if before.navigation_area.is_some() => { + entity_mut.remove::(); + true + } + COMPONENT_NAVIGATION_LINK if before.navigation_link.is_some() => { + entity_mut.remove::(); + true + } _ => false, } } else { @@ -733,7 +1030,10 @@ fn remove_registered_component(world: &mut World, entity: Entity, type_name: &st if type_name == COMPONENT_ANIMATION_CONTROLLER_DESC { crate::ui::animation_inspector::stop_preview_if_actor(world, entity); } - if removed_dedicated_audio_kind { + if removed_dedicated_audio_kind + || removed_dedicated_navigation_kind + || removed_dedicated_skinned_kind + { let fallback = world .get_entity(entity) .ok() @@ -761,10 +1061,13 @@ pub fn authoring_inspector_ui(world: &mut World, ui: &mut egui::Ui, entity: Enti } let present = present_component_type_names(world, entity); - let ordered = world - .get::(entity) - .map(|order| order.ordered_components(&present)) - .unwrap_or(present); + let ordered = if let Some(order) = world.get::(entity) { + world + .resource::() + .ordered_components(order, &present) + } else { + present + }; for type_name in ordered { draw_authoring_component_by_type(world, ui, entity, type_name); } @@ -776,11 +1079,19 @@ fn draw_authoring_component_by_type( entity: Entity, type_name: &'static str, ) { + if let Some(inspector) = world + .resource::() + .inspector(type_name) + { + inspector(world, ui, entity); + return; + } match type_name { COMPONENT_ANIMATION_CONTROLLER_DESC => { crate::ui::animation_inspector::animation_controller_inspector_ui(world, ui, entity); } COMPONENT_STATIC_MESH_RENDERER => static_mesh_renderer_ui(world, ui, entity), + COMPONENT_SKINNED_MESH_RENDERER => skinned_mesh_renderer_ui(world, ui, entity), COMPONENT_BRUSH_DESC => brush_editor_ui(world, ui, entity), COMPONENT_PRIMITIVE => primitive_editor_ui(world, ui, entity), COMPONENT_MATERIAL_DESC => material_editor_ui(world, ui, entity), @@ -804,6 +1115,18 @@ fn draw_authoring_component_by_type( crate::ui::post_process_volume_ui::post_process_volume_inspector_ui(world, ui, entity); } COMPONENT_PROJECT_SUN => project_sun_ui(world, ui, entity), + COMPONENT_NAVIGATION_BOUNDS => { + super::navigation_inspector::navigation_bounds_inspector_ui(world, ui, entity) + } + COMPONENT_NAVIGATION_OBSTACLE => { + super::navigation_inspector::navigation_obstacle_inspector_ui(world, ui, entity) + } + COMPONENT_NAVIGATION_AREA => { + super::navigation_inspector::navigation_area_inspector_ui(world, ui, entity) + } + COMPONENT_NAVIGATION_LINK => { + super::navigation_inspector::navigation_link_inspector_ui(world, ui, entity) + } _ => {} } } @@ -1080,6 +1403,7 @@ fn filtered_component_descriptors<'a>( EditorComponentCategory::Rendering, EditorComponentCategory::Animation, EditorComponentCategory::Audio, + EditorComponentCategory::Navigation, EditorComponentCategory::Physics, EditorComponentCategory::Gameplay, EditorComponentCategory::Volumes, @@ -1102,6 +1426,7 @@ fn component_category_label(category: EditorComponentCategory) -> &'static str { EditorComponentCategory::Rendering => "Rendering", EditorComponentCategory::Animation => "Animation", EditorComponentCategory::Audio => "Audio", + EditorComponentCategory::Navigation => "Navigation", EditorComponentCategory::Physics => "Physics", EditorComponentCategory::Gameplay => "Gameplay", EditorComponentCategory::Volumes => "Volumes", @@ -1112,6 +1437,7 @@ fn component_category_label(category: EditorComponentCategory) -> &'static str { struct ComponentAddState { addable: bool, reason: Option, + required: Vec<&'static str>, recommended: Vec<&'static str>, conflicts: Vec<&'static str>, } @@ -1146,15 +1472,18 @@ fn component_add_state( .copied() .filter(|type_name| !component_present(world, entity, type_name)) .collect::>(); - let animation_target_missing = descriptor.type_name == COMPONENT_ANIMATION_CONTROLLER_DESC - && (!world - .get::(entity) - .is_some_and(|kind| *kind == ActorKind::ImportedModel) - || world.get::(entity).is_none()); + let registry = world.resource::(); + let required = registry + .required_component_ids(descriptor) + .iter() + .filter_map(|id| registry.by_id(id)) + .filter(|required| !registry.component_present(world, entity, required.type_name)) + .map(|required| required.display_name) + .collect::>(); let reason = if duplicate { Some("Already present on this actor.".to_string()) - } else if animation_target_missing { - Some("Animation Controller requires an ImportedModel actor with ModelRef.".to_string()) + } else if !required.is_empty() { + Some(format!("Requires {}.", required.join(", "))) } else if !conflicts.is_empty() { Some(format!( "Conflicts with {}.", @@ -1170,6 +1499,7 @@ fn component_add_state( ComponentAddState { addable: reason.is_none(), reason, + required, recommended, conflicts, } @@ -1191,6 +1521,9 @@ fn component_hover_text( if let Some(reason) = &state.reason { lines.push(format!("Unavailable: {reason}")); } + if !state.required.is_empty() { + lines.push(format!("Requires: {}", state.required.join(", "))); + } if !state.recommended.is_empty() { lines.push(format!( "Recommended with: {}", @@ -1216,6 +1549,11 @@ fn component_display_name( } fn component_present(world: &World, entity: Entity, type_name: &str) -> bool { + if let Some(registry) = world.get_resource::() { + if registry.by_type_name(type_name).is_some() { + return registry.component_present(world, entity, type_name); + } + } match type_name { COMPONENT_ANIMATION_CONTROLLER_DESC => { world.get::(entity).is_some() @@ -1225,6 +1563,7 @@ fn component_present(world: &World, entity: Entity, type_name: &str) -> bool { "shared::components::StaticMeshRenderer" => { world.get::(entity).is_some() } + COMPONENT_SKINNED_MESH_RENDERER => world.get::(entity).is_some(), "shared::components::MaterialDesc" => world.get::(entity).is_some(), "shared::components::LightDesc" => world.get::(entity).is_some(), "shared::components::AudioSourceDesc" => world.get::(entity).is_some(), @@ -1239,11 +1578,51 @@ fn component_present(world: &World, entity: Entity, type_name: &str) -> bool { "shared::components::PostProcessVolumeDesc" => { world.get::(entity).is_some() } + COMPONENT_NAVIGATION_BOUNDS => world.get::(entity).is_some(), + COMPONENT_NAVIGATION_OBSTACLE => world.get::(entity).is_some(), + COMPONENT_NAVIGATION_AREA => world.get::(entity).is_some(), + COMPONENT_NAVIGATION_LINK => world.get::(entity).is_some(), _ => false, } } fn insert_registered_component(world: &mut World, entity: Entity, type_name: &str) { + let descriptor = world + .resource::() + .by_type_name(type_name) + .cloned(); + if let Some(descriptor) = descriptor { + if !descriptor.addable || component_present(world, entity, descriptor.type_name) { + return; + } + if descriptor.type_name == COMPONENT_ANIMATION_CONTROLLER_DESC { + let controller = + crate::ui::animation_inspector::default_controller_for_actor(world, entity); + let _ = crate::history::reflected_component_transaction( + world, + entity, + "Add Component", + descriptor.id, + descriptor.type_name, + move |world, entity| { + world.entity_mut(entity).insert(controller); + Ok(()) + }, + ); + } else { + let _ = crate::history::reflected_component_transaction( + world, + entity, + "Add Component", + descriptor.id, + descriptor.type_name, + move |world, entity| { + crate::history::apply_reflected_default(world, entity, descriptor.type_name) + }, + ); + } + return; + } match type_name { COMPONENT_ANIMATION_CONTROLLER_DESC => { let controller = @@ -1309,6 +1688,26 @@ fn insert_registered_component(world: &mut World, entity: Entity, type_name: &st objective_id: "objective".into(), }); }), + COMPONENT_NAVIGATION_BOUNDS => insert_component(world, entity, |world, e| { + world + .entity_mut(e) + .insert((ActorKind::Navigation, NavigationBounds::default())); + }), + COMPONENT_NAVIGATION_OBSTACLE => insert_component(world, entity, |world, e| { + world + .entity_mut(e) + .insert((ActorKind::Navigation, NavigationObstacle::default())); + }), + COMPONENT_NAVIGATION_AREA => insert_component(world, entity, |world, e| { + world + .entity_mut(e) + .insert((ActorKind::Navigation, NavigationArea::default())); + }), + COMPONENT_NAVIGATION_LINK => insert_component(world, entity, |world, e| { + world + .entity_mut(e) + .insert((ActorKind::Navigation, NavigationLink::default())); + }), "shared::components::PostProcessVolumeDesc" => { insert_component(world, entity, |world, e| { world.entity_mut(e).insert(PostProcessVolumeDesc::default()); @@ -1355,6 +1754,10 @@ fn diff_added(before: &EditorEntitySnapshot, after: &EditorEntitySnapshot) -> Ed .static_mesh_renderer .clone() .filter(|_| before.static_mesh_renderer.is_none()), + skinned_mesh_renderer: after + .skinned_mesh_renderer + .clone() + .filter(|_| before.skinned_mesh_renderer.is_none()), material: after.material.clone().filter(|_| before.material.is_none()), material_override: after .material_override @@ -1402,8 +1805,26 @@ fn diff_added(before: &EditorEntitySnapshot, after: &EditorEntitySnapshot) -> Ed .objective .clone() .filter(|_| before.objective.is_none()), + navigation_bounds: after + .navigation_bounds + .clone() + .filter(|_| before.navigation_bounds.is_none()), + navigation_obstacle: after + .navigation_obstacle + .clone() + .filter(|_| before.navigation_obstacle.is_none()), + navigation_area: after + .navigation_area + .clone() + .filter(|_| before.navigation_area.is_none()), + navigation_link: after + .navigation_link + .clone() + .filter(|_| before.navigation_link.is_none()), hierarchy_sibling_index: after.hierarchy_sibling_index, editor_visibility: after.editor_visibility, + inspector_order: None, + component_states: None, children: Vec::new(), } } @@ -1459,7 +1880,6 @@ fn static_mesh_asset_ref_candidates( detail: format!("{} | {}", manifest.label, manifest.source.path), selection: selection.clone(), folder_path: folder_path.clone(), - source_material: source_material_ref, texture_id, } } @@ -1473,7 +1893,6 @@ fn static_mesh_asset_ref_candidates( detail: format!("{} | {}", manifest.label, manifest.source.path), selection: selection.clone(), folder_path: folder_path.clone(), - source_material: None, texture_id: None, } } @@ -1551,7 +1970,6 @@ fn texture_asset_ref_candidates(world: &World) -> Vec { detail: path.to_string(), selection: AssetSelection::File(path.to_string()), folder_path: asset.folder_path.clone(), - source_material: None, texture_id: snapshot .as_ref() .and_then(|snapshot| snapshot.texture_for(asset)), @@ -1590,7 +2008,12 @@ fn brush_face_material_ref_candidates(world: &mut World) -> Vec Vec(entity).cloned() else { + return; + }; + let original = renderer.clone(); + let mut changed = false; + let mut options = ComponentCardOptions::removable( + COMPONENT_SKINNED_MESH_RENDERER, + "Skinned Mesh Renderer", + icons::PERSON_SIMPLE_RUN, + ); + options.active_toggle = false; + options.removable = world.get::(entity).is_none(); + options.resettable = false; + options.copyable = false; + let context = component_card_context(world, entity, options); + let response = component_card(ui, &context, |ui| { + property_row(ui, "Source", |ui| { + ui.add( + egui::Label::new(if renderer.path.trim().is_empty() { + "Unassigned" + } else { + renderer.path.as_str() + }) + .truncate(), + ); + }); + property_row(ui, "Scene", |ui| { + ui.label(renderer.scene_index.to_string()); + }); + property_row(ui, "Asset ID", |ui| { + ui.add( + egui::Label::new(if renderer.asset_id.trim().is_empty() { + "Legacy/path-only" + } else { + renderer.asset_id.as_str() + }) + .truncate(), + ); + }); + ui.label( + egui::RichText::new( + "Preserves the imported skeleton hierarchy and Bevy skinned-mesh bindings.", + ) + .small() + .color(TEXT_DIM), + ); + ui.add_space(8.0); + ui.label(egui::RichText::new("Material slots").strong()); + if renderer.materials.slots.is_empty() { + ui.label( + egui::RichText::new("No imported slots; reimport the source model") + .small() + .color(TEXT_DIM), + ); + } + for slot in &mut renderer.materials.slots { + slot_card(ui, |ui| { + ui.label(egui::RichText::new(&slot.name).strong()); + let response = asset_selector_row( + ui, + "Material", + icons::PALETTE, + slot.material.as_ref().map(|reference| &reference.0), + slot.source_material.as_ref().map(|reference| &reference.0), + true, + &material_candidates, + None, + ); + if let Some(selected) = response.selected { + slot.material = Some(MaterialRef::new(selected)); + changed = true; + } + if response.clear { + slot.material = None; + changed = true; + } + if response.locate { + let reference = slot + .material + .as_ref() + .or(slot.source_material.as_ref()) + .map(|reference| &reference.0); + locate_asset_ref(world, reference, &material_candidates); + } + ui.label( + egui::RichText::new(format!("ID: {}", slot.id.0)) + .small() + .color(TEXT_DIM), + ); + }); + } + for orphan in &renderer.materials.orphaned_assignments { + ui.label( + egui::RichText::new(format!( + "Orphaned: {} ({})", + orphan.last_known_name, orphan.slot_id.0 + )) + .small() + .color(egui::Color32::YELLOW), + ); + } + if world.get::(entity).is_some() { + ui.label( + egui::RichText::new( + "Remove the Animation Controller before removing its renderer.", + ) + .small() + .color(TEXT_MUTED), + ); + } + }); + apply_component_card_response(world, entity, response); + if changed && renderer != original { + let result = reflected_component_transaction( + world, + entity, + "Assign Skinned Material Slot", + shared::AUTHORING_COMPONENT_SKINNED_MESH_RENDERER, + COMPONENT_SKINNED_MESH_RENDERER, + move |world, entity| { + world.entity_mut(entity).insert(renderer); + Ok(()) + }, + ); + if let Err(error) = result { + world.resource_mut::().status = error; + } + } +} + fn static_mesh_renderer_ui(world: &mut World, ui: &mut egui::Ui, entity: Entity) { let mesh_candidates = static_mesh_asset_ref_candidates(world, AssetRefCandidateKind::Mesh); - let material_candidates = - static_mesh_asset_ref_candidates(world, AssetRefCandidateKind::Material); + let material_candidates = brush_face_material_ref_candidates(world); let Some(mut renderer) = world.get::(entity).cloned() else { return; }; let original = renderer.clone(); let mut changed = false; let mut remove_entry = None; + for index in 0..renderer.slots.len() { + ensure_slot_id(&mut renderer.slots[index], index); + let part = &renderer.slots[index]; + if renderer.materials.slot(&part.material_slot_id).is_none() { + renderer.materials.slots.push(shared::RendererMaterialSlot { + id: part.material_slot_id.clone(), + name: part.name.clone(), + source_material: part.material.clone().map(MaterialRef::new), + material: None, + }); + } + } let card = component_card_context( world, @@ -1932,11 +2498,11 @@ fn static_mesh_renderer_ui(world: &mut World, ui: &mut egui::Ui, entity: Entity) for (index, entry) in renderer.slots.iter_mut().enumerate() { ensure_slot_id(entry, index); - let inherited_material = source_material_for_mesh(&entry.mesh, &mesh_candidates); - let material_for_locate = entry - .material - .clone() - .or_else(|| inherited_material.clone()); + let material_slot_name = renderer + .materials + .slot(&entry.material_slot_id) + .map(|slot| slot.name.clone()) + .unwrap_or_else(|| "Missing slot".into()); let thumbnail = thumbnail_for_mesh(&entry.mesh, &mesh_candidates); slot_card(ui, |ui| { @@ -1976,27 +2542,9 @@ fn static_mesh_renderer_ui(world: &mut World, ui: &mut egui::Ui, entity: Entity) locate_asset_ref(world, Some(&entry.mesh), &mesh_candidates); } - let material_response = asset_selector_row( - ui, - "Source material", - icons::PALETTE, - entry.material.as_ref(), - inherited_material.as_ref(), - true, - &material_candidates, - None, - ); - if let Some(selected) = material_response.selected { - entry.material = Some(selected); - changed = true; - } - if material_response.clear { - entry.material = None; - changed = true; - } - if material_response.locate { - locate_asset_ref(world, material_for_locate.as_ref(), &material_candidates); - } + property_row(ui, "Material slot", |ui| { + ui.label(&material_slot_name); + }); ui.horizontal_wrapped(|ui| { changed |= ui.checkbox(&mut entry.visible, "Visible").changed(); changed |= ui @@ -2025,6 +2573,45 @@ fn static_mesh_renderer_ui(world: &mut World, ui: &mut egui::Ui, entity: Entity) }); } + ui.add_space(8.0); + ui.label(egui::RichText::new("Material slots").strong()); + for slot in &mut renderer.materials.slots { + slot_card(ui, |ui| { + ui.label(egui::RichText::new(&slot.name).strong()); + let response = asset_selector_row( + ui, + "Material", + icons::PALETTE, + slot.material.as_ref().map(|reference| &reference.0), + slot.source_material.as_ref().map(|reference| &reference.0), + true, + &material_candidates, + None, + ); + if let Some(selected) = response.selected { + slot.material = Some(MaterialRef::new(selected)); + changed = true; + } + if response.clear { + slot.material = None; + changed = true; + } + if response.locate { + let reference = slot + .material + .as_ref() + .or(slot.source_material.as_ref()) + .map(|reference| &reference.0); + locate_asset_ref(world, reference, &material_candidates); + } + ui.label( + egui::RichText::new(format!("ID: {}", slot.id.0)) + .small() + .color(TEXT_DIM), + ); + }); + } + let add_slot = ui .add_sized( [ui.available_width().max(1.0), 28.0], @@ -2035,8 +2622,15 @@ fn static_mesh_renderer_ui(world: &mut World, ui: &mut egui::Ui, entity: Entity) let index = renderer.slots.len(); let entry = StaticMeshRendererEntry { id: ComponentInstanceId::new(format!("slot:{index}")), + material_slot_id: ComponentInstanceId::new(format!("slot:manual:{index}")), ..Default::default() }; + renderer.materials.slots.push(shared::RendererMaterialSlot { + id: entry.material_slot_id.clone(), + name: format!("Material {index}"), + source_material: None, + material: None, + }); renderer.slots.push(entry); changed = true; } @@ -2044,7 +2638,25 @@ fn static_mesh_renderer_ui(world: &mut World, ui: &mut egui::Ui, entity: Entity) apply_component_card_response(world, entity, card_response); if let Some(index) = remove_entry { - renderer.slots.remove(index); + let removed = renderer.slots.remove(index); + if let Some(slot_index) = renderer + .materials + .slots + .iter() + .position(|slot| slot.id == removed.material_slot_id) + { + let slot = renderer.materials.slots.remove(slot_index); + if let Some(material) = slot.material { + renderer + .materials + .orphaned_assignments + .push(shared::OrphanedMaterialAssignment { + slot_id: slot.id, + last_known_name: slot.name, + material, + }); + } + } changed = true; } if changed && renderer != original { @@ -2056,6 +2668,9 @@ fn ensure_slot_id(entry: &mut StaticMeshRendererEntry, index: usize) { if entry.id.is_empty() { entry.id = ComponentInstanceId::new(format!("slot:{index}")); } + if entry.material_slot_id.is_empty() { + entry.material_slot_id = ComponentInstanceId::new(format!("slot:{}", entry.id.0)); + } } fn slot_card(ui: &mut egui::Ui, add_contents: impl FnOnce(&mut egui::Ui)) { @@ -2142,19 +2757,6 @@ fn slot_thumbnail(ui: &mut egui::Ui, texture_id: Option) { } } -fn source_material_for_mesh( - mesh: &EditorAssetRef, - candidates: &[AssetRefCandidate], -) -> Option { - candidates - .iter() - .find(|candidate| { - candidate.reference.asset_id == mesh.asset_id - && candidate.reference.sub_asset_id == mesh.sub_asset_id - }) - .and_then(|candidate| candidate.source_material.clone()) -} - fn thumbnail_for_mesh( mesh: &EditorAssetRef, candidates: &[AssetRefCandidate], @@ -3588,10 +4190,14 @@ pub fn project_sun_ui(world: &mut World, ui: &mut egui::Ui, entity: Entity) { fn has_scene_sun_override(world: &mut World) -> bool { world - .query_filtered::<(&shared::LightDesc, Option<&InspectorOrder>), With>() + .query_filtered::<( + &shared::LightDesc, + Option<&AuthoringComponentStates>, + Option<&InspectorOrder>, + ), With>() .iter(world) - .any(|(light, order)| { - inspector_component_active(order, COMPONENT_LIGHT_DESC) + .any(|(light, states, legacy_order)| { + authoring_component_active(states, legacy_order, COMPONENT_LIGHT_DESC) && matches!(light.kind, shared::AuthoringLightKind::Directional) }) } diff --git a/crates/editor/src/ui/menu.rs b/crates/editor/src/ui/menu.rs index 8619c93..822f039 100644 --- a/crates/editor/src/ui/menu.rs +++ b/crates/editor/src/ui/menu.rs @@ -158,6 +158,44 @@ pub fn top_menu_bar( crate::rendering_diagnostics::spawn_post_process_volume_at_camera(world); ui.close(); } + ui.separator(); + ui.menu_button("Navigation", |ui| { + use super::navigation_inspector::{ + spawn_navigation_actor, NavigationActorType, + }; + for (label, actor_type) in [ + ("Create Bounds", NavigationActorType::Bounds), + ("Create Obstacle", NavigationActorType::Obstacle), + ("Create Area", NavigationActorType::Area), + ("Create Link", NavigationActorType::Link), + ] { + if menu_item(ui, label, None, true).clicked() { + spawn_navigation_actor(world, actor_type); + ui.close(); + } + } + ui.separator(); + let selected_bounds = + selected.as_slice().iter().copied().find(|entity| { + world.get::(*entity).is_some() + }); + if menu_item(ui, "Bake Selected Bounds", None, selected_bounds.is_some()) + .clicked() + { + if let Some(entity) = selected_bounds { + super::navigation_inspector::bake_bounds(world, entity); + } + ui.close(); + } + if menu_item(ui, "Test Selected Path", None, selected_bounds.is_some()) + .clicked() + { + if let Some(entity) = selected_bounds { + super::navigation_inspector::query_preview(world, entity); + } + ui.close(); + } + }); }); ui.menu_button("Build", |ui| { diff --git a/crates/editor/src/ui/mod.rs b/crates/editor/src/ui/mod.rs index 290e48a..422fb3d 100644 --- a/crates/editor/src/ui/mod.rs +++ b/crates/editor/src/ui/mod.rs @@ -3,7 +3,7 @@ mod animation_inspector; mod asset_browser; mod audio_inspector; mod build; -mod component_registry; +pub mod component_registry; mod diagnostics; mod dock_tabs; mod fonts; @@ -14,6 +14,7 @@ pub mod hierarchy_state; pub(crate) mod inspector; mod layout; mod menu; +pub(crate) mod navigation_inspector; mod play_controls; mod post_process_volume_ui; mod scene_tabs; @@ -294,6 +295,7 @@ impl Plugin for EditorUiPlugin { .init_resource::() .init_resource::() .init_resource::() + .init_resource::() .init_resource::() .init_resource::() .add_systems( @@ -301,6 +303,7 @@ impl Plugin for EditorUiPlugin { init_hierarchy_panel_from_prefs .after(crate::project_io::load_user_preferences_on_startup), ) + .add_systems(Startup, validate_editor_component_registry) .add_systems( Startup, apply_ui_state_from_prefs @@ -312,11 +315,25 @@ impl Plugin for EditorUiPlugin { ( hierarchy_state::tick_hierarchy_prefs_save, build::tick_build_job, + navigation_inspector::sync_navigation_state, ), ); } } +fn validate_editor_component_registry(world: &mut World) { + let errors = world + .resource::() + .validate(world) + .err(); + if let Some(errors) = errors { + panic!( + "invalid authoring component registry:\n{}", + errors.join("\n") + ); + } +} + fn apply_ui_state_from_prefs(mut ui_state: ResMut, prefs: Res) { *ui_state = UiState::from_prefs(&prefs); } diff --git a/crates/editor/src/ui/navigation_inspector.rs b/crates/editor/src/ui/navigation_inspector.rs new file mode 100644 index 0000000..c675def --- /dev/null +++ b/crates/editor/src/ui/navigation_inspector.rs @@ -0,0 +1,765 @@ +use std::collections::HashSet; +use std::path::{Path, PathBuf}; + +use bevy::prelude::*; +use bevy_egui::egui; +use egui_phosphor_icons::icons; +use scene::navigation::{ + aabb_overlaps, bake_navigation, geometry_overlaps_bounds, navigation_geometry_from_parts, + navigation_source_fingerprint, point_in_aabb, read_navigation_artifact, + write_navigation_artifact, NavigationAreaInput, NavigationBakeArtifact, NavigationBakeInput, + NavigationLinkInput, NavigationVolumeInput, +}; +use shared::{ + ActorId, BrushDesc, NavigationArea, NavigationBounds, NavigationLink, NavigationObstacle, + Primitive, COMPONENT_NAVIGATION_AREA, COMPONENT_NAVIGATION_BOUNDS, COMPONENT_NAVIGATION_LINK, + COMPONENT_NAVIGATION_OBSTACLE, +}; + +use crate::history::{set_navigation_with_history, NavigationComponentState}; +use crate::project_io::ProjectWorkspace; +use crate::scene_io::SceneIo; +use crate::ui::inspector::{ + apply_component_card_response, component_card, component_card_context, property_row, + ComponentCardOptions, +}; +use crate::ui::theme::{ERROR, SUCCESS, TEXT_DIM, WARNING}; +use crate::viewport::{editor_spawn_ground_position, snap_translation, ViewportSettings}; + +#[derive(Resource, Debug, Clone)] +pub struct NavigationEditorState { + pub bounds_entity: Option, + pub artifact: Option, + pub preview_start: Vec3, + pub preview_end: Vec3, + pub preview_path: Vec, + pub status: String, + pub error: bool, + pub show_mesh: bool, + observed_fingerprint: Option, + auto_bake_attempt: Option, + debounce_elapsed: f32, +} + +impl Default for NavigationEditorState { + fn default() -> Self { + Self { + bounds_entity: None, + artifact: None, + preview_start: Vec3::new(-4.0, 0.0, 0.0), + preview_end: Vec3::new(4.0, 0.0, 0.0), + preview_path: Vec::new(), + status: "Not baked".into(), + error: false, + show_mesh: true, + observed_fingerprint: None, + auto_bake_attempt: None, + debounce_elapsed: 0.0, + } + } +} + +pub fn navigation_bounds_inspector_ui(world: &mut World, ui: &mut egui::Ui, entity: Entity) { + let Some(mut bounds) = world.get::(entity).cloned() else { + return; + }; + let original = bounds.clone(); + let mut preview_start = world.resource::().preview_start; + let mut preview_end = world.resource::().preview_end; + let mut show_mesh = world.resource::().show_mesh; + let status = world.resource::().status.clone(); + let status_error = world.resource::().error; + let current = build_input(world, entity, &bounds).ok(); + let stale = current.as_ref().is_some_and(|input| { + world + .resource::() + .artifact + .as_ref() + .is_none_or(|artifact| artifact.is_stale_for(input)) + }); + let mut bake = false; + let mut preview = false; + + let card = component_card_context( + world, + entity, + ComponentCardOptions::removable( + COMPONENT_NAVIGATION_BOUNDS, + "Navigation Bounds", + icons::PATH, + ), + ); + let response = component_card(ui, &card, |ui| { + property_row(ui, "Half extents", |ui| { + vec3_editor(ui, &mut bounds.half_extents, 0.1) + }); + property_row(ui, "Agent ID", |ui| { + ui.text_edit_singleline(&mut bounds.agent.id) + }); + property_row(ui, "Radius / height", |ui| { + ui.horizontal(|ui| { + ui.add(egui::DragValue::new(&mut bounds.agent.radius).speed(0.01)); + ui.add(egui::DragValue::new(&mut bounds.agent.height).speed(0.01)) + }) + .response + }); + property_row(ui, "Climb / slope", |ui| { + ui.horizontal(|ui| { + ui.add(egui::DragValue::new(&mut bounds.agent.max_climb).speed(0.01)); + ui.add( + egui::DragValue::new(&mut bounds.agent.max_slope_deg) + .speed(0.25) + .suffix(" deg"), + ) + }) + .response + }); + property_row(ui, "Voxel detail", |ui| { + ui.horizontal(|ui| { + ui.add( + egui::DragValue::new(&mut bounds.agent.cell_size_fraction) + .speed(0.1) + .range(1.0..=12.0), + ); + ui.add( + egui::DragValue::new(&mut bounds.agent.cell_height_fraction) + .speed(0.1) + .range(1.0..=24.0), + ) + }) + .response + }); + property_row(ui, "Artifact", |ui| { + ui.text_edit_singleline(&mut bounds.artifact_path) + }); + property_row(ui, "Auto bake", |ui| { + ui.checkbox(&mut bounds.auto_bake, "Debounced") + }); + + ui.separator(); + ui.horizontal(|ui| { + bake = ui + .button(format!("{} Bake", icons::HAMMER.as_str())) + .on_hover_text("Bake deterministic navigation artifact") + .clicked(); + preview = ui + .add_enabled( + !stale, + egui::Button::new(format!("{} Test Path", icons::PATH.as_str())), + ) + .on_hover_text("Query the baked artifact using the game runtime API") + .clicked(); + ui.checkbox(&mut show_mesh, "Overlay"); + }); + let status_color = if status_error { + ERROR + } else if stale { + WARNING + } else { + SUCCESS + }; + ui.colored_label( + status_color, + if stale { + format!("Stale: {status}") + } else { + status.clone() + }, + ); + + property_row(ui, "Path start", |ui| { + vec3_editor(ui, &mut preview_start, 0.1) + }); + property_row(ui, "Path end", |ui| vec3_editor(ui, &mut preview_end, 0.1)); + if let Some(input) = current.as_ref() { + ui.label( + egui::RichText::new(format!( + "{} obstacles {} areas {} links", + input.obstacles.len(), + input.areas.len(), + input.links.len() + )) + .small() + .color(TEXT_DIM), + ); + } + }); + apply_component_card_response(world, entity, response); + + if bounds != original { + set_navigation_with_history( + world, + entity, + NavigationComponentState { + bounds: Some(bounds.clone()), + ..Default::default() + }, + ); + } + { + let mut state = world.resource_mut::(); + state.preview_start = preview_start; + state.preview_end = preview_end; + state.show_mesh = show_mesh; + } + if bake { + bake_bounds(world, entity); + } + if preview { + query_preview(world, entity); + } +} + +pub fn navigation_obstacle_inspector_ui(world: &mut World, ui: &mut egui::Ui, entity: Entity) { + let Some(mut value) = world.get::(entity).cloned() else { + return; + }; + let original = value.clone(); + let card = component_card_context( + world, + entity, + ComponentCardOptions::removable( + COMPONENT_NAVIGATION_OBSTACLE, + "Navigation Obstacle", + icons::SELECTION_SLASH, + ), + ); + let response = component_card(ui, &card, |ui| { + property_row(ui, "Half extents", |ui| { + vec3_editor(ui, &mut value.half_extents, 0.05) + }); + property_row(ui, "Runtime", |ui| { + ui.checkbox(&mut value.dynamic, "Dynamic obstacle") + }); + if value.dynamic { + ui.colored_label( + WARNING, + "Dynamic carving is not baked in navigation v1; this volume remains a static bake input.", + ); + } + }); + apply_component_card_response(world, entity, response); + if value != original { + set_navigation_with_history( + world, + entity, + NavigationComponentState { + obstacle: Some(value), + ..Default::default() + }, + ); + } +} + +pub fn navigation_area_inspector_ui(world: &mut World, ui: &mut egui::Ui, entity: Entity) { + let Some(mut value) = world.get::(entity).cloned() else { + return; + }; + let original = value.clone(); + let card = component_card_context( + world, + entity, + ComponentCardOptions::removable( + COMPONENT_NAVIGATION_AREA, + "Navigation Area", + icons::POLYGON, + ), + ); + let response = component_card(ui, &card, |ui| { + property_row(ui, "Area ID", |ui| ui.text_edit_singleline(&mut value.id)); + property_row(ui, "Half extents", |ui| { + vec3_editor(ui, &mut value.half_extents, 0.05) + }); + property_row(ui, "Cost", |ui| { + ui.add( + egui::DragValue::new(&mut value.cost) + .speed(0.05) + .range(0.05..=100.0), + ) + }); + property_row(ui, "Walkable", |ui| { + ui.checkbox(&mut value.walkable, "Enabled") + }); + }); + apply_component_card_response(world, entity, response); + if value != original { + set_navigation_with_history( + world, + entity, + NavigationComponentState { + area: Some(value), + ..Default::default() + }, + ); + } +} + +pub fn navigation_link_inspector_ui(world: &mut World, ui: &mut egui::Ui, entity: Entity) { + let Some(mut value) = world.get::(entity).cloned() else { + return; + }; + let original = value.clone(); + let card = component_card_context( + world, + entity, + ComponentCardOptions::removable(COMPONENT_NAVIGATION_LINK, "Navigation Link", icons::LINK), + ); + let response = component_card(ui, &card, |ui| { + property_row(ui, "Start", |ui| vec3_editor(ui, &mut value.start, 0.05)); + property_row(ui, "End", |ui| vec3_editor(ui, &mut value.end, 0.05)); + property_row(ui, "Direction", |ui| { + ui.checkbox(&mut value.bidirectional, "Bidirectional") + }); + property_row(ui, "Cost", |ui| { + ui.add( + egui::DragValue::new(&mut value.cost) + .speed(0.05) + .range(0.05..=100.0), + ) + }); + property_row(ui, "Enabled", |ui| { + ui.checkbox(&mut value.enabled, "Active") + }); + }); + apply_component_card_response(world, entity, response); + if value != original { + set_navigation_with_history( + world, + entity, + NavigationComponentState { + link: Some(value), + ..Default::default() + }, + ); + } +} + +pub fn bake_bounds(world: &mut World, entity: Entity) { + let Some(bounds) = world.get::(entity).cloned() else { + set_status( + world, + "Bake failed: selected actor has no Navigation Bounds", + true, + ); + return; + }; + let input = match build_input(world, entity, &bounds) { + Ok(input) => input, + Err(error) => { + set_status(world, format!("Bake failed: {error}"), true); + return; + } + }; + let artifact = match bake_navigation(&input) { + Ok(artifact) => artifact, + Err(error) => { + set_status(world, format!("Bake failed: {error}"), true); + return; + } + }; + let path = match artifact_path(world, &bounds.artifact_path) { + Ok(path) => path, + Err(error) => { + set_status(world, format!("Bake failed: {error}"), true); + return; + } + }; + if let Err(error) = write_navigation_artifact(&path, &artifact) { + set_status(world, format!("Bake failed: {error}"), true); + return; + } + let polygon_count: usize = artifact + .mesh + .layers + .iter() + .map(|layer| layer.polygons.len()) + .sum(); + let mut state = world.resource_mut::(); + state.bounds_entity = Some(entity); + state.artifact = Some(artifact); + state.preview_path.clear(); + state.error = false; + state.status = format!("Baked {polygon_count} polygons to {}", bounds.artifact_path); +} + +pub fn query_preview(world: &mut World, entity: Entity) { + let (artifact, start, end) = { + let state = world.resource::(); + ( + state.artifact.clone(), + state.preview_start, + state.preview_end, + ) + }; + let Some(artifact) = artifact else { + set_status(world, "Path test failed: bake navigation first", true); + return; + }; + let runtime = match game::navigation::NavigationRuntime::from_artifact(artifact) { + Ok(runtime) => runtime, + Err(error) => { + set_status(world, format!("Path test failed: {error}"), true); + return; + } + }; + match runtime.query(start, end) { + Ok(path) => { + let mut state = world.resource_mut::(); + state.bounds_entity = Some(entity); + state.preview_path = path.points; + state.error = false; + state.status = format!( + "Path {:.2} m, {} waypoints{}", + path.length, + state.preview_path.len(), + path.used_link_actor_id + .as_deref() + .map(|id| format!(", link {id}")) + .unwrap_or_default() + ); + } + Err(error) => set_status(world, format!("Path test failed: {error}"), true), + } +} + +fn build_input( + world: &mut World, + bounds_entity: Entity, + bounds: &NavigationBounds, +) -> Result { + let active_path = world + .resource::() + .active_path + .as_ref() + .cloned() + .ok_or_else(|| "save the scene before baking navigation".to_string())?; + let project_root = Path::new(&world.resource::().root); + let source_scene = active_path + .strip_prefix(project_root) + .unwrap_or(&active_path) + .to_string_lossy() + .replace('\\', "/"); + let bounds_actor_id = actor_id(world, bounds_entity)?; + let bounds_transform = world_transform(world, bounds_entity); + let center = bounds_transform.translation.to_array(); + let half_extents = world_aabb_half_extents(bounds_transform, bounds.half_extents).to_array(); + + let mut obstacles = Vec::new(); + let mut obstacle_query = world.query::<(Entity, &NavigationObstacle)>(); + for (entity, obstacle) in obstacle_query.iter(world) { + let value = NavigationVolumeInput { + actor_id: actor_id(world, entity)?, + center: world_position(world, entity).to_array(), + half_extents: world_aabb_half_extents( + world_transform(world, entity), + obstacle.half_extents, + ) + .to_array(), + }; + if aabb_overlaps(center, half_extents, value.center, value.half_extents) { + obstacles.push(value); + } + } + obstacles.sort_by(|a, b| a.actor_id.cmp(&b.actor_id)); + + let mut areas = Vec::new(); + let mut area_query = world.query::<(Entity, &NavigationArea)>(); + for (entity, area) in area_query.iter(world) { + let value = NavigationAreaInput { + actor_id: actor_id(world, entity)?, + id: area.id.clone(), + center: world_position(world, entity).to_array(), + half_extents: world_aabb_half_extents( + world_transform(world, entity), + area.half_extents, + ) + .to_array(), + cost: area.cost, + walkable: area.walkable, + }; + if aabb_overlaps(center, half_extents, value.center, value.half_extents) { + areas.push(value); + } + } + areas.sort_by(|a, b| a.actor_id.cmp(&b.actor_id)); + + let mut links = Vec::new(); + let mut link_query = world.query::<(Entity, &NavigationLink)>(); + for (entity, link) in link_query.iter(world) { + let transform = world_transform(world, entity); + let value = NavigationLinkInput { + actor_id: actor_id(world, entity)?, + start: transform.transform_point(link.start).to_array(), + end: transform.transform_point(link.end).to_array(), + bidirectional: link.bidirectional, + cost: link.cost, + enabled: link.enabled, + }; + if point_in_aabb(value.start, center, half_extents) + || point_in_aabb(value.end, center, half_extents) + { + links.push(value); + } + } + links.sort_by(|a, b| a.actor_id.cmp(&b.actor_id)); + + let mut geometry = Vec::new(); + let mut geometry_query = world.query::<(Entity, Option<&Primitive>, Option<&BrushDesc>)>(); + for (entity, primitive, brush) in geometry_query.iter(world) { + if primitive.is_none() && brush.is_none() { + continue; + } + let transform = world_transform(world, entity); + let Some(value) = navigation_geometry_from_parts( + actor_id(world, entity)?, + transform.translation.to_array(), + transform.rotation.to_array(), + transform.scale.to_array(), + primitive, + brush, + ) else { + continue; + }; + if geometry_overlaps_bounds(&value, center, half_extents) { + geometry.push(value); + } + } + geometry.sort_by(|a, b| a.actor_id.cmp(&b.actor_id)); + + Ok(NavigationBakeInput { + source_scene, + bounds_actor_id, + center, + half_extents, + agent: bounds.agent.clone(), + geometry, + obstacles, + areas, + links, + }) +} + +fn actor_id(world: &World, entity: Entity) -> Result { + world + .get::(entity) + .map(|id| id.0.clone()) + .filter(|id| !id.trim().is_empty()) + .ok_or_else(|| format!("navigation actor {entity:?} requires a stable ActorId")) +} + +fn world_transform(world: &World, entity: Entity) -> Transform { + let mut chain = Vec::new(); + let mut current = entity; + let mut visited = HashSet::new(); + while visited.insert(current) { + chain.push(world.get::(current).copied().unwrap_or_default()); + let Some(parent) = world.get::(current).map(ChildOf::parent) else { + break; + }; + current = parent; + } + if world.get::(current).is_some() { + return Transform::default(); + } + let mut chain = chain.into_iter().rev(); + let root = chain.next().unwrap_or_default(); + chain + .fold(GlobalTransform::from(root), |global, local| { + global.mul_transform(local) + }) + .compute_transform() +} + +fn world_position(world: &World, entity: Entity) -> Vec3 { + world_transform(world, entity).translation +} + +fn world_aabb_half_extents(transform: Transform, local_half_extents: Vec3) -> Vec3 { + let half = transform.scale.abs() * local_half_extents; + let x = transform.rotation * Vec3::X; + let y = transform.rotation * Vec3::Y; + let z = transform.rotation * Vec3::Z; + x.abs() * half.x + y.abs() * half.y + z.abs() * half.z +} + +fn artifact_path(world: &World, authored: &str) -> Result { + let relative = Path::new(authored); + if relative.is_absolute() + || relative + .components() + .any(|component| matches!(component, std::path::Component::ParentDir)) + { + return Err("navigation artifact path must stay inside the project".into()); + } + if !authored + .replace('\\', "/") + .starts_with("assets/navigation/generated/") + { + return Err("navigation artifacts must be under assets/navigation/generated/".into()); + } + Ok(Path::new(&world.resource::().root).join(relative)) +} + +fn set_status(world: &mut World, message: impl Into, error: bool) { + let message = message.into(); + let mut state = world.resource_mut::(); + state.status.clone_from(&message); + state.error = error; + if error { + state.preview_path.clear(); + } + world.resource_mut::().set_status(message); +} + +fn vec3_editor(ui: &mut egui::Ui, value: &mut Vec3, speed: f64) -> egui::Response { + ui.horizontal(|ui| { + ui.add(egui::DragValue::new(&mut value.x).speed(speed)); + ui.add(egui::DragValue::new(&mut value.y).speed(speed)); + ui.add(egui::DragValue::new(&mut value.z).speed(speed)) + }) + .response +} + +#[derive(Debug, Clone, Copy)] +pub enum NavigationActorType { + Bounds, + Obstacle, + Area, + Link, +} + +pub fn spawn_navigation_actor(world: &mut World, actor_type: NavigationActorType) -> Entity { + let rect = world.resource::().viewport_rect; + let settings = world.resource::().clone(); + let translation = snap_translation(editor_spawn_ground_position(world, rect), &settings); + let (name, bounds, obstacle, area, link) = match actor_type { + NavigationActorType::Bounds => ( + "Navigation Bounds", + Some(NavigationBounds::default()), + None, + None, + None, + ), + NavigationActorType::Obstacle => ( + "Navigation Obstacle", + None, + Some(NavigationObstacle::default()), + None, + None, + ), + NavigationActorType::Area => ( + "Navigation Area", + None, + None, + Some(NavigationArea::default()), + None, + ), + NavigationActorType::Link => ( + "Navigation Link", + None, + None, + None, + Some(NavigationLink::default()), + ), + }; + let mut snapshot = + super::helpers::primitive_snapshot(name, shared::PrimitiveShape::Box, translation); + snapshot.actor_kind = shared::ActorKind::Navigation; + snapshot.primitive = None; + snapshot.material = None; + snapshot.physics = None; + snapshot.collider = None; + snapshot.rigid_body = None; + snapshot.navigation_bounds = bounds; + snapshot.navigation_obstacle = obstacle; + snapshot.navigation_area = area; + snapshot.navigation_link = link; + crate::history::spawn_with_history(world, snapshot) +} + +pub fn sync_navigation_state(world: &mut World) { + let selected = world.resource::().0; + let Some(entity) = selected.filter(|entity| world.get::(*entity).is_some()) + else { + return; + }; + let Some(bounds) = world.get::(entity).cloned() else { + return; + }; + let input = match build_input(world, entity, &bounds) { + Ok(input) => input, + Err(_) => return, + }; + let fingerprint = navigation_source_fingerprint(&input); + let changed_bounds = world.resource::().bounds_entity != Some(entity); + if changed_bounds { + let loaded = artifact_path(world, &bounds.artifact_path) + .ok() + .and_then(|path| read_navigation_artifact(&path).ok()); + let mut state = world.resource_mut::(); + state.bounds_entity = Some(entity); + state.artifact = loaded; + state.preview_path.clear(); + state.observed_fingerprint = None; + state.auto_bake_attempt = None; + state.debounce_elapsed = 0.0; + } + + let delta = world.resource::