bevy_symbios_shape 0.6.1

Bevy integration for Symbios Shape.
Documentation

bevy_symbios_shape

Bevy integration for symbios-shape — a CGA Shape Grammar engine for procedural architecture.

Converts grammar outputs (ShapeModel / Terminal nodes) into Bevy entity hierarchies with procedural or asset-based meshes.

Features

  • Plugin: BevySymbiosShapePlugin registers ShapeRegistry, ShapeMeshCache, LastDerivedModel, and SnapPlanes resources, plus an observer for SpawnShapeRequest events.
  • Asset routing: Register GLTF scenes and materials by string ID; unregistered terminals fall back to procedural meshes.
  • Procedural meshes: build_profiled_mesh generates geometry for tapered prisms, triangles, trapezoids, and arbitrary polygons based on the terminal's FaceProfile.
  • Round cross-sections: register an asset ID with register_round_mesh and its terminals bake as elliptical prisms — cylinders, frusta, and cones — so columns, silos, and spires read as round while the grammar keeps working in axis-aligned boxes.
  • Coordinate translation: scope_to_transform handles the f64→f32 downcast and corner-to-centroid offset between symbios-shape scopes and Bevy transforms.
  • Spawn extension: commands.spawn_shape(...) derives a grammar and spawns the full terminal hierarchy in one call.
  • Event-driven spawning: trigger SpawnShapeRequest and observe SpawnShapeSpawned / SpawnShapeFailed for decoupled call sites.
  • Cross-spawn mesh cache: ShapeMeshCache deduplicates procedural meshes across spawn_shape calls (with hit/miss/eviction counters) and is bounded — least-recently-used entries are evicted past its capacity, so long-lived re-rolling sessions cannot grow it without limit.
  • Mass properties: terminals whose grammar carries Mat(id, density) get a TerminalMass component (mass / centroid / inertia) for downstream physics, IK, and LOD.
  • Occlusion labels: terminals derived under Label("class") get a TerminalLabel component, so ECS systems can select the same groups the grammar's IfClear / IfInside conditionals reasoned about.
  • Snap planes: RegSnap("label") recordings are surfaced via the SnapPlanes resource.
  • Spatial queries: LastDerivedModel plus the re-exported TerminalQuery / obb_overlap give downstream systems OBB collision tests against spawned terminals.

Quick start

use bevy::prelude::*;
use bevy_symbios_shape::prelude::*;
use symbios_shape::{Interpreter, Scope, grammar::parse_ops};

fn main() {
    App::new()
        .add_plugins((DefaultPlugins, BevySymbiosShapePlugin))
        .add_systems(Startup, setup)
        .run();
}

