wde_terrain_grid/core/
entries.rs1use wde_logger::prelude::*;
2
3use bevy::prelude::*;
4use wde_gltf::prelude::*;
5use wde_scene::prelude::*;
6
7const PLACEMENT_CONFIG_VERSION: u32 = 1;
8const PLACEMENT_CONFIG_PATH: &str = "core/config/placement.json";
9
10#[derive(Clone, Debug, Reflect)]
12pub struct PlacementConfigEntry {
13 pub label: String,
14 pub asset: Handle<GltfAsset>,
15 pub extent: UVec2,
16 pub anchors_in: Vec<Vec2>,
17 pub anchors_out: Vec<Vec2>
18}
19
20#[derive(Resource, Default)]
22pub struct PlacementConfig {
23 pending_entries: Vec<(String, Handle<GltfAsset>)>,
24 pub labels: Vec<String>,
25 pub entries: Vec<PlacementConfigEntry>
26}
27
28pub struct PlacementPlugin;
29impl Plugin for PlacementPlugin {
30 fn build(&self, app: &mut App) {
31 app.init_resource::<PlacementConfig>()
32 .add_systems(Startup, read_placement_file)
33 .add_systems(Update, process_pending_entries);
34 }
35}
36
37fn read_placement_file(
38 asset_server: Res<AssetServer>,
39 mut placement_config_struct: ResMut<PlacementConfig>
40) {
41 let placement_config = match serialize::parse_file(PLACEMENT_CONFIG_PATH) {
43 Ok(config) => config,
44 Err(err) => {
45 error!(
46 "Failed to load placement configuration file at '{}': {}",
47 PLACEMENT_CONFIG_PATH, err
48 );
49 return;
50 }
51 };
52 let placement_config = placement_config.data;
53
54 if placement_config["version"].as_u64().unwrap_or(0) != PLACEMENT_CONFIG_VERSION as u64 {
56 error!(
57 "Unsupported placement configuration version: {}. Expected version: {}",
58 placement_config["version"], PLACEMENT_CONFIG_VERSION
59 );
60 return;
61 }
62
63 let config_entities = placement_config["entities"].as_array();
65 let mut entries = Vec::new();
66 for entity in config_entities.unwrap_or(&vec![]) {
67 let (label, model_path) = match (entity["label"].as_str(), entity["model"].as_str()) {
69 (Some(label), Some(model_path)) => (label.to_string(), model_path.to_string()),
70 _ => {
71 error!(
72 "Invalid entity configuration in placement config: {:?}",
73 entity
74 );
75 continue;
76 }
77 };
78 entries.push((label.clone(), asset_server.load(&model_path)));
79 }
80 placement_config_struct.pending_entries = entries;
81}
82
83fn process_pending_entries(
84 mut placement_config_struct: ResMut<PlacementConfig>,
85 gltf_assets: Res<Assets<GltfAsset>>
86) {
87 if placement_config_struct.pending_entries.is_empty() {
88 return;
89 }
90 let mut processed_entries = Vec::new();
91 let mut processed_labels = Vec::new();
92 let mut retry_entries = Vec::new();
93 for (label, asset_handle) in placement_config_struct.pending_entries.drain(..) {
94 if let Some(gltf_asset) = gltf_assets.get(&asset_handle) {
95 let properties = &gltf_asset.properties;
96 let extent = match properties.get("Footprint") {
97 Some((_pos, scale)) => {
98 UVec2::new((scale.x).round() as u32, (scale.z).round() as u32)
99 }
100 None => {
101 error!(
102 "Extent not specified for label '{}', defaulting to (1, 1).",
103 label
104 );
105 UVec2::new(1, 1)
106 }
107 };
108 let mut anchors_in = Vec::new();
109 let mut anchors_out = Vec::new();
110 for (anchor_name, (pos, _scale)) in properties.iter() {
111 if anchor_name.starts_with("AnchorIn") {
112 anchors_in.push(Vec2::new(pos.x, pos.z));
113 } else if anchor_name.starts_with("AnchorOut") {
114 anchors_out.push(Vec2::new(pos.x, pos.z));
115 }
116 }
117 processed_entries.push(PlacementConfigEntry {
118 label: label.clone(),
119 asset: asset_handle.clone(),
120 extent,
121 anchors_in,
122 anchors_out
123 });
124 processed_labels.push(label);
125 } else {
126 retry_entries.push((label, asset_handle));
127 }
128 }
129 placement_config_struct.entries.extend(processed_entries);
130 placement_config_struct.labels.extend(processed_labels);
131 placement_config_struct.pending_entries = retry_entries;
132}