Skip to main content

bimifc_bevy/
section.rs

1//! Section plane system
2//!
3//! Provides clipping plane functionality for viewing building cross-sections.
4
5#[cfg(target_arch = "wasm32")]
6#[allow(unused_imports)]
7use crate::storage::load_section;
8use crate::storage::SectionStorage;
9use bevy::prelude::*;
10
11/// Section plane plugin
12pub struct SectionPlanePlugin;
13
14impl Plugin for SectionPlanePlugin {
15    fn build(&self, app: &mut App) {
16        app.init_resource::<SectionPlane>()
17            .add_systems(Update, poll_section_settings);
18    }
19}
20
21/// Section plane axis
22#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
23pub enum SectionAxis {
24    X,
25    #[default]
26    Y,
27    Z,
28}
29
30impl SectionAxis {
31    /// Get plane normal vector
32    pub fn normal(&self, flipped: bool) -> Vec3 {
33        let base = match self {
34            SectionAxis::X => Vec3::X,
35            SectionAxis::Y => Vec3::Y,
36            SectionAxis::Z => Vec3::Z,
37        };
38        if flipped {
39            -base
40        } else {
41            base
42        }
43    }
44
45    /// Parse from string
46    pub fn parse(s: &str) -> Self {
47        match s.to_lowercase().as_str() {
48            "x" => SectionAxis::X,
49            "y" => SectionAxis::Y,
50            _ => SectionAxis::Z,
51        }
52    }
53
54    /// Convert to string
55    pub fn as_str(&self) -> &'static str {
56        match self {
57            SectionAxis::X => "x",
58            SectionAxis::Y => "y",
59            SectionAxis::Z => "z",
60        }
61    }
62}
63
64/// Section plane state
65#[derive(Resource)]
66pub struct SectionPlane {
67    /// Whether section plane is enabled
68    pub enabled: bool,
69    /// Section axis
70    pub axis: SectionAxis,
71    /// Position along axis (0.0 to 1.0 of scene bounds)
72    pub position: f32,
73    /// Whether plane normal is flipped
74    pub flipped: bool,
75    /// Cached plane equation (normal.xyz, distance)
76    pub plane: Vec4,
77}
78
79impl Default for SectionPlane {
80    fn default() -> Self {
81        Self {
82            enabled: false,
83            axis: SectionAxis::Y,
84            position: 0.5,
85            flipped: false,
86            plane: Vec4::new(0.0, 1.0, 0.0, 0.0),
87        }
88    }
89}
90
91impl SectionPlane {
92    /// Set axis
93    pub fn set_axis(&mut self, axis: SectionAxis) {
94        self.axis = axis;
95        self.update_plane();
96    }
97
98    /// Set position (0.0 to 1.0)
99    pub fn set_position(&mut self, position: f32) {
100        self.position = position.clamp(0.0, 1.0);
101        self.update_plane();
102    }
103
104    /// Toggle flip
105    pub fn toggle_flip(&mut self) {
106        self.flipped = !self.flipped;
107        self.update_plane();
108    }
109
110    /// Toggle enabled
111    pub fn toggle(&mut self) {
112        self.enabled = !self.enabled;
113    }
114
115    /// Update plane equation from current settings
116    pub fn update_plane(&mut self) {
117        let normal = self.axis.normal(self.flipped);
118        // Distance is calculated based on position - will be updated with scene bounds
119        self.plane = Vec4::new(normal.x, normal.y, normal.z, 0.0);
120    }
121
122    /// Update plane with scene bounds
123    pub fn update_with_bounds(&mut self, min: Vec3, max: Vec3) {
124        let normal = self.axis.normal(self.flipped);
125        let axis_min = match self.axis {
126            SectionAxis::X => min.x,
127            SectionAxis::Y => min.y,
128            SectionAxis::Z => min.z,
129        };
130        let axis_max = match self.axis {
131            SectionAxis::X => max.x,
132            SectionAxis::Y => max.y,
133            SectionAxis::Z => max.z,
134        };
135        let distance = axis_min + (axis_max - axis_min) * self.position;
136        self.plane = Vec4::new(normal.x, normal.y, normal.z, distance);
137    }
138
139    /// Load from storage
140    pub fn from_storage(&mut self, storage: &SectionStorage) {
141        self.enabled = storage.enabled;
142        self.axis = SectionAxis::parse(&storage.axis);
143        self.position = storage.position;
144        self.flipped = storage.flipped;
145        self.update_plane();
146    }
147
148    /// Convert to storage
149    pub fn to_storage(&self) -> SectionStorage {
150        SectionStorage {
151            enabled: self.enabled,
152            axis: self.axis.as_str().to_string(),
153            position: self.position,
154            flipped: self.flipped,
155        }
156    }
157}
158
159/// Poll section settings from localStorage
160#[allow(unused_mut)]
161fn poll_section_settings(mut section: ResMut<SectionPlane>) {
162    #[cfg(target_arch = "wasm32")]
163    {
164        // Only poll occasionally
165        static mut POLL_COUNTER: u32 = 0;
166        unsafe {
167            POLL_COUNTER += 1;
168            if POLL_COUNTER % 30 == 0 {
169                if let Some(storage) = load_section() {
170                    if storage.enabled != section.enabled
171                        || storage.axis != section.axis.as_str()
172                        || storage.position != section.position
173                        || storage.flipped != section.flipped
174                    {
175                        section.from_storage(&storage);
176                    }
177                }
178            }
179        }
180    }
181
182    // Suppress unused warning for native builds
183    let _ = &section;
184}
185
186// Note: Actual clipping would require custom shaders.
187// For a simpler approach, we can use Bevy's built-in clipping planes
188// or implement material-based clipping in a custom shader.
189//
190// For now, this module provides the data structures and settings.
191// The actual clipping can be implemented using:
192// 1. Custom material with clip plane uniform
193// 2. Bevy's ClipPlane component (if available)
194// 3. Post-processing effect
195
196/// Custom material with section plane support (placeholder)
197/// To implement actual clipping, create a custom material:
198///
199/// ```glsl
200/// // In fragment shader:
201/// if (section_enabled) {
202///     float d = dot(world_position.xyz, section_plane.xyz) - section_plane.w;
203///     if (d > 0.0) discard;
204/// }
205/// ```
206#[derive(Clone, Debug)]
207pub struct SectionPlaneMaterial {
208    pub base_color: Color,
209    pub section_plane: Vec4,
210    pub section_enabled: bool,
211}