fn setup(
    mut commands: Commands,
    registry: Res<ShapeRegistry>,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
    mut cache: ResMut<ShapeMeshCache>,
) {
    let mut interp = Interpreter::new();
    interp.add_rule("Lot",    parse_ops("Extrude(12) Split(Y) { 3: Ground | ~1: Upper | 2: Roof }").unwrap());
    interp.add_rule("Ground", parse_ops(r#"I("GroundFloor")"#).unwrap());
    interp.add_rule("Upper",  parse_ops(r#"I("Floor")"#).unwrap());
    interp.add_rule("Roof",   parse_ops(r#"Taper(0.8) I("Roof")"#).unwrap());

    let footprint = Scope::new(
        symbios_shape::Vec3::ZERO,
        symbios_shape::Quat::IDENTITY,
        symbios_shape::Vec3::new(10.0, 0.0, 10.0),
    );

    commands
        .spawn_shape(&interp, footprint, "Lot", &registry, &mut meshes, &mut materials, &mut cache)
        .unwrap();
}

Modules

Module Purpose
cache ShapeMeshCache — persistent procedural mesh cache shared across spawns
events SpawnShapeRequest / SpawnShapeSpawned / SpawnShapeFailed events + observer
label TerminalLabel Component — the occlusion class stamped by the grammar's Label("…")
mass TerminalMass Component — mass / centroid / inertia from upstream MassProperties
mesh build_profiled_mesh / build_profiled_mesh_with / build_tapered_cuboid — meshing
query LastDerivedModel resource holding the most recently derived ShapeModel
registry ShapeRegistry — asset routing table (mesh IDs → scenes, material IDs → materials)
snap SnapPlane / SnapPlanes resource exposing RegSnap recordings
spawner SpawnShapeExtCommands extension trait for one-call grammar derivation + spawning
transform scope_to_transform — f64 scope to f32 Bevy Transform with corner-to-centroid offset

Asset registration

Register GLTF scenes and materials before spawning so the grammar can resolve them:

fn setup(
    mut registry: ResMut<ShapeRegistry>,
    mut materials: ResMut<Assets<StandardMaterial>>,
    asset_server: Res<AssetServer>,
) {
    registry.register_mesh("Window", asset_server.load("window.glb#Scene0"));
    registry.register_material("Brick", asset_server.load("brick_material.glb#Material0"));
    registry.default_material = Some(materials.add(StandardMaterial::default()));

    // Stretch UVs across the face for materials whose texture should fit exactly
    // once per face (signage, stained glass) instead of tiling in world space.
    registry.register_stretch_material("Window");
}

Terminals whose mesh_id is not registered get a procedural mesh colored by a stable hash of the ID. Material fallback order is: registered handle → default_material → generated grey.

Round cross-sections

The grammar stays OBB-pure — every scope is an axis-aligned box — but a terminal's rendering can be round. Opt an asset ID in and the mesher inscribes an elliptical prism in the scope's footprint:

// Grammar:  Column --> Extrude(0.6) Taper(0.14) Mat("Marble") I("Column")
registry.register_round_mesh("Column");     // or register_round_material("Marble")
registry.set_round_segments(32);            // default 24, clamped to [3, 256]
Profile Rendered as
Rectangle cylinder (circular when the footprint is square, elliptical otherwise)
Taper(t) frustum — the top ring shrinks by t
Taper(1.0) cone (the top cap is dropped)
Triangle / Trapezoid / Polygon unchanged — flat panels have no cross-section to round

Side UVs wrap the circumference in world units (so texel density matches flat walls) unless the ID is also registered as a stretch target. Because roundness is a rendering choice, splits, occlusion queries, and mass properties are all computed on the original box.

Event-driven spawning

For decoupled call sites — e.g. a UI button that requests a building without holding the mesh/material asset registries — trigger [SpawnShapeRequest]:

use std::sync::Arc;

fn on_button_click(mut commands: Commands, interp: Res<MyInterpreter>) {
    commands.trigger(SpawnShapeRequest {
        interpreter: Arc::new(interp.0.clone()),
        root_scope: footprint(),
        root_rule: "Lot".into(),
    });
}

// Observe the result anywhere:
fn track_spawns(mut spawned: On<SpawnShapeSpawned>) { /* spawned.event().0 = root Entity */ }
fn track_errors(mut failed: On<SpawnShapeFailed>)   { /* failed.event().0 = error string */ }

The plugin's built-in observer performs the derivation and spawn, then triggers SpawnShapeSpawned(root_entity) on success or SpawnShapeFailed(message) on derivation failure.

Cargo features

Feature Default Effect
egui off Pulls in bevy_egui for downstream inspector/editor UIs.
mutation off Enables the mutation module: MaterialMutationPlugin, TextureConfigStore, and the MutateMaterialsRequest / MaterialTextureMutated event pair. Drives procedural-texture evolution via bevy_symbios_texture and symbios-genetics.

Enable from Cargo.toml:

[dependencies]
bevy_symbios_shape = { version = "0.5", features = ["mutation"] }

Mesh cache capacity

The cache defaults to DEFAULT_MESH_CACHE_CAPACITY (8192) entries and evicts least-recently-used meshes beyond it:

let mut cache = ShapeMeshCache::with_capacity(2048);
cache.set_capacity(None);            // opt out of eviction entirely
println!("{} evictions", cache.evictions());

Eviction drops the cached handle, not the GPU mesh — anything still spawned keeps its geometry alive. A steadily climbing evictions() means the working set exceeds the ceiling; raise it, or accept the re-upload churn.

Examples

# The 0.3 language: attributes, styles, guards, rhythm groups, Pick,
# ShapeL, Scatter, Label — plus round columns. Keys 1/2/3 switch the
# prosperity register, R re-rolls the seed, C toggles roundness.
cargo run --example parametric_hall

# Click-to-evolve buildings with procedural textures.
cargo run --example detailed_villa
cargo run --example medieval_castle

License

MIT