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