Skip to main content

wde_terrain_grid/core/
entries.rs

1use 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/// Entry for a single entity in the placement configuration.
11#[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/// Configuration for the placement of entities in the terrain grid.
21#[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    // Load placement configuration file
42    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    // Check version
55    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    // Load entities
64    let config_entities = placement_config["entities"].as_array();
65    let mut entries = Vec::new();
66    for entity in config_entities.unwrap_or(&vec![]) {
67        // Parse entity configuration
68        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}