Skip to main content

eldiron_shared/
context.rs

1use crate::prelude::*;
2use rusterix::Surface;
3pub use rusterix::{Value, VertexBlendPreset, map::*};
4use scenevm::GeoId;
5use theframework::prelude::*;
6
7/// Identifies which texture is currently being edited by editor tools.
8/// This abstraction allows the same draw/fill/pick tools to work on
9/// any paintable texture source (tiles, avatar frames, future types).
10/// Each variant also determines how colors are resolved for painting.
11#[derive(PartialEq, Clone, Copy, Debug)]
12pub enum PixelEditingContext {
13    /// No active editing target.
14    None,
15    /// Editing a tile texture: (tile_id, frame_index).
16    Tile(Uuid, usize),
17    /// Editing an avatar animation frame: (avatar_id, anim_id, perspective_index, frame_index).
18    AvatarFrame(Uuid, Uuid, usize, usize),
19}
20
21impl Default for PixelEditingContext {
22    fn default() -> Self {
23        Self::None
24    }
25}
26
27#[derive(PartialEq, Clone, Copy, Debug)]
28pub enum AvatarAnchorEditSlot {
29    None,
30    Main,
31    Off,
32}
33
34impl Default for AvatarAnchorEditSlot {
35    fn default() -> Self {
36        Self::None
37    }
38}
39
40impl PixelEditingContext {
41    /// Returns the [r, g, b, a] color to paint with for this context.
42    /// For tiles: uses the palette color with opacity applied.
43    /// For avatar frames: uses the selected body marker color.
44    pub fn get_draw_color(
45        &self,
46        palette: &ThePalette,
47        opacity: f32,
48        body_marker_color: Option<[u8; 4]>,
49    ) -> Option<[u8; 4]> {
50        match self {
51            PixelEditingContext::None => None,
52            PixelEditingContext::Tile(..) => {
53                if let Some(color) = palette.get_current_color() {
54                    let mut arr = color.to_u8_array();
55                    arr[3] = (arr[3] as f32 * opacity) as u8;
56                    Some(arr)
57                } else {
58                    None
59                }
60            }
61            PixelEditingContext::AvatarFrame(..) => body_marker_color,
62        }
63    }
64
65    /// Returns the number of animation frames for this context.
66    pub fn get_frame_count(&self, project: &Project) -> usize {
67        match self {
68            PixelEditingContext::None => 0,
69            PixelEditingContext::Tile(tile_id, _) => {
70                project.tiles.get(tile_id).map_or(0, |t| t.textures.len())
71            }
72            PixelEditingContext::AvatarFrame(avatar_id, anim_id, persp_index, _) => project
73                .avatars
74                .get(avatar_id)
75                .and_then(|a| a.animations.iter().find(|anim| anim.id == *anim_id))
76                .and_then(|anim| anim.perspectives.get(*persp_index))
77                .map_or(0, |p| p.frames.len()),
78        }
79    }
80
81    /// Returns a copy of this context with the frame index replaced.
82    pub fn with_frame(&self, frame_index: usize) -> Self {
83        match *self {
84            PixelEditingContext::None => PixelEditingContext::None,
85            PixelEditingContext::Tile(tile_id, _) => {
86                PixelEditingContext::Tile(tile_id, frame_index)
87            }
88            PixelEditingContext::AvatarFrame(avatar_id, anim_id, persp_index, _) => {
89                PixelEditingContext::AvatarFrame(avatar_id, anim_id, persp_index, frame_index)
90            }
91        }
92    }
93}
94
95#[derive(PartialEq, Clone, Copy, Debug)]
96pub enum GizmoMode {
97    XZ,
98    XY,
99    YZ,
100}
101
102#[derive(PartialEq, Clone, Copy, Debug)]
103pub enum EditorViewMode {
104    D2,
105    Orbit,
106    Iso,
107    FirstP,
108}
109
110impl EditorViewMode {
111    pub fn to_index(&self) -> i32 {
112        match self {
113            EditorViewMode::D2 => 0,
114            EditorViewMode::Orbit => 1,
115            EditorViewMode::Iso => 2,
116            EditorViewMode::FirstP => 3,
117        }
118    }
119    pub fn from_index(idx: i32) -> Self {
120        match idx {
121            1 => EditorViewMode::Orbit,
122            2 => EditorViewMode::Iso,
123            3 => EditorViewMode::FirstP,
124            _ => EditorViewMode::D2,
125        }
126    }
127
128    pub fn is_3d(&self) -> bool {
129        match self {
130            EditorViewMode::D2 => false,
131            _ => true,
132        }
133    }
134}
135
136#[derive(PartialEq, Clone, Copy, Debug)]
137pub enum ContentContext {
138    Unknown,
139    CharacterInstance(Uuid),
140    ItemInstance(Uuid),
141    Sector(Uuid),
142    CharacterTemplate(Uuid),
143    ItemTemplate(Uuid),
144    Shader(Uuid),
145}
146
147#[derive(PartialEq, Clone, Copy, Debug)]
148pub enum ProjectContext {
149    Unknown,
150    Region(Uuid),
151    RegionSettings(Uuid),
152    RegionCharacterInstance(Uuid, Uuid),
153    RegionItemInstance(Uuid, Uuid),
154    Character(Uuid),
155    CharacterVisualCode(Uuid),
156    CharacterCode(Uuid),
157    CharacterData(Uuid),
158    CharacterPreviewRigging(Uuid),
159    Item(Uuid),
160    ItemVisualCode(Uuid),
161    ItemCode(Uuid),
162    ItemData(Uuid),
163    Tilemap(Uuid),
164    Screen(Uuid),
165    ScreenWidget(Uuid, Uuid),
166    Asset(Uuid),
167    Avatar(Uuid),
168    AvatarAnimation(Uuid, Uuid, usize),
169    ProjectSettings,
170    GameRules,
171    GameLocales,
172    GameAudioFx,
173    DebugLog,
174    Console,
175}
176
177impl ProjectContext {
178    pub fn id(self) -> Option<Uuid> {
179        match self {
180            ProjectContext::Unknown
181            | ProjectContext::ProjectSettings
182            | ProjectContext::GameRules
183            | ProjectContext::GameLocales
184            | ProjectContext::GameAudioFx
185            | ProjectContext::DebugLog
186            | ProjectContext::Console => None,
187            ProjectContext::Region(id)
188            | ProjectContext::RegionSettings(id)
189            | ProjectContext::RegionCharacterInstance(id, _)
190            | ProjectContext::RegionItemInstance(id, _)
191            | ProjectContext::Character(id)
192            | ProjectContext::CharacterVisualCode(id)
193            | ProjectContext::CharacterCode(id)
194            | ProjectContext::CharacterData(id)
195            | ProjectContext::CharacterPreviewRigging(id)
196            | ProjectContext::Item(id)
197            | ProjectContext::ItemVisualCode(id)
198            | ProjectContext::ItemCode(id)
199            | ProjectContext::ItemData(id)
200            | ProjectContext::Tilemap(id)
201            | ProjectContext::Screen(id)
202            | ProjectContext::ScreenWidget(id, _)
203            | ProjectContext::Asset(id)
204            | ProjectContext::Avatar(id)
205            | ProjectContext::AvatarAnimation(id, _, _) => Some(id),
206        }
207    }
208
209    pub fn is_region(&self) -> bool {
210        match self {
211            ProjectContext::Region(_)
212            | ProjectContext::RegionSettings(_)
213            | ProjectContext::RegionCharacterInstance(_, _)
214            | ProjectContext::RegionItemInstance(_, _) => true,
215            _ => false,
216        }
217    }
218
219    pub fn get_region_character_instance_id(&self) -> Option<Uuid> {
220        match self {
221            ProjectContext::RegionCharacterInstance(_, instance_id) => Some(*instance_id),
222            _ => None,
223        }
224    }
225
226    pub fn get_region_item_instance_id(&self) -> Option<Uuid> {
227        match self {
228            ProjectContext::RegionItemInstance(_, instance_id) => Some(*instance_id),
229            _ => None,
230        }
231    }
232
233    pub fn is_character(&self) -> bool {
234        match self {
235            ProjectContext::Character(_)
236            | ProjectContext::CharacterVisualCode(_)
237            | ProjectContext::CharacterCode(_)
238            | ProjectContext::CharacterData(_)
239            | ProjectContext::CharacterPreviewRigging(_) => true,
240            _ => false,
241        }
242    }
243
244    pub fn is_item(&self) -> bool {
245        match self {
246            ProjectContext::Item(_)
247            | ProjectContext::ItemVisualCode(_)
248            | ProjectContext::ItemCode(_)
249            | ProjectContext::ItemData(_) => true,
250            _ => false,
251        }
252    }
253
254    pub fn is_tilemap(&self) -> bool {
255        match self {
256            ProjectContext::Tilemap(_) => true,
257            _ => false,
258        }
259    }
260
261    pub fn is_screen(&self) -> bool {
262        match self {
263            ProjectContext::Screen(_) | ProjectContext::ScreenWidget(_, _) => true,
264            _ => false,
265        }
266    }
267
268    pub fn get_screen_widget_id(&self) -> Option<Uuid> {
269        match self {
270            ProjectContext::ScreenWidget(_, widget_id) => Some(*widget_id),
271            _ => None,
272        }
273    }
274
275    pub fn is_asset(&self) -> bool {
276        match self {
277            ProjectContext::Asset(_) => true,
278            _ => false,
279        }
280    }
281
282    pub fn is_project_settings(&self) -> bool {
283        match self {
284            ProjectContext::ProjectSettings => true,
285            _ => false,
286        }
287    }
288
289    pub fn is_game_rules(&self) -> bool {
290        match self {
291            ProjectContext::GameRules => true,
292            _ => false,
293        }
294    }
295
296    pub fn is_game_locales(&self) -> bool {
297        match self {
298            ProjectContext::GameLocales => true,
299            _ => false,
300        }
301    }
302
303    pub fn is_game_audio_fx(&self) -> bool {
304        match self {
305            ProjectContext::GameAudioFx => true,
306            _ => false,
307        }
308    }
309
310    pub fn has_custom_map(&self) -> bool {
311        match self {
312            ProjectContext::Screen(_) => true,
313            _ => false,
314        }
315    }
316}
317
318#[derive(PartialEq, Clone, Copy, Debug)]
319pub enum MapContext {
320    Region,
321    Screen,
322    Character,
323    Item,
324}
325
326/// This gives context to the server of the editing state for live highlighting.
327pub struct ServerContext {
328    // Tree Ids
329    pub tree_regions_id: Uuid,
330    pub tree_characters_id: Uuid,
331    pub tree_items_id: Uuid,
332    pub tree_tilemaps_id: Uuid,
333    pub tree_screens_id: Uuid,
334    pub tree_avatars_id: Uuid,
335    pub tree_assets_id: Uuid,
336    pub tree_assets_fonts_id: Uuid,
337    pub tree_assets_audio_id: Uuid,
338    pub tree_palette_id: Uuid,
339    pub tree_settings_id: Uuid,
340
341    /// The currently selected region in the editor.
342    pub curr_region: Uuid,
343
344    /// The current region content.
345    pub curr_region_content: ContentContext,
346
347    /// The currently selected character in the editor.
348    pub curr_character: ContentContext,
349
350    /// The currently selected item in the editor.
351    pub curr_item: ContentContext,
352
353    /// The current content context.
354    pub cc: ContentContext,
355
356    /// The current project context.
357    pub pc: ProjectContext,
358
359    /// The currently selected codegrid in the code editor.
360    pub curr_grid_id: Option<Uuid>,
361
362    /// The currently selected screen.
363    pub curr_screen: Uuid,
364
365    /// The logged interactions of the characters.
366    pub interactions: FxHashMap<Uuid, Vec<Interaction>>,
367
368    /// The currently selected tile
369    pub curr_tile_id: Option<Uuid>,
370
371    /// The current frame/texture index being edited in tile editor
372    pub curr_tile_frame_index: usize,
373
374    /// The palette opacity for drawing tools
375    pub palette_opacity: f32,
376
377    /// The currently selected model
378    pub curr_model_id: Option<Uuid>,
379
380    /// The currently selected material
381    pub curr_material_id: Option<Uuid>,
382
383    /// The screen editor drawing mode.
384    pub screen_editor_mode_foreground: bool,
385
386    /// Hover geometry info
387    pub hover: (Option<u32>, Option<u32>, Option<u32>),
388
389    /// The current grid hover position
390    pub hover_cursor: Option<Vec2<f32>>,
391
392    /// The current 3d hover position
393    pub hover_cursor_3d: Option<Vec3<f32>>,
394
395    /// The current grid hover height
396    pub hover_height: Option<f32>,
397
398    /// Current Tool Type
399    pub curr_map_tool_type: MapToolType,
400
401    /// Current Map Context
402    curr_map_context: MapContext,
403
404    /// A click on map content originated from the map
405    pub content_click_from_map: bool,
406
407    /// Dont show rect based geometry on map
408    pub no_rect_geo_on_map: bool,
409
410    /// Show wall profile in linedef mode.
411    pub profile_view: Option<u32>,
412
413    /// The current editing surface
414    pub editing_surface: Option<Surface>,
415
416    /// The currently selected action
417    pub curr_action_id: Option<Uuid>,
418
419    /// Automatially apply actions
420    pub auto_action: bool,
421
422    /// Pending entity position changes: (from, to)
423    pub moved_entities: FxHashMap<Uuid, (Vec3<f32>, Vec3<f32>)>,
424
425    /// Pending item position changes: (from, to)
426    pub moved_items: FxHashMap<Uuid, (Vec3<f32>, Vec3<f32>)>,
427
428    /// Pending character orientation changes: (from, to)
429    pub rotated_entities: FxHashMap<Uuid, (Vec2<f32>, Vec2<f32>)>,
430
431    /// Selected wall row, set by the linedef Hud
432    pub selected_wall_row: Option<i32>,
433
434    /// View mode of the editor
435    pub editor_view_mode: EditorViewMode,
436
437    /// World mode is active
438    pub world_mode: bool,
439
440    /// Game server is running
441    pub game_mode: bool,
442
443    /// Map clipboard
444    pub clipboard: Map,
445
446    /// Map clipboard which is currently being pasted
447    pub paste_clipboard: Option<Map>,
448
449    /// Background Progress Text
450    pub background_progress: Option<String>,
451
452    /// Character Region Override
453    pub character_region_override: bool,
454
455    /// Item Region Override
456    pub item_region_override: bool,
457
458    /// Animation counter for tile previews
459    pub animation_counter: usize,
460
461    /// The current 3D hover hit
462    pub geo_hit: Option<GeoId>,
463
464    /// The current geometry hover hit position
465    pub geo_hit_pos: Vec3<f32>,
466
467    /// Temporary storage for the editing positon
468    pub editing_pos_buffer: Option<Vec3<f32>>,
469    /// Per-map, per-3D-view camera anchor (editing position).
470    pub editing_view_pos_by_map: FxHashMap<(Uuid, i32), Vec3<f32>>,
471    /// Per-map, per-3D-view look target.
472    pub editing_view_look_by_map: FxHashMap<(Uuid, i32), Vec3<f32>>,
473    /// Per-map 2D camera state: (offset, grid_size).
474    pub editing_view_2d_by_map: FxHashMap<Uuid, (Vec2<f32>, f32)>,
475    /// Per-map Iso camera scale.
476    pub editing_view_iso_scale_by_map: FxHashMap<Uuid, f32>,
477
478    /// The index of the selected icon in the hud
479    pub selected_hud_icon_index: i32,
480
481    ///Switch for showing 3D editing geometry
482    pub show_editing_geometry: bool,
483
484    /// Position of the 2D editing slice.
485    pub editing_slice: f32,
486    /// Height/thickness of the 2D editing slice.
487    pub editing_slice_height: f32,
488
489    /// The current plane for 3D movement
490    pub gizmo_mode: GizmoMode,
491
492    // For the Rect tool, identify the current sector and tile for preview
493    pub rect_sector_id_3d: Option<u32>,
494    pub rect_tile_id_3d: (i32, i32),
495    pub rect_terrain_id: Option<(i32, i32)>,
496    pub rect_blend_preset: VertexBlendPreset,
497
498    /// Game input mode
499    pub game_input_mode: bool,
500
501    /// Help mode state
502    pub help_mode: bool,
503
504    /// The current editing context for texture editor tools.
505    pub editing_ctx: PixelEditingContext,
506
507    /// The selected body marker color for avatar painting.
508    pub body_marker_color: Option<[u8; 4]>,
509    /// Active avatar anchor edit slot for pixel editor clicks.
510    pub avatar_anchor_slot: AvatarAnchorEditSlot,
511}
512
513impl Default for ServerContext {
514    fn default() -> Self {
515        Self::new()
516    }
517}
518
519impl ServerContext {
520    pub fn new() -> Self {
521        Self {
522            curr_region: Uuid::nil(),
523
524            tree_regions_id: Uuid::new_v4(),
525            tree_characters_id: Uuid::new_v4(),
526            tree_items_id: Uuid::new_v4(),
527            tree_tilemaps_id: Uuid::new_v4(),
528            tree_screens_id: Uuid::new_v4(),
529            tree_avatars_id: Uuid::new_v4(),
530            tree_assets_id: Uuid::new_v4(),
531            tree_assets_fonts_id: Uuid::new_v4(),
532            tree_assets_audio_id: Uuid::new_v4(),
533            tree_palette_id: Uuid::new_v4(),
534            tree_settings_id: Uuid::new_v4(),
535
536            curr_region_content: ContentContext::Unknown,
537            curr_character: ContentContext::Unknown,
538            curr_item: ContentContext::Unknown,
539            cc: ContentContext::Unknown,
540
541            pc: ProjectContext::Unknown,
542
543            curr_grid_id: None,
544
545            curr_screen: Uuid::nil(),
546
547            interactions: FxHashMap::default(),
548
549            curr_tile_id: None,
550            curr_tile_frame_index: 0,
551
552            palette_opacity: 1.0,
553
554            curr_model_id: None,
555            curr_material_id: None,
556
557            screen_editor_mode_foreground: false,
558
559            hover: (None, None, None),
560            hover_cursor: None,
561            hover_cursor_3d: None,
562            hover_height: None,
563
564            curr_map_tool_type: MapToolType::Linedef,
565            curr_map_context: MapContext::Region,
566
567            content_click_from_map: false,
568            no_rect_geo_on_map: true,
569            profile_view: None,
570
571            editing_surface: None,
572            curr_action_id: None,
573            auto_action: true,
574
575            moved_entities: FxHashMap::default(),
576            moved_items: FxHashMap::default(),
577            rotated_entities: FxHashMap::default(),
578
579            selected_wall_row: Some(0),
580
581            editor_view_mode: EditorViewMode::D2,
582
583            world_mode: false,
584            game_mode: false,
585
586            clipboard: Map::default(),
587            paste_clipboard: None,
588
589            background_progress: None,
590
591            character_region_override: true,
592            item_region_override: true,
593
594            animation_counter: 0,
595
596            geo_hit: None,
597            geo_hit_pos: Vec3::zero(),
598
599            editing_pos_buffer: None,
600            editing_view_pos_by_map: FxHashMap::default(),
601            editing_view_look_by_map: FxHashMap::default(),
602            editing_view_2d_by_map: FxHashMap::default(),
603            editing_view_iso_scale_by_map: FxHashMap::default(),
604
605            selected_hud_icon_index: 0,
606            show_editing_geometry: true,
607
608            gizmo_mode: GizmoMode::XZ,
609
610            editing_slice: 0.0,
611            editing_slice_height: 2.0,
612            rect_sector_id_3d: None,
613            rect_tile_id_3d: (0, 0),
614            rect_terrain_id: None,
615            rect_blend_preset: VertexBlendPreset::Solid,
616
617            game_input_mode: false,
618            help_mode: false,
619
620            editing_ctx: PixelEditingContext::None,
621            body_marker_color: None,
622            avatar_anchor_slot: AvatarAnchorEditSlot::None,
623        }
624    }
625
626    /// Checks if the PolyView has focus.
627    pub fn polyview_has_focus(&self, ctx: &TheContext) -> bool {
628        if let Some(focus) = &ctx.ui.focus {
629            if focus.name == "PolyView" {
630                return true;
631            }
632        }
633        false
634    }
635
636    /// Returns the current map context
637    pub fn get_map_context(&self) -> MapContext {
638        if self.pc.is_screen() {
639            return MapContext::Screen;
640        }
641
642        MapContext::Region
643
644        /*
645        if (self.curr_map_context == MapContext::Character && self.character_region_override)
646            || (self.curr_map_context == MapContext::Item && self.item_region_override)
647            || (self.curr_map_context == MapContext::Shader)
648        {
649            MapContext::Region
650        } else {
651            self.curr_map_context
652        }*/
653    }
654
655    pub fn set_map_context(&mut self, map_context: MapContext) {
656        self.curr_map_context = map_context;
657    }
658
659    #[inline]
660    fn view_key(map_id: Uuid, mode: EditorViewMode) -> (Uuid, i32) {
661        (map_id, mode.to_index())
662    }
663
664    pub fn store_edit_view_for_map(
665        &mut self,
666        map_id: Uuid,
667        mode: EditorViewMode,
668        pos: Vec3<f32>,
669        look: Vec3<f32>,
670    ) {
671        if mode == EditorViewMode::D2 {
672            return;
673        }
674        let key = Self::view_key(map_id, mode);
675        self.editing_view_pos_by_map.insert(key, pos);
676        self.editing_view_look_by_map.insert(key, look);
677    }
678
679    pub fn load_edit_view_for_map(
680        &self,
681        map_id: Uuid,
682        mode: EditorViewMode,
683    ) -> Option<(Vec3<f32>, Vec3<f32>)> {
684        if mode == EditorViewMode::D2 {
685            return None;
686        }
687        let key = Self::view_key(map_id, mode);
688        let pos = self.editing_view_pos_by_map.get(&key).copied()?;
689        let look = self.editing_view_look_by_map.get(&key).copied()?;
690        Some((pos, look))
691    }
692
693    pub fn store_edit_view_2d_for_map(&mut self, map_id: Uuid, offset: Vec2<f32>, grid_size: f32) {
694        self.editing_view_2d_by_map
695            .insert(map_id, (offset, grid_size));
696    }
697
698    pub fn load_edit_view_2d_for_map(&self, map_id: Uuid) -> Option<(Vec2<f32>, f32)> {
699        self.editing_view_2d_by_map.get(&map_id).copied()
700    }
701
702    pub fn store_edit_view_iso_scale_for_map(&mut self, map_id: Uuid, scale: f32) {
703        self.editing_view_iso_scale_by_map.insert(map_id, scale);
704    }
705
706    pub fn load_edit_view_iso_scale_for_map(&self, map_id: Uuid) -> Option<f32> {
707        self.editing_view_iso_scale_by_map.get(&map_id).copied()
708    }
709
710    /// Clears all state data.
711    pub fn clear(&mut self) {
712        self.curr_region_content = ContentContext::Unknown;
713        self.curr_character = ContentContext::Unknown;
714        self.curr_item = ContentContext::Unknown;
715        self.cc = ContentContext::Unknown;
716
717        self.curr_region = Uuid::nil();
718        self.curr_grid_id = None;
719        self.curr_screen = Uuid::nil();
720        self.interactions.clear();
721        self.moved_entities.clear();
722        self.moved_items.clear();
723        self.editing_view_pos_by_map.clear();
724        self.editing_view_look_by_map.clear();
725        self.editing_view_2d_by_map.clear();
726        self.editing_view_iso_scale_by_map.clear();
727    }
728
729    pub fn clear_interactions(&mut self) {
730        self.interactions.clear();
731    }
732
733    /// Convert local screen position to a map grid position
734    pub fn local_to_map_grid(
735        &self,
736        screen_size: Vec2<f32>,
737        coord: Vec2<f32>,
738        map: &Map,
739        subdivisions: f32,
740    ) -> Vec2<f32> {
741        let grid_space_pos = coord - screen_size / 2.0 - Vec2::new(map.offset.x, -map.offset.y);
742        let snapped = grid_space_pos / map.grid_size;
743        let rounded = snapped.map(|x| x.round());
744
745        if subdivisions > 1.0 {
746            let subdivision_size = 1.0 / subdivisions;
747            // Calculate fractional part of the snapped position
748            let fractional = snapped - rounded;
749            // Snap the fractional part to the nearest subdivision
750            rounded + fractional.map(|x| (x / subdivision_size).round() * subdivision_size)
751        } else {
752            rounded
753        }
754    }
755
756    /// Snap to a grid cell
757    pub fn local_to_map_cell(
758        &self,
759        screen_size: Vec2<f32>,
760        coord: Vec2<f32>,
761        map: &Map,
762        subdivisions: f32,
763    ) -> Vec2<f32> {
764        let grid_space_pos = coord - screen_size / 2.0 - Vec2::new(map.offset.x, -map.offset.y);
765        let grid_cell = (grid_space_pos / map.grid_size).map(|x| x.floor());
766
767        if subdivisions > 1.0 {
768            let sub_cell_size = map.grid_size / subdivisions;
769            let sub_index = grid_space_pos
770                .map(|x| x.rem_euclid(map.grid_size) / sub_cell_size)
771                .map(|x| x.floor());
772            grid_cell + sub_index / subdivisions
773        } else {
774            grid_cell
775        }
776    }
777
778    /// Convert a map grid position to a local screen position
779    pub fn map_grid_to_local(screen_size: Vec2<f32>, grid_pos: Vec2<f32>, map: &Map) -> Vec2<f32> {
780        let grid_space_pos = grid_pos * map.grid_size;
781        grid_space_pos + Vec2::new(map.offset.x, -map.offset.y) + screen_size / 2.0
782    }
783
784    /// Centers the map at the given grid position.
785    pub fn center_map_at_grid_pos(
786        &mut self,
787        _screen_size: Vec2<f32>,
788        grid_pos: Vec2<f32>,
789        map: &mut Map,
790    ) {
791        let pixel_pos = grid_pos * map.grid_size;
792        map.offset.x = -(pixel_pos.x);
793        map.offset.y = pixel_pos.y;
794    }
795
796    /// Returns the geometry at the given screen_position
797    pub fn geometry_at(
798        &self,
799        screen_size: Vec2<f32>,
800        screen_pos: Vec2<f32>,
801        map: &Map,
802    ) -> (Option<u32>, Option<u32>, Option<u32>) {
803        let mut selection: (Option<u32>, Option<u32>, Option<u32>) = (None, None, None);
804        let hover_threshold = 6.0;
805
806        // Check the vertices
807        for vertex in &map.vertices {
808            if vertex.intersects_vertical_slice(self.editing_slice, self.editing_slice_height) {
809                if let Some(vertex_pos) = map.get_vertex(vertex.id) {
810                    let vertex_pos = Self::map_grid_to_local(screen_size, vertex_pos, map);
811                    if (screen_pos - vertex_pos).magnitude() <= hover_threshold {
812                        selection.0 = Some(vertex.id);
813                        break;
814                    }
815                }
816            }
817        }
818
819        // Check the lines
820        for linedef in &map.linedefs {
821            if linedef.intersects_vertical_slice(map, self.editing_slice, self.editing_slice_height)
822            {
823                if self.no_rect_geo_on_map && map.is_linedef_in_rect(linedef.id) {
824                    continue;
825                }
826
827                let start_vertex = map.get_vertex(linedef.start_vertex);
828                let end_vertex = map.get_vertex(linedef.end_vertex);
829
830                if let Some(start_vertex) = start_vertex {
831                    if let Some(end_vertex) = end_vertex {
832                        let start_pos = Self::map_grid_to_local(screen_size, start_vertex, map);
833                        let end_pos = Self::map_grid_to_local(screen_size, end_vertex, map);
834
835                        // Compute the perpendicular distance from the point to the line
836                        let line_vec = end_pos - start_pos;
837                        let mouse_vec = screen_pos - start_pos;
838                        let line_vec_length = line_vec.magnitude();
839                        let projection =
840                            mouse_vec.dot(line_vec) / (line_vec_length * line_vec_length);
841                        let closest_point = start_pos + projection.clamp(0.0, 1.0) * line_vec;
842                        let distance = (screen_pos - closest_point).magnitude();
843
844                        if distance <= hover_threshold {
845                            selection.1 = Some(linedef.id);
846                            break;
847                        }
848                    }
849                }
850            }
851        }
852
853        // Check the sectors
854
855        /// Point-in-polygon test using the ray-casting method
856        fn point_in_polygon(point: Vec2<f32>, polygon: &[Vec2<f32>]) -> bool {
857            let mut inside = false;
858            let mut j = polygon.len() - 1;
859
860            for i in 0..polygon.len() {
861                if (polygon[i].y > point.y) != (polygon[j].y > point.y)
862                    && point.x
863                        < (polygon[j].x - polygon[i].x) * (point.y - polygon[i].y)
864                            / (polygon[j].y - polygon[i].y)
865                            + polygon[i].x
866                {
867                    inside = !inside;
868                }
869                j = i;
870            }
871
872            inside
873        }
874
875        // Reverse on sorted sectors by area (to allow to pick small sectors first)
876        let ordered = map.sorted_sectors_by_area();
877        for sector in ordered.iter().rev() {
878            if sector.intersects_vertical_slice(map, self.editing_slice, self.editing_slice_height)
879            {
880                if self.no_rect_geo_on_map
881                    && sector.properties.contains("rect")
882                    && sector.name.is_empty()
883                {
884                    continue;
885                }
886                let mut vertices = Vec::new();
887                for &linedef_id in &sector.linedefs {
888                    if let Some(linedef) = map.find_linedef(linedef_id) {
889                        if let Some(start_vertex) = map.find_vertex(linedef.start_vertex) {
890                            let vertex =
891                                Self::map_grid_to_local(screen_size, start_vertex.as_vec2(), map);
892
893                            // Add the vertex to the list if it isn't already there
894                            if vertices.last() != Some(&vertex) {
895                                vertices.push(vertex);
896                            }
897                        }
898                    }
899                }
900
901                if point_in_polygon(screen_pos, &vertices) {
902                    selection.2 = Some(sector.id);
903                    return selection;
904                }
905            }
906        }
907
908        selection
909    }
910
911    /// Returns all geometry within the given rectangle.
912    pub fn geometry_in_rectangle(
913        &self,
914        top_left: Vec2<f32>,
915        bottom_right: Vec2<f32>,
916        map: &Map,
917    ) -> (Vec<u32>, Vec<u32>, Vec<u32>) {
918        let mut selection: (Vec<u32>, Vec<u32>, Vec<u32>) = (Vec::new(), Vec::new(), Vec::new());
919
920        // Define helper to check if a point is within the rectangle
921        fn point_in_rectangle(
922            point: Vec2<f32>,
923            top_left: Vec2<f32>,
924            bottom_right: Vec2<f32>,
925        ) -> bool {
926            point.x >= top_left.x
927                && point.x <= bottom_right.x
928                && point.y >= top_left.y
929                && point.y <= bottom_right.y
930        }
931
932        /// Check if a line segment intersects a rectangle
933        fn line_intersects_rectangle(
934            a: Vec2<f32>,
935            b: Vec2<f32>,
936            top_left: Vec2<f32>,
937            bottom_right: Vec2<f32>,
938        ) -> bool {
939            // fn between(x: f32, min: f32, max: f32) -> bool {
940            //     x >= min && x <= max
941            // }
942
943            // Axis-Aligned Bounding Box (AABB) test for the line segment
944            let (min_x, max_x) = (a.x.min(b.x), a.x.max(b.x));
945            let (min_y, max_y) = (a.y.min(b.y), a.y.max(b.y));
946
947            if min_x > bottom_right.x
948                || max_x < top_left.x
949                || min_y > bottom_right.y
950                || max_y < top_left.y
951            {
952                return false; // Line is outside the rectangle
953            }
954
955            // Check if either endpoint is inside the rectangle
956            if point_in_rectangle(a, top_left, bottom_right)
957                || point_in_rectangle(b, top_left, bottom_right)
958            {
959                return true;
960            }
961
962            // Check edge intersections
963            let rect_edges = [
964                (top_left, Vec2::new(bottom_right.x, top_left.y)), // Top
965                (Vec2::new(bottom_right.x, top_left.y), bottom_right), // Right
966                (bottom_right, Vec2::new(top_left.x, bottom_right.y)), // Bottom
967                (Vec2::new(top_left.x, bottom_right.y), top_left), // Left
968            ];
969
970            rect_edges
971                .iter()
972                .any(|&(p1, p2)| line_segments_intersect(a, b, p1, p2))
973        }
974
975        /// Check if two line segments intersect
976        fn line_segments_intersect(
977            p1: Vec2<f32>,
978            p2: Vec2<f32>,
979            q1: Vec2<f32>,
980            q2: Vec2<f32>,
981        ) -> bool {
982            fn ccw(a: Vec2<f32>, b: Vec2<f32>, c: Vec2<f32>) -> bool {
983                (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x)
984            }
985
986            ccw(p1, q1, q2) != ccw(p2, q1, q2) && ccw(p1, p2, q1) != ccw(p1, p2, q2)
987        }
988
989        // Check vertices
990        for vertex in &map.vertices {
991            if vertex.intersects_vertical_slice(self.editing_slice, self.editing_slice_height) {
992                if let Some(vertex_pos) = map.get_vertex(vertex.id) {
993                    if point_in_rectangle(vertex_pos, top_left, bottom_right) {
994                        selection.0.push(vertex.id);
995                    }
996                }
997            }
998        }
999
1000        // Check linedefs
1001        for linedef in &map.linedefs {
1002            if linedef.intersects_vertical_slice(map, self.editing_slice, self.editing_slice_height)
1003            {
1004                let start_vertex = map.get_vertex(linedef.start_vertex);
1005                let end_vertex = map.get_vertex(linedef.end_vertex);
1006
1007                if let (Some(start_vertex), Some(end_vertex)) = (start_vertex, end_vertex) {
1008                    let start_pos = start_vertex;
1009                    let end_pos = end_vertex;
1010
1011                    // Check if either endpoint is inside the rectangle
1012                    if point_in_rectangle(start_pos, top_left, bottom_right)
1013                        || point_in_rectangle(end_pos, top_left, bottom_right)
1014                    {
1015                        selection.1.push(linedef.id);
1016                    }
1017                }
1018            }
1019        }
1020
1021        // Check sectors
1022        // fn point_in_polygon(point: Vec2f, polygon: &[Vec2f]) -> bool {
1023        //     let mut inside = false;
1024        //     let mut j = polygon.len() - 1;
1025
1026        //     for i in 0..polygon.len() {
1027        //         if (polygon[i].y > point.y) != (polygon[j].y > point.y)
1028        //             && point.x
1029        //                 < (polygon[j].x - polygon[i].x) * (point.y - polygon[i].y)
1030        //                     / (polygon[j].y - polygon[i].y)
1031        //                     + polygon[i].x
1032        //         {
1033        //             inside = !inside;
1034        //         }
1035        //         j = i;
1036        //     }
1037
1038        //     inside
1039        // }
1040
1041        for sector in &map.sectors {
1042            if sector.intersects_vertical_slice(map, self.editing_slice, self.editing_slice_height)
1043            {
1044                let mut vertices = Vec::new();
1045                for &linedef_id in &sector.linedefs {
1046                    if let Some(linedef) = map.find_linedef(linedef_id) {
1047                        if let Some(start_vertex) = map.find_vertex(linedef.start_vertex) {
1048                            let vertex = start_vertex.as_vec2();
1049
1050                            // Add the vertex to the list if it isn't already there
1051                            if vertices.last() != Some(&vertex) {
1052                                vertices.push(vertex);
1053                            }
1054                        }
1055                    }
1056                }
1057
1058                // Check if any part of the sector polygon is in the rectangle
1059                if vertices
1060                    .iter()
1061                    .any(|v| point_in_rectangle(*v, top_left, bottom_right))
1062                    || vertices.windows(2).any(|pair| {
1063                        // For edges, check if they intersect the rectangle
1064                        let (a, b) = (pair[0], pair[1]);
1065                        line_intersects_rectangle(a, b, top_left, bottom_right)
1066                    })
1067                {
1068                    selection.2.push(sector.id);
1069                }
1070            }
1071        }
1072
1073        selection
1074    }
1075
1076    /// Returns false if the hover is empty
1077    pub fn hover_is_empty(&self) -> bool {
1078        self.hover.0.is_none() && self.hover.1.is_none() && self.hover.2.is_none()
1079    }
1080
1081    /// Converts the hover into arrays.
1082    pub fn hover_to_arrays(&self) -> (Vec<u32>, Vec<u32>, Vec<u32>) {
1083        let mut arrays: (Vec<u32>, Vec<u32>, Vec<u32>) = (vec![], vec![], vec![]);
1084        if let Some(v) = self.hover.0 {
1085            arrays.0.push(v);
1086        }
1087        if let Some(l) = self.hover.1 {
1088            arrays.1.push(l);
1089        }
1090        if let Some(s) = self.hover.2 {
1091            arrays.2.push(s);
1092        }
1093        arrays
1094    }
1095
1096    /// Adds the given interactions provided by a server tick to the context.
1097    pub fn add_interactions(&mut self, interactions: Vec<Interaction>) {
1098        for interaction in interactions {
1099            if let Some(interactions) = self.interactions.get_mut(&interaction.to) {
1100                interactions.push(interaction);
1101            } else {
1102                self.interactions.insert(interaction.to, vec![interaction]);
1103            }
1104        }
1105    }
1106}