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;