bevy_ghx_proc_gen/
lib.rs

1#![warn(missing_docs)]
2
3//! This library encapsulates (and re-exports) the "ghx_proc_gen" library for 2D & 3D procedural generation with Model synthesis/Wave function Collapse, for a Bevy usage.
4//! Also provide grid utilities to manipulate & debug 2d & 3d grid data with Bevy.
5
6use bevy::{
7    ecs::{
8        bundle::Bundle,
9        component::Component,
10        entity::Entity,
11        event::EntityEvent,
12        query::Added,
13        resource::Resource,
14        system::{Commands, Query, Res},
15    },
16    math::Vec3,
17    platform::collections::HashSet,
18    prelude::{Deref, DerefMut, Without},
19};
20use ghx_proc_gen::{
21    generator::{
22        model::{ModelIndex, ModelInstance},
23        GeneratedNode,
24    },
25    ghx_grid::{
26        cartesian::{coordinates::CartesianCoordinates, grid::CartesianGrid},
27        grid::GridData,
28    },
29    NodeIndex,
30};
31
32use assets::BundleInserter;
33use spawner_plugin::NodesSpawner;
34
35pub use bevy_ghx_grid;
36pub use ghx_proc_gen as proc_gen;
37
38/// Types to define and spawn assets
39pub mod assets;
40
41/// Debug plugin to run the generation with different visualization options
42#[cfg(feature = "debug-plugin")]
43pub mod debug_plugin;
44/// Simple plugin to run the generation
45#[cfg(feature = "simple-plugin")]
46pub mod simple_plugin;
47/// Plugin to automatically spawn generated nodes
48pub mod spawner_plugin;
49
50/// Adds default [`BundleInserter`] implementations for some common types.
51///
52/// **WARNING**: those default implementations each assume a specific `Rotation Axis` for the `Models` (Z+ for 2d, Y+ for 3d)
53#[cfg(feature = "default-bundle-inserters")]
54pub mod default_bundles;
55
56#[cfg(feature = "egui-edit")]
57pub use bevy_egui;
58
59/// The generation with the specified entity was fully generated
60#[derive(EntityEvent, Clone)]
61pub struct GridGeneratedEvent<C: CartesianCoordinates> {
62    /// The entity of the generation
63    pub entity: Entity,
64    /// The grid data that was generated
65    pub grid_data: GridData<C, ModelInstance, CartesianGrid<C>>,
66}
67
68/// The generation with the specified entity was reinitialized
69#[derive(EntityEvent, Clone, Debug)]
70pub struct GenerationResetEvent(pub Entity);
71
72/// The generation with the specified entity was updated on the specified node
73#[derive(EntityEvent, Clone, Debug)]
74pub struct NodesGeneratedEvent {
75    /// The entity of the generation
76    pub entity: Entity,
77    /// The nodes that were generated
78    pub nodes: Vec<GeneratedNode>,
79}
80
81/// Used to mark a node spawned by a [`ghx_proc_gen::generator::Generator`]. Stores the [NodeIndex] of this node
82#[derive(Component)]
83pub struct GridNode(pub NodeIndex);
84
85/// Main component marker for a cursor target
86#[derive(Component)]
87pub struct CursorTarget;
88
89/// Component used to store model indexes of models with no assets.
90///
91/// Can be used for special handling of those models (skip their generation when stepping, ...).
92#[derive(Component, Default, Deref, DerefMut)]
93pub struct VoidNodes(pub HashSet<ModelIndex>);
94
95/// Utility system. Adds a [`Bundle`] (or a [`Component`]) to every [`Entity`] that has [`GridNode`] Component (this is the case of nodes spawned by the `spawn_node` system). The `Bundle` will have its default value.
96///
97/// ### Example
98///
99/// Add a `MyAnimation` Component with its default value to every newly spawned node Entity
100/// ```ignore
101/// #[derive(Component, Default)]
102/// pub struct MyAnimation {
103///     duration_sec: f32,
104///     final_scale: Vec3,
105/// }
106/// impl Default for MyAnimation {
107///     fn default() -> Self {
108///         Self {
109///             duration_sec: 5.,
110///             final_scale: Vec3::splat(2.0),
111///         }
112///     }
113/// }
114/// // ... In the `App` initialization code:
115/// app.add_systems(
116///     Update,
117///     insert_default_bundle_to_spawned_nodes::<MyAnimation>
118/// );
119/// ```
120pub fn insert_default_bundle_to_spawned_nodes<B: Bundle + Default>(
121    mut commands: Commands,
122    spawned_nodes: Query<Entity, (Added<GridNode>, Without<CursorTarget>)>,
123) {
124    for node in spawned_nodes.iter() {
125        commands.entity(node).try_insert(B::default());
126    }
127}
128
129/// Utility system. Adds a [`Bundle`] (or a [`Component`]) to every [`Entity`] that has [`GridNode`] Component (this is the case of nodes spawned by the `spawn_node` system). The `Bundle` will be cloned from a `Resource`
130///
131/// ### Example
132///
133/// Add a `MyAnimation` Component cloned from a `Resource` to every newly spawned node Entity
134/// ```ignore
135/// #[derive(Component, Resource)]
136/// pub struct MyAnimation {
137///     duration_sec: f32,
138///     final_scale: Vec3,
139/// }
140/// app.insert_resource(MyAnimation {
141///     duration_sec: 0.8,
142///     final_scale: Vec3::ONE,
143/// });
144/// app.add_systems(
145///     Update,
146///     insert_bundle_from_resource_to_spawned_nodes::<MyAnimation>
147/// );
148/// ```
149pub fn insert_bundle_from_resource_to_spawned_nodes<B: Bundle + Resource + Clone>(
150    mut commands: Commands,
151    bundle_to_clone: Res<B>,
152    spawned_nodes: Query<Entity, (Added<GridNode>, Without<CursorTarget>)>,
153) {
154    for node in spawned_nodes.iter() {
155        commands.entity(node).try_insert(bundle_to_clone.clone());
156    }
157}
158
159/// Utility function to spawn grid nodes. Can work for multiple asset types.
160///
161/// Used by [`simple_plugin::ProcGenSimpleRunnerPlugin`] and [`debug_plugin::ProcGenDebugRunnerPlugin`] to spawn assets automatically.
162///
163/// ### Examples
164///
165/// Spawn 3d models (gltf) assets with a `Cartesian3D` grid
166/// ```ignore
167/// spawn_node::<Cartesian3D, Handle<Scene>>(...);
168/// ```
169/// Spawn 2d sprites (png, ...) assets with a `Cartesian3D` grid
170/// ```ignore
171/// spawn_node::<Cartesian3D, Handle<Image>>(...);
172/// ```
173pub fn spawn_node<C: CartesianCoordinates, A: BundleInserter>(
174    commands: &mut Commands,
175    gen_entity: Entity,
176    grid: &CartesianGrid<C>,
177    node_spawner: &NodesSpawner<A>,
178    instance: &ModelInstance,
179    node_index: NodeIndex,
180) {
181    let node_assets = match node_spawner.assets.get(&instance.model_index) {
182        Some(node_assets) => node_assets,
183        None => return,
184    };
185
186    let pos = grid.pos_from_index(node_index);
187    for node_asset in node_assets {
188        let offset = &node_asset.world_offset;
189        let grid_offset = &node_asset.grid_offset;
190        // + (0.5 * size) to center `translation` in the node
191        let mut translation = Vec3::new(
192            offset.x + node_spawner.node_size.x * (pos.x as f32 + grid_offset.dx as f32 + 0.5),
193            offset.y + node_spawner.node_size.y * (pos.y as f32 + grid_offset.dy as f32 + 0.5),
194            offset.z + node_spawner.node_size.z * (pos.z as f32 + grid_offset.dz as f32 + 0.5),
195        );
196
197        if node_spawner.z_offset_from_y {
198            translation.z += node_spawner.node_size.z * (1. - pos.y as f32 / grid.size_y() as f32);
199        }
200
201        let node_entity = commands.spawn(GridNode(node_index)).id();
202        let node_entity_commands = &mut commands.entity(node_entity);
203
204        node_asset.assets_bundle.insert_bundle(
205            node_entity_commands,
206            translation,
207            node_spawner.spawn_scale,
208            instance.rotation,
209        );
210        (node_asset.spawn_commands)(node_entity_commands);
211
212        commands.entity(gen_entity).add_child(node_entity);
213    }
214}
215
216macro_rules! add_named_observer {
217    ($system: expr, $app: expr) => {
218        $app.world_mut().spawn((
219            bevy::prelude::Name::new(stringify!($system)),
220            bevy::ecs::observer::Observer::new($system),
221        ))
222    };
223}
224pub(crate) use add_named_observer;