bevy_symbios_shape 0.5.0

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.
  • 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 counters).
  • Mass properties: terminals whose grammar carries Mat(id, density) get a TerminalMass component (mass / centroid / inertia) for downstream physics, IK, and LOD.
  • 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
mass TerminalMass Component — mass / centroid / inertia from upstream MassProperties
mesh build_profiled_mesh / build_tapered_cuboid — procedural mesh generation
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.

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"] }

Examples

cargo run --example detailed_villa
cargo run --example medieval_castle

License

MIT