1use crate::prelude::*;
2use rusterix::DungeonTileKind;
3use rusterix::Surface;
4pub use rusterix::{Value, VertexBlendPreset, map::*};
5use scenevm::GeoId;
6use theframework::prelude::*;
7
8#[derive(PartialEq, Clone, Copy, Debug)]
13pub enum PixelEditingContext {
14 None,
16 Tile(Uuid, usize),
18 AvatarFrame(Uuid, Uuid, usize, usize),
20}
21
22impl Default for PixelEditingContext {
23 fn default() -> Self {
24 Self::None
25 }
26}
27
28#[derive(PartialEq, Clone, Copy, Debug)]
29pub enum AvatarAnchorEditSlot {
30 None,
31 Main,
32 Off,
33}
34
35impl Default for AvatarAnchorEditSlot {
36 fn default() -> Self {
37 Self::None
38 }
39}
40
41impl PixelEditingContext {
42 pub fn get_draw_color(
46 &self,
47 palette: &ThePalette,
48 opacity: f32,
49 body_marker_color: Option<[u8; 4]>,
50 ) -> Option<[u8; 4]> {
51 match self {
52 PixelEditingContext::None => None,
53 PixelEditingContext::Tile(..) => {
54 if let Some(color) = palette.get_current_color() {
55 let mut arr = color.to_u8_array();
56 arr[3] = (arr[3] as f32 * opacity) as u8;
57 Some(arr)
58 } else {
59 None
60 }
61 }
62 PixelEditingContext::AvatarFrame(..) => body_marker_color,
63 }
64 }
65
66 pub fn get_frame_count(&self, project: &Project) -> usize {
68 match self {
69 PixelEditingContext::None => 0,
70 PixelEditingContext::Tile(tile_id, _) => {
71 project.tiles.get(tile_id).map_or(0, |t| t.textures.len())
72 }
73 PixelEditingContext::AvatarFrame(avatar_id, anim_id, persp_index, _) => project
74 .avatars
75 .get(avatar_id)
76 .and_then(|a| a.animations.iter().find(|anim| anim.id == *anim_id))
77 .and_then(|anim| anim.perspectives.get(*persp_index))
78 .map_or(0, |p| p.frames.len()),
79 }
80 }
81
82 pub fn with_frame(&self, frame_index: usize) -> Self {
84 match *self {
85 PixelEditingContext::None => PixelEditingContext::None,
86 PixelEditingContext::Tile(tile_id, _) => {
87 PixelEditingContext::Tile(tile_id, frame_index)
88 }
89 PixelEditingContext::AvatarFrame(avatar_id, anim_id, persp_index, _) => {
90 PixelEditingContext::AvatarFrame(avatar_id, anim_id, persp_index, frame_index)
91 }
92 }
93 }
94}
95
96#[derive(PartialEq, Clone, Copy, Debug)]
97pub enum GizmoMode {
98 XZ,
99 XY,
100 YZ,
101}
102
103#[derive(PartialEq, Clone, Copy, Debug)]
104pub enum GeometryEditMode {
105 Geometry,
106 Detail,
107}
108
109#[derive(PartialEq, Clone, Copy, Debug)]
110pub enum EditorViewMode {
111 D2,
112 Orbit,
113 Iso,
114 FirstP,
115}
116
117impl EditorViewMode {
118 pub fn to_index(&self) -> i32 {
119 match self {
120 EditorViewMode::D2 => 0,
121 EditorViewMode::Orbit => 1,
122 EditorViewMode::Iso => 2,
123 EditorViewMode::FirstP => 3,
124 }
125 }
126 pub fn from_index(idx: i32) -> Self {
127 match idx {
128 1 => EditorViewMode::Orbit,
129 2 => EditorViewMode::Iso,
130 3 => EditorViewMode::FirstP,
131 _ => EditorViewMode::D2,
132 }
133 }
134
135 pub fn is_3d(&self) -> bool {
136 match self {
137 EditorViewMode::D2 => false,
138 _ => true,
139 }
140 }
141}
142
143#[derive(PartialEq, Clone, Copy, Debug)]
144pub enum EditingGeoFilter {
145 All,
146 DungeonOnly,
147}
148
149impl EditingGeoFilter {
150 pub fn to_index(&self) -> i32 {
151 match self {
152 EditingGeoFilter::All => 0,
153 EditingGeoFilter::DungeonOnly => 1,
154 }
155 }
156
157 pub fn from_index(idx: i32) -> Self {
158 match idx {
159 1 => EditingGeoFilter::DungeonOnly,
160 _ => EditingGeoFilter::All,
161 }
162 }
163}
164
165#[derive(PartialEq, Clone, Copy, Debug)]
166pub enum ContentContext {
167 Unknown,
168 CharacterInstance(Uuid),
169 ItemInstance(Uuid),
170 Sector(Uuid),
171 CharacterTemplate(Uuid),
172 ItemTemplate(Uuid),
173 Shader(Uuid),
174}
175
176#[derive(PartialEq, Clone, Copy, Debug)]
177pub enum ProjectContext {
178 Unknown,
179 Region(Uuid),
180 RegionSettings(Uuid),
181 RegionCharacterInstance(Uuid, Uuid),
182 RegionItemInstance(Uuid, Uuid),
183 Character(Uuid),
184 CharacterVisualCode(Uuid),
185 CharacterCode(Uuid),
186 CharacterData(Uuid),
187 CharacterPreviewRigging(Uuid),
188 Item(Uuid),
189 ItemVisualCode(Uuid),
190 ItemCode(Uuid),
191 ItemData(Uuid),
192 Tilemap(Uuid),
193 Screen(Uuid),
194 ScreenWidget(Uuid, Uuid),
195 Asset(Uuid),
196 Avatar(Uuid),
197 AvatarAnimation(Uuid, Uuid, usize),
198 ProjectSettings,
199 GameRules,
200 GameLocales,
201 GameAudioFx,
202 GameAuthoring,
203 DebugLog,
204 Console,
205}
206
207impl ProjectContext {
208 pub fn id(self) -> Option<Uuid> {
209 match self {
210 ProjectContext::Unknown
211 | ProjectContext::ProjectSettings
212 | ProjectContext::GameRules
213 | ProjectContext::GameLocales
214 | ProjectContext::GameAudioFx
215 | ProjectContext::GameAuthoring
216 | ProjectContext::DebugLog
217 | ProjectContext::Console => None,
218 ProjectContext::Region(id)
219 | ProjectContext::RegionSettings(id)
220 | ProjectContext::RegionCharacterInstance(id, _)
221 | ProjectContext::RegionItemInstance(id, _)
222 | ProjectContext::Character(id)
223 | ProjectContext::CharacterVisualCode(id)
224 | ProjectContext::CharacterCode(id)
225 | ProjectContext::CharacterData(id)
226 | ProjectContext::CharacterPreviewRigging(id)
227 | ProjectContext::Item(id)
228 | ProjectContext::ItemVisualCode(id)
229 | ProjectContext::ItemCode(id)
230 | ProjectContext::ItemData(id)
231 | ProjectContext::Tilemap(id)
232 | ProjectContext::Screen(id)
233 | ProjectContext::ScreenWidget(id, _)
234 | ProjectContext::Asset(id)
235 | ProjectContext::Avatar(id)
236 | ProjectContext::AvatarAnimation(id, _, _) => Some(id),
237 }
238 }
239
240 pub fn is_region(&self) -> bool {
241 match self {
242 ProjectContext::Region(_)
243 | ProjectContext::RegionSettings(_)
244 | ProjectContext::RegionCharacterInstance(_, _)
245 | ProjectContext::RegionItemInstance(_, _) => true,
246 _ => false,
247 }
248 }
249
250 pub fn get_region_character_instance_id(&self) -> Option<Uuid> {
251 match self {
252 ProjectContext::RegionCharacterInstance(_, instance_id) => Some(*instance_id),
253 _ => None,
254 }
255 }
256
257 pub fn get_region_item_instance_id(&self) -> Option<Uuid> {
258 match self {
259 ProjectContext::RegionItemInstance(_, instance_id) => Some(*instance_id),
260 _ => None,
261 }
262 }
263
264 pub fn is_character(&self) -> bool {
265 match self {
266 ProjectContext::Character(_)
267 | ProjectContext::CharacterVisualCode(_)
268 | ProjectContext::CharacterCode(_)
269 | ProjectContext::CharacterData(_)
270 | ProjectContext::CharacterPreviewRigging(_) => true,
271 _ => false,
272 }
273 }
274
275 pub fn is_item(&self) -> bool {
276 match self {
277 ProjectContext::Item(_)
278 | ProjectContext::ItemVisualCode(_)
279 | ProjectContext::ItemCode(_)
280 | ProjectContext::ItemData(_) => true,
281 _ => false,
282 }
283 }
284
285 pub fn is_tilemap(&self) -> bool {
286 match self {
287 ProjectContext::Tilemap(_) => true,
288 _ => false,
289 }
290 }
291
292 pub fn is_screen(&self) -> bool {
293 match self {
294 ProjectContext::Screen(_) | ProjectContext::ScreenWidget(_, _) => true,
295 _ => false,
296 }
297 }
298
299 pub fn get_screen_widget_id(&self) -> Option<Uuid> {
300 match self {
301 ProjectContext::ScreenWidget(_, widget_id) => Some(*widget_id),
302 _ => None,
303 }
304 }
305
306 pub fn is_asset(&self) -> bool {
307 match self {
308 ProjectContext::Asset(_) => true,
309 _ => false,
310 }
311 }
312
313 pub fn is_project_settings(&self) -> bool {
314 match self {
315 ProjectContext::ProjectSettings => true,
316 _ => false,
317 }
318 }
319
320 pub fn is_game_rules(&self) -> bool {
321 match self {
322 ProjectContext::GameRules => true,
323 _ => false,
324 }
325 }
326
327 pub fn is_game_locales(&self) -> bool {
328 match self {
329 ProjectContext::GameLocales => true,
330 _ => false,
331 }
332 }
333
334 pub fn is_game_audio_fx(&self) -> bool {
335 match self {
336 ProjectContext::GameAudioFx => true,
337 _ => false,
338 }
339 }
340
341 pub fn is_game_authoring(&self) -> bool {
342 match self {
343 ProjectContext::GameAuthoring => true,
344 _ => false,
345 }
346 }
347
348 pub fn has_custom_map(&self) -> bool {
349 match self {
350 ProjectContext::Screen(_) => true,
351 _ => false,
352 }
353 }
354}
355
356#[derive(PartialEq, Clone, Copy, Debug)]
357pub enum MapContext {
358 Region,
359 Screen,
360 Character,
361 Item,
362}
363
364pub struct ServerContext {
366 pub tree_regions_id: Uuid,
368 pub tree_characters_id: Uuid,
369 pub tree_items_id: Uuid,
370 pub tree_tilemaps_id: Uuid,
371 pub tree_screens_id: Uuid,
372 pub tree_avatars_id: Uuid,
373 pub tree_assets_id: Uuid,
374 pub tree_assets_fonts_id: Uuid,
375 pub tree_assets_audio_id: Uuid,
376 pub tree_palette_id: Uuid,
377 pub tree_settings_id: Uuid,
378
379 pub curr_region: Uuid,
381
382 pub curr_region_content: ContentContext,
384
385 pub curr_character: ContentContext,
387
388 pub curr_item: ContentContext,
390
391 pub cc: ContentContext,
393
394 pub pc: ProjectContext,
396
397 pub curr_grid_id: Option<Uuid>,
399
400 pub curr_screen: Uuid,
402
403 pub interactions: FxHashMap<Uuid, Vec<Interaction>>,
405
406 pub curr_tile_id: Option<Uuid>,
408
409 pub curr_tile_source: Option<TileSource>,
411
412 pub tile_node_group_id: Option<Uuid>,
414
415 pub curr_builder_graph_id: Option<Uuid>,
417
418 pub curr_dungeon_tile: DungeonTileKind,
420
421 pub prev_dungeon_dock: Option<String>,
423
424 pub curr_dungeon_floor_base: f32,
426
427 pub curr_dungeon_height: f32,
429
430 pub curr_dungeon_create_floor: bool,
432
433 pub curr_dungeon_create_ceiling: bool,
435
436 pub curr_dungeon_standalone: bool,
438
439 pub curr_dungeon_tile_span: i32,
441
442 pub curr_dungeon_tile_depth: f32,
444
445 pub curr_dungeon_tile_height: f32,
447
448 pub curr_dungeon_tile_open_mode: i32,
450
451 pub curr_dungeon_tile_item: String,
453
454 pub curr_dungeon_stair_target_floor_base: f32,
456
457 pub curr_dungeon_stair_steps: i32,
459
460 pub curr_dungeon_stair_tile_id: String,
462
463 pub curr_dungeon_stair_tile_mode: i32,
465
466 pub curr_dungeon_render_toml: String,
468
469 pub curr_dungeon_render_enabled: bool,
471
472 pub curr_dungeon_render_transition_seconds: f32,
474
475 pub curr_dungeon_render_sun_enabled: bool,
477
478 pub curr_dungeon_render_shadow_enabled: bool,
480
481 pub curr_dungeon_render_fog_density: f32,
483
484 pub curr_dungeon_render_fog_color: String,
486
487 pub prev_dungeon_subdivisions: Option<f32>,
489
490 pub builder_tool_active: bool,
492 pub palette_tool_active: bool,
494
495 pub curr_tile_frame_index: usize,
497
498 pub palette_opacity: f32,
500
501 pub curr_model_id: Option<Uuid>,
503
504 pub curr_material_id: Option<Uuid>,
506
507 pub screen_editor_mode_foreground: bool,
509
510 pub hover: (Option<u32>, Option<u32>, Option<u32>),
512
513 pub hover_cursor: Option<Vec2<f32>>,
515
516 pub hover_cursor_3d: Option<Vec3<f32>>,
518 pub hover_ray_dir_3d: Option<Vec3<f32>>,
520
521 pub hover_surface: Option<Surface>,
523 pub hover_surface_hit_pos: Option<Vec3<f32>>,
525 pub active_detail_surface: Option<Surface>,
527
528 pub hover_height: Option<f32>,
530
531 pub curr_map_tool_type: MapToolType,
533
534 curr_map_context: MapContext,
536
537 pub content_click_from_map: bool,
539
540 pub no_rect_geo_on_map: bool,
542
543 pub profile_view: Option<u32>,
545
546 pub editing_surface: Option<Surface>,
548 pub editing_surface_hit_pos: Option<Vec3<f32>>,
550
551 pub curr_action_id: Option<Uuid>,
553 pub auto_action: bool,
555
556 pub moved_entities: FxHashMap<Uuid, (Vec3<f32>, Vec3<f32>)>,
558
559 pub moved_items: FxHashMap<Uuid, (Vec3<f32>, Vec3<f32>)>,
561
562 pub rotated_entities: FxHashMap<Uuid, (Vec2<f32>, Vec2<f32>)>,
564
565 pub selected_wall_row: Option<i32>,
567
568 pub editor_view_mode: EditorViewMode,
570 pub prev_dungeon_view_mode: Option<EditorViewMode>,
572
573 pub world_mode: bool,
575
576 pub game_mode: bool,
578
579 pub clipboard: Map,
581
582 pub paste_clipboard: Option<Map>,
584
585 pub background_progress: Option<String>,
587
588 pub character_region_override: bool,
590
591 pub item_region_override: bool,
593
594 pub animation_counter: usize,
596
597 pub geo_hit: Option<GeoId>,
599
600 pub geo_hit_pos: Vec3<f32>,
602
603 pub editing_pos_buffer: Option<Vec3<f32>>,
605 pub editing_view_pos_by_map: FxHashMap<(Uuid, i32), Vec3<f32>>,
607 pub editing_view_look_by_map: FxHashMap<(Uuid, i32), Vec3<f32>>,
609 pub editing_view_2d_by_map: FxHashMap<Uuid, (Vec2<f32>, f32)>,
611 pub editing_view_iso_scale_by_map: FxHashMap<Uuid, f32>,
613
614 pub selected_hud_icon_index: i32,
616
617 pub show_editing_geometry: bool,
619
620 pub editing_geo_filter: EditingGeoFilter,
622 pub dungeon_no_ceiling: bool,
624
625 pub editing_slice: f32,
627 pub editing_slice_height: f32,
629
630 pub gizmo_mode: GizmoMode,
632 pub geometry_edit_mode: GeometryEditMode,
634
635 pub rect_sector_id_3d: Option<u32>,
637 pub rect_tile_id_3d: (i32, i32),
638 pub rect_terrain_id: Option<(i32, i32)>,
639 pub rect_blend_preset: VertexBlendPreset,
640
641 pub game_input_mode: bool,
643
644 pub text_game_mode: bool,
646
647 pub help_mode: bool,
649
650 pub editing_ctx: PixelEditingContext,
652
653 pub body_marker_color: Option<[u8; 4]>,
655 pub avatar_anchor_slot: AvatarAnchorEditSlot,
657}
658
659impl Default for ServerContext {
660 fn default() -> Self {
661 Self::new()
662 }
663}
664
665impl ServerContext {
666 pub fn new() -> Self {
667 Self {
668 curr_region: Uuid::nil(),
669
670 tree_regions_id: Uuid::new_v4(),
671 tree_characters_id: Uuid::new_v4(),
672 tree_items_id: Uuid::new_v4(),
673 tree_tilemaps_id: Uuid::new_v4(),
674 tree_screens_id: Uuid::new_v4(),
675 tree_avatars_id: Uuid::new_v4(),
676 tree_assets_id: Uuid::new_v4(),
677 tree_assets_fonts_id: Uuid::new_v4(),
678 tree_assets_audio_id: Uuid::new_v4(),
679 tree_palette_id: Uuid::new_v4(),
680 tree_settings_id: Uuid::new_v4(),
681
682 curr_region_content: ContentContext::Unknown,
683 curr_character: ContentContext::Unknown,
684 curr_item: ContentContext::Unknown,
685 cc: ContentContext::Unknown,
686
687 pc: ProjectContext::Unknown,
688
689 curr_grid_id: None,
690
691 curr_screen: Uuid::nil(),
692
693 interactions: FxHashMap::default(),
694
695 curr_tile_id: None,
696 curr_tile_source: None,
697 tile_node_group_id: None,
698 curr_builder_graph_id: None,
699 curr_dungeon_tile: DungeonTileKind::FLOOR,
700 prev_dungeon_dock: None,
701 curr_dungeon_floor_base: 0.0,
702 curr_dungeon_height: 4.0,
703 curr_dungeon_create_floor: true,
704 curr_dungeon_create_ceiling: true,
705 curr_dungeon_standalone: false,
706 curr_dungeon_tile_span: 2,
707 curr_dungeon_tile_depth: 0.5,
708 curr_dungeon_tile_height: 2.25,
709 curr_dungeon_tile_open_mode: 0,
710 curr_dungeon_tile_item: "Door Handler".to_string(),
711 curr_dungeon_stair_target_floor_base: -1.0,
712 curr_dungeon_stair_steps: 4,
713 curr_dungeon_stair_tile_id: String::new(),
714 curr_dungeon_stair_tile_mode: 1,
715 curr_dungeon_render_toml: "[render]\ntransition_seconds = 1.0\nsun_enabled = false\nshadow_enabled = true\nfog_density = 5.0\nfog_color = \"#000000\"\n".to_string(),
716 curr_dungeon_render_enabled: true,
717 curr_dungeon_render_transition_seconds: 1.0,
718 curr_dungeon_render_sun_enabled: false,
719 curr_dungeon_render_shadow_enabled: true,
720 curr_dungeon_render_fog_density: 5.0,
721 curr_dungeon_render_fog_color: "#000000".to_string(),
722 prev_dungeon_subdivisions: None,
723 builder_tool_active: false,
724 palette_tool_active: false,
725 curr_tile_frame_index: 0,
726
727 palette_opacity: 1.0,
728
729 curr_model_id: None,
730 curr_material_id: None,
731
732 screen_editor_mode_foreground: false,
733
734 hover: (None, None, None),
735 hover_cursor: None,
736 hover_cursor_3d: None,
737 hover_ray_dir_3d: None,
738 hover_surface: None,
739 hover_surface_hit_pos: None,
740 active_detail_surface: None,
741 hover_height: None,
742
743 curr_map_tool_type: MapToolType::Linedef,
744 curr_map_context: MapContext::Region,
745
746 content_click_from_map: false,
747 no_rect_geo_on_map: true,
748 profile_view: None,
749
750 editing_surface: None,
751 editing_surface_hit_pos: None,
752 curr_action_id: None,
753 auto_action: false,
754
755 moved_entities: FxHashMap::default(),
756 moved_items: FxHashMap::default(),
757 rotated_entities: FxHashMap::default(),
758
759 selected_wall_row: Some(0),
760
761 editor_view_mode: EditorViewMode::D2,
762 prev_dungeon_view_mode: None,
763
764 world_mode: false,
765 game_mode: false,
766
767 clipboard: Map::default(),
768 paste_clipboard: None,
769
770 background_progress: None,
771
772 character_region_override: true,
773 item_region_override: true,
774
775 animation_counter: 0,
776
777 geo_hit: None,
778 geo_hit_pos: Vec3::zero(),
779
780 editing_pos_buffer: None,
781 editing_view_pos_by_map: FxHashMap::default(),
782 editing_view_look_by_map: FxHashMap::default(),
783 editing_view_2d_by_map: FxHashMap::default(),
784 editing_view_iso_scale_by_map: FxHashMap::default(),
785
786 selected_hud_icon_index: 0,
787 show_editing_geometry: true,
788 editing_geo_filter: EditingGeoFilter::All,
789 dungeon_no_ceiling: false,
790
791 gizmo_mode: GizmoMode::XZ,
792 geometry_edit_mode: GeometryEditMode::Geometry,
793
794 editing_slice: 0.0,
795 editing_slice_height: 2.0,
796 rect_sector_id_3d: None,
797 rect_tile_id_3d: (0, 0),
798 rect_terrain_id: None,
799 rect_blend_preset: VertexBlendPreset::Solid,
800
801 game_input_mode: false,
802 text_game_mode: false,
803 help_mode: false,
804
805 editing_ctx: PixelEditingContext::None,
806 body_marker_color: None,
807 avatar_anchor_slot: AvatarAnchorEditSlot::None,
808 }
809 }
810
811 pub fn polyview_has_focus(&self, ctx: &TheContext) -> bool {
813 if let Some(focus) = &ctx.ui.focus {
814 if focus.name == "PolyView" {
815 return true;
816 }
817 }
818 false
819 }
820
821 pub fn get_map_context(&self) -> MapContext {
823 if self.pc.is_screen() {
824 return MapContext::Screen;
825 }
826
827 MapContext::Region
828
829 }
839
840 pub fn set_map_context(&mut self, map_context: MapContext) {
841 self.curr_map_context = map_context;
842 }
843
844 #[inline]
845 fn view_key(map_id: Uuid, mode: EditorViewMode) -> (Uuid, i32) {
846 (map_id, mode.to_index())
847 }
848
849 pub fn store_edit_view_for_map(
850 &mut self,
851 map_id: Uuid,
852 mode: EditorViewMode,
853 pos: Vec3<f32>,
854 look: Vec3<f32>,
855 ) {
856 if mode == EditorViewMode::D2 {
857 return;
858 }
859 let key = Self::view_key(map_id, mode);
860 self.editing_view_pos_by_map.insert(key, pos);
861 self.editing_view_look_by_map.insert(key, look);
862 }
863
864 pub fn load_edit_view_for_map(
865 &self,
866 map_id: Uuid,
867 mode: EditorViewMode,
868 ) -> Option<(Vec3<f32>, Vec3<f32>)> {
869 if mode == EditorViewMode::D2 {
870 return None;
871 }
872 let key = Self::view_key(map_id, mode);
873 let pos = self.editing_view_pos_by_map.get(&key).copied()?;
874 let look = self.editing_view_look_by_map.get(&key).copied()?;
875 Some((pos, look))
876 }
877
878 pub fn store_edit_view_2d_for_map(&mut self, map_id: Uuid, offset: Vec2<f32>, grid_size: f32) {
879 self.editing_view_2d_by_map
880 .insert(map_id, (offset, grid_size));
881 }
882
883 pub fn load_edit_view_2d_for_map(&self, map_id: Uuid) -> Option<(Vec2<f32>, f32)> {
884 self.editing_view_2d_by_map.get(&map_id).copied()
885 }
886
887 pub fn store_edit_view_iso_scale_for_map(&mut self, map_id: Uuid, scale: f32) {
888 self.editing_view_iso_scale_by_map.insert(map_id, scale);
889 }
890
891 pub fn load_edit_view_iso_scale_for_map(&self, map_id: Uuid) -> Option<f32> {
892 self.editing_view_iso_scale_by_map.get(&map_id).copied()
893 }
894
895 pub fn clear(&mut self) {
897 self.curr_region_content = ContentContext::Unknown;
898 self.curr_character = ContentContext::Unknown;
899 self.curr_item = ContentContext::Unknown;
900 self.cc = ContentContext::Unknown;
901
902 self.curr_region = Uuid::nil();
903 self.curr_grid_id = None;
904 self.curr_screen = Uuid::nil();
905 self.tile_node_group_id = None;
906 self.curr_builder_graph_id = None;
907 self.builder_tool_active = false;
908 self.palette_tool_active = false;
909 self.interactions.clear();
910 self.moved_entities.clear();
911 self.moved_items.clear();
912 self.editing_view_pos_by_map.clear();
913 self.editing_view_look_by_map.clear();
914 self.editing_view_2d_by_map.clear();
915 self.editing_view_iso_scale_by_map.clear();
916 }
917
918 pub fn clear_interactions(&mut self) {
919 self.interactions.clear();
920 }
921
922 pub fn local_to_map_grid(
924 &self,
925 screen_size: Vec2<f32>,
926 coord: Vec2<f32>,
927 map: &Map,
928 subdivisions: f32,
929 ) -> Vec2<f32> {
930 let grid_space_pos = coord - screen_size / 2.0 - Vec2::new(map.offset.x, -map.offset.y);
931 let snapped = grid_space_pos / map.grid_size;
932 let rounded = snapped.map(|x| x.round());
933
934 if subdivisions > 1.0 {
935 let subdivision_size = 1.0 / subdivisions;
936 let fractional = snapped - rounded;
938 rounded + fractional.map(|x| (x / subdivision_size).round() * subdivision_size)
940 } else {
941 rounded
942 }
943 }
944
945 fn snap_scalar(value: f32, subdivisions: f32) -> f32 {
946 let step = 1.0 / subdivisions.max(1.0);
947 (value / step).round() * step
948 }
949
950 pub fn snap_world_point_for_edit(&self, map: &Map, point: Vec3<f32>) -> Vec3<f32> {
951 if self.geometry_edit_mode == GeometryEditMode::Detail
952 && let Some(surface) = self.active_detail_surface.as_ref().or(self
953 .hover_surface
954 .as_ref()
955 .or(self.editing_surface.as_ref()))
956 {
957 let uv = surface.world_to_uv(point);
958 let snapped_uv = Vec2::new(
959 Self::snap_scalar(uv.x, map.subdivisions),
960 Self::snap_scalar(uv.y, map.subdivisions),
961 );
962 return surface.uv_to_world(snapped_uv);
963 }
964
965 Vec3::new(
966 Self::snap_scalar(point.x, map.subdivisions),
967 Self::snap_scalar(point.y, map.subdivisions),
968 Self::snap_scalar(point.z, map.subdivisions),
969 )
970 }
971
972 pub fn local_to_map_cell(
974 &self,
975 screen_size: Vec2<f32>,
976 coord: Vec2<f32>,
977 map: &Map,
978 subdivisions: f32,
979 ) -> Vec2<f32> {
980 let grid_space_pos = coord - screen_size / 2.0 - Vec2::new(map.offset.x, -map.offset.y);
981 let grid_cell = (grid_space_pos / map.grid_size).map(|x| x.floor());
982
983 if subdivisions > 1.0 {
984 let sub_cell_size = map.grid_size / subdivisions;
985 let sub_index = grid_space_pos
986 .map(|x| x.rem_euclid(map.grid_size) / sub_cell_size)
987 .map(|x| x.floor());
988 grid_cell + sub_index / subdivisions
989 } else {
990 grid_cell
991 }
992 }
993
994 pub fn map_grid_to_local(screen_size: Vec2<f32>, grid_pos: Vec2<f32>, map: &Map) -> Vec2<f32> {
996 let grid_space_pos = grid_pos * map.grid_size;
997 grid_space_pos + Vec2::new(map.offset.x, -map.offset.y) + screen_size / 2.0
998 }
999
1000 pub fn center_map_at_grid_pos(
1002 &mut self,
1003 _screen_size: Vec2<f32>,
1004 grid_pos: Vec2<f32>,
1005 map: &mut Map,
1006 ) {
1007 let pixel_pos = grid_pos * map.grid_size;
1008 map.offset.x = -(pixel_pos.x);
1009 map.offset.y = pixel_pos.y;
1010 }
1011
1012 pub fn geometry_at(
1014 &self,
1015 screen_size: Vec2<f32>,
1016 screen_pos: Vec2<f32>,
1017 map: &Map,
1018 ) -> (Option<u32>, Option<u32>, Option<u32>) {
1019 let mut selection: (Option<u32>, Option<u32>, Option<u32>) = (None, None, None);
1020 let hover_threshold = 6.0;
1021
1022 for vertex in &map.vertices {
1024 if vertex.intersects_vertical_slice(self.editing_slice, self.editing_slice_height) {
1025 if let Some(vertex_pos) = map.get_vertex(vertex.id) {
1026 let vertex_pos = Self::map_grid_to_local(screen_size, vertex_pos, map);
1027 if (screen_pos - vertex_pos).magnitude() <= hover_threshold {
1028 selection.0 = Some(vertex.id);
1029 break;
1030 }
1031 }
1032 }
1033 }
1034
1035 for linedef in &map.linedefs {
1037 if linedef.intersects_vertical_slice(map, self.editing_slice, self.editing_slice_height)
1038 {
1039 if self.no_rect_geo_on_map && map.is_linedef_in_rect(linedef.id) {
1040 continue;
1041 }
1042
1043 let start_vertex = map.get_vertex(linedef.start_vertex);
1044 let end_vertex = map.get_vertex(linedef.end_vertex);
1045
1046 if let Some(start_vertex) = start_vertex {
1047 if let Some(end_vertex) = end_vertex {
1048 let start_pos = Self::map_grid_to_local(screen_size, start_vertex, map);
1049 let end_pos = Self::map_grid_to_local(screen_size, end_vertex, map);
1050
1051 let line_vec = end_pos - start_pos;
1053 let mouse_vec = screen_pos - start_pos;
1054 let line_vec_length = line_vec.magnitude();
1055 let projection =
1056 mouse_vec.dot(line_vec) / (line_vec_length * line_vec_length);
1057 let closest_point = start_pos + projection.clamp(0.0, 1.0) * line_vec;
1058 let distance = (screen_pos - closest_point).magnitude();
1059
1060 if distance <= hover_threshold {
1061 selection.1 = Some(linedef.id);
1062 break;
1063 }
1064 }
1065 }
1066 }
1067 }
1068
1069 fn point_in_polygon(point: Vec2<f32>, polygon: &[Vec2<f32>]) -> bool {
1073 let mut inside = false;
1074 let mut j = polygon.len() - 1;
1075
1076 for i in 0..polygon.len() {
1077 if (polygon[i].y > point.y) != (polygon[j].y > point.y)
1078 && point.x
1079 < (polygon[j].x - polygon[i].x) * (point.y - polygon[i].y)
1080 / (polygon[j].y - polygon[i].y)
1081 + polygon[i].x
1082 {
1083 inside = !inside;
1084 }
1085 j = i;
1086 }
1087
1088 inside
1089 }
1090
1091 let ordered = map.sorted_sectors_by_area();
1093 for sector in ordered.iter().rev() {
1094 if sector.intersects_vertical_slice(map, self.editing_slice, self.editing_slice_height)
1095 {
1096 if self.no_rect_geo_on_map
1097 && sector.properties.contains("rect")
1098 && sector.name.is_empty()
1099 {
1100 continue;
1101 }
1102 let mut vertices = Vec::new();
1103 for &linedef_id in §or.linedefs {
1104 if let Some(linedef) = map.find_linedef(linedef_id) {
1105 if let Some(start_vertex) = map.find_vertex(linedef.start_vertex) {
1106 let vertex =
1107 Self::map_grid_to_local(screen_size, start_vertex.as_vec2(), map);
1108
1109 if vertices.last() != Some(&vertex) {
1111 vertices.push(vertex);
1112 }
1113 }
1114 }
1115 }
1116
1117 if point_in_polygon(screen_pos, &vertices) {
1118 selection.2 = Some(sector.id);
1119 return selection;
1120 }
1121 }
1122 }
1123
1124 selection
1125 }
1126
1127 pub fn geometry_in_rectangle(
1129 &self,
1130 top_left: Vec2<f32>,
1131 bottom_right: Vec2<f32>,
1132 map: &Map,
1133 ) -> (Vec<u32>, Vec<u32>, Vec<u32>) {
1134 let mut selection: (Vec<u32>, Vec<u32>, Vec<u32>) = (Vec::new(), Vec::new(), Vec::new());
1135
1136 fn point_in_rectangle(
1138 point: Vec2<f32>,
1139 top_left: Vec2<f32>,
1140 bottom_right: Vec2<f32>,
1141 ) -> bool {
1142 point.x >= top_left.x
1143 && point.x <= bottom_right.x
1144 && point.y >= top_left.y
1145 && point.y <= bottom_right.y
1146 }
1147
1148 fn line_intersects_rectangle(
1150 a: Vec2<f32>,
1151 b: Vec2<f32>,
1152 top_left: Vec2<f32>,
1153 bottom_right: Vec2<f32>,
1154 ) -> bool {
1155 let (min_x, max_x) = (a.x.min(b.x), a.x.max(b.x));
1161 let (min_y, max_y) = (a.y.min(b.y), a.y.max(b.y));
1162
1163 if min_x > bottom_right.x
1164 || max_x < top_left.x
1165 || min_y > bottom_right.y
1166 || max_y < top_left.y
1167 {
1168 return false; }
1170
1171 if point_in_rectangle(a, top_left, bottom_right)
1173 || point_in_rectangle(b, top_left, bottom_right)
1174 {
1175 return true;
1176 }
1177
1178 let rect_edges = [
1180 (top_left, Vec2::new(bottom_right.x, top_left.y)), (Vec2::new(bottom_right.x, top_left.y), bottom_right), (bottom_right, Vec2::new(top_left.x, bottom_right.y)), (Vec2::new(top_left.x, bottom_right.y), top_left), ];
1185
1186 rect_edges
1187 .iter()
1188 .any(|&(p1, p2)| line_segments_intersect(a, b, p1, p2))
1189 }
1190
1191 fn line_segments_intersect(
1193 p1: Vec2<f32>,
1194 p2: Vec2<f32>,
1195 q1: Vec2<f32>,
1196 q2: Vec2<f32>,
1197 ) -> bool {
1198 fn ccw(a: Vec2<f32>, b: Vec2<f32>, c: Vec2<f32>) -> bool {
1199 (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x)
1200 }
1201
1202 ccw(p1, q1, q2) != ccw(p2, q1, q2) && ccw(p1, p2, q1) != ccw(p1, p2, q2)
1203 }
1204
1205 for vertex in &map.vertices {
1207 if vertex.intersects_vertical_slice(self.editing_slice, self.editing_slice_height) {
1208 if let Some(vertex_pos) = map.get_vertex(vertex.id) {
1209 if point_in_rectangle(vertex_pos, top_left, bottom_right) {
1210 selection.0.push(vertex.id);
1211 }
1212 }
1213 }
1214 }
1215
1216 for linedef in &map.linedefs {
1218 if linedef.intersects_vertical_slice(map, self.editing_slice, self.editing_slice_height)
1219 {
1220 let start_vertex = map.get_vertex(linedef.start_vertex);
1221 let end_vertex = map.get_vertex(linedef.end_vertex);
1222
1223 if let (Some(start_vertex), Some(end_vertex)) = (start_vertex, end_vertex) {
1224 let start_pos = start_vertex;
1225 let end_pos = end_vertex;
1226
1227 if point_in_rectangle(start_pos, top_left, bottom_right)
1229 || point_in_rectangle(end_pos, top_left, bottom_right)
1230 {
1231 selection.1.push(linedef.id);
1232 }
1233 }
1234 }
1235 }
1236
1237 for sector in &map.sectors {
1258 if sector.intersects_vertical_slice(map, self.editing_slice, self.editing_slice_height)
1259 {
1260 let mut vertices = Vec::new();
1261 for &linedef_id in §or.linedefs {
1262 if let Some(linedef) = map.find_linedef(linedef_id) {
1263 if let Some(start_vertex) = map.find_vertex(linedef.start_vertex) {
1264 let vertex = start_vertex.as_vec2();
1265
1266 if vertices.last() != Some(&vertex) {
1268 vertices.push(vertex);
1269 }
1270 }
1271 }
1272 }
1273
1274 if vertices
1276 .iter()
1277 .any(|v| point_in_rectangle(*v, top_left, bottom_right))
1278 || vertices.windows(2).any(|pair| {
1279 let (a, b) = (pair[0], pair[1]);
1281 line_intersects_rectangle(a, b, top_left, bottom_right)
1282 })
1283 {
1284 selection.2.push(sector.id);
1285 }
1286 }
1287 }
1288
1289 selection
1290 }
1291
1292 pub fn hover_is_empty(&self) -> bool {
1294 self.hover.0.is_none() && self.hover.1.is_none() && self.hover.2.is_none()
1295 }
1296
1297 pub fn hover_to_arrays(&self) -> (Vec<u32>, Vec<u32>, Vec<u32>) {
1299 let mut arrays: (Vec<u32>, Vec<u32>, Vec<u32>) = (vec![], vec![], vec![]);
1300 if let Some(v) = self.hover.0 {
1301 arrays.0.push(v);
1302 }
1303 if let Some(l) = self.hover.1 {
1304 arrays.1.push(l);
1305 }
1306 if let Some(s) = self.hover.2 {
1307 arrays.2.push(s);
1308 }
1309 arrays
1310 }
1311
1312 pub fn add_interactions(&mut self, interactions: Vec<Interaction>) {
1314 for interaction in interactions {
1315 if let Some(interactions) = self.interactions.get_mut(&interaction.to) {
1316 interactions.push(interaction);
1317 } else {
1318 self.interactions.insert(interaction.to, vec![interaction]);
1319 }
1320 }
1321 }
1322}