Skip to main content

rustapi/
hud.rs

1use crate::editor::RUSTERIX;
2use crate::prelude::*;
3use rusterix::ShapeStack;
4use rusterix::prelude::*;
5use theframework::prelude::*;
6
7#[derive(PartialEq, Clone, Copy, Debug)]
8pub enum HudMode {
9    Selection,
10    Vertex,
11    Linedef,
12    Sector,
13    Effects,
14    Rect,
15    Terrain,
16    Entity,
17}
18
19pub struct Hud {
20    mode: HudMode,
21
22    icon_rects: Vec<TheDim>,
23
24    pub selected_icon_index: i32,
25
26    subdiv_rects: Vec<TheDim>,
27
28    mouse_pos: Vec2<i32>,
29
30    is_playing: bool,
31    light_icon: Option<TheRGBABuffer>,
32
33    // Plane picker widget for 3D mode
34    plane_picker_rects: Vec<TheDim>,
35}
36
37impl Hud {
38    pub fn new(mode: HudMode) -> Self {
39        Self {
40            mode,
41
42            icon_rects: vec![],
43            selected_icon_index: 0,
44
45            subdiv_rects: vec![],
46
47            mouse_pos: Vec2::zero(),
48
49            is_playing: false,
50            light_icon: None,
51
52            plane_picker_rects: vec![],
53        }
54    }
55
56    pub fn draw(
57        &mut self,
58        buffer: &mut TheRGBABuffer,
59        map: &mut Map,
60        ctx: &mut TheContext,
61        server_ctx: &mut ServerContext,
62        id: Option<u32>,
63        assets: &Assets,
64    ) {
65        if (self.mode == HudMode::Linedef || self.mode == HudMode::Sector)
66            && self.light_icon.is_none()
67        {
68            if let Some(li) = ctx.ui.icon("light_small") {
69                self.light_icon = Some(li.clone());
70            }
71        }
72
73        let width = buffer.dim().width as usize;
74        let height = buffer.dim().height as usize;
75        let stride = buffer.stride();
76
77        let info_height = 20;
78        let bg_color = [50, 50, 50, 255];
79        // let dark_bg_color = [30, 30, 30, 255];
80        let text_color = [150, 150, 150, 255];
81        let sel_text_color = [220, 220, 220, 255];
82
83        self.subdiv_rects = vec![];
84
85        ctx.draw.rect(
86            buffer.pixels_mut(),
87            &(0, 0, width, info_height),
88            stride,
89            &bg_color,
90        );
91
92        if let Some(v) = server_ctx.hover_cursor {
93            ctx.draw.text(
94                buffer.pixels_mut(),
95                &(10, 2),
96                stride,
97                &format!("{:.2}, {:.2}", v.x, v.y),
98                TheFontSettings {
99                    size: 13.0,
100                    ..Default::default()
101                },
102                &text_color,
103                &bg_color,
104            );
105        }
106
107        if let Some(v) = &server_ctx.background_progress {
108            ctx.draw.text(
109                buffer.pixels_mut(),
110                &(550, 2),
111                stride,
112                v,
113                TheFontSettings {
114                    size: 13.0,
115                    ..Default::default()
116                },
117                &text_color,
118                &bg_color,
119            );
120        }
121
122        // Icons
123
124        let icon_size = 40;
125        let mut icons = 0;
126
127        if server_ctx.get_map_context() == MapContext::Region {
128            icons = if self.mode == HudMode::Vertex {
129                0
130            } else if self.mode == HudMode::Linedef {
131                0
132            } else {
133                1
134            };
135        } else if server_ctx.get_map_context() == MapContext::Screen {
136            icons = if self.mode == HudMode::Sector { 2 } else { 0 };
137        }
138
139        if self.mode == HudMode::Effects
140            || self.mode == HudMode::Rect
141            || self.mode == HudMode::Terrain
142        {
143            icons = 0;
144        }
145
146        self.icon_rects.clear();
147        let x = width as i32 - (icon_size * icons) - 1;
148        for i in 0..icons {
149            let rect = TheDim::rect(x + (i * icon_size), 20, icon_size, icon_size);
150            ctx.draw.rect(
151                buffer.pixels_mut(),
152                &rect.to_buffer_utuple(),
153                stride,
154                &bg_color,
155            );
156
157            let r = rect.to_buffer_utuple();
158            ctx.draw.text_rect(
159                buffer.pixels_mut(),
160                &(r.0, 1, r.2, 19),
161                stride,
162                &self.get_icon_text(i, server_ctx),
163                TheFontSettings {
164                    size: 10.0,
165                    ..Default::default()
166                },
167                &text_color,
168                &bg_color,
169                TheHorizontalAlign::Center,
170                TheVerticalAlign::Center,
171            );
172
173            let r = &rect.to_buffer_utuple();
174            ctx.draw.rect(
175                buffer.pixels_mut(),
176                &(r.0 + 1, r.1 + 1, r.2 - 2, r.3 - 2),
177                stride,
178                &[30, 30, 30, 255],
179            );
180
181            if let Some(id) = id {
182                let (tile, has_light) = self.get_icon(i, map, id, icon_size as usize);
183                if let Some(tile) = tile {
184                    let texture = tile.textures[0].resized(icon_size as usize, icon_size as usize);
185                    ctx.draw.blend_slice(
186                        buffer.pixels_mut(),
187                        &texture.data,
188                        &rect.to_buffer_utuple(),
189                        stride,
190                    );
191                }
192                if has_light {
193                    if let Some(light_icon) = &self.light_icon {
194                        ctx.draw.blend_slice(
195                            buffer.pixels_mut(),
196                            light_icon.pixels(),
197                            &(
198                                rect.x as usize + 1,
199                                rect.y as usize + 1,
200                                light_icon.dim().width as usize,
201                                light_icon.dim().height as usize,
202                            ),
203                            stride,
204                        );
205                    }
206                }
207            }
208
209            if i == self.selected_icon_index {
210                ctx.draw.rect_outline(
211                    buffer.pixels_mut(),
212                    &rect.to_buffer_utuple(),
213                    stride,
214                    &sel_text_color,
215                );
216            }
217
218            self.icon_rects.push(rect);
219        }
220
221        // Show Subdivs
222        if (map.camera == MapCamera::TwoD || server_ctx.get_map_context() == MapContext::Screen)
223            && self.mode != HudMode::Terrain
224            && self.mode != HudMode::Rect
225        {
226            let x = 150;
227
228            let size = 20;
229            for i in 0..10 {
230                let rect = TheDim::rect(x + (i * size), 0, size, size);
231
232                let r = rect.to_buffer_utuple();
233                ctx.draw.text_rect(
234                    buffer.pixels_mut(),
235                    &(r.0, 1, r.2, 19),
236                    stride,
237                    &(i + 1).to_string(),
238                    TheFontSettings {
239                        size: 13.0,
240                        ..Default::default()
241                    },
242                    &if (i + 1) as f32 == map.subdivisions || rect.contains(self.mouse_pos) {
243                        sel_text_color
244                    } else {
245                        text_color
246                    },
247                    &bg_color,
248                    TheHorizontalAlign::Center,
249                    TheVerticalAlign::Center,
250                );
251                self.subdiv_rects.push(rect);
252            }
253        }
254
255        // Terrain: Height
256        if self.mode == HudMode::Terrain {
257            if let Some(v) = server_ctx.hover_height {
258                ctx.draw.text(
259                    buffer.pixels_mut(),
260                    &(150, 2),
261                    stride,
262                    &format!("Elevation {v:.2}"),
263                    TheFontSettings {
264                        size: 13.0,
265                        ..Default::default()
266                    },
267                    &text_color,
268                    &bg_color,
269                );
270            }
271        }
272
273        // Preview
274
275        if server_ctx.get_map_context() == MapContext::Character
276            || server_ctx.get_map_context() == MapContext::Item
277        {
278            if self.is_playing {
279                let size = 128;
280                let mut texture = Texture::alloc(size as usize, size as usize);
281
282                let mut stack = ShapeStack::new(Vec2::new(-5.0, -5.0), Vec2::new(5.0, 5.0));
283                stack.render_geometry(&mut texture, map, assets, false, &FxHashMap::default());
284
285                map.properties.set("shape", Value::Texture(texture));
286            }
287
288            if let Some(Value::Texture(texture)) = map.properties.get("shape") {
289                let w = texture.width as i32;
290                let h = texture.height as i32;
291                let preview_rect = TheDim::rect(width as i32 - w - 1, height as i32 - h - 1, w, h);
292                ctx.draw.blend_slice(
293                    buffer.pixels_mut(),
294                    &texture.data,
295                    &preview_rect.to_buffer_utuple(),
296                    stride,
297                );
298            }
299        } else if server_ctx.get_map_context() == MapContext::Screen {
300            // Show the widget previews in the icon rects
301            if let Some(id) = map.selected_sectors.get(0) {
302                if let Some(sector) = map.find_sector(*id) {
303                    if let Some(Value::Source(PixelSource::ShapeFXGraphId(id))) =
304                        sector.properties.get("screen_graph")
305                    {
306                        if let Some(graph) = map.shapefx_graphs.get(id)
307                            && self.icon_rects.len() == 2
308                        {
309                            let w = self.icon_rects[0].width;
310                            let h = self.icon_rects[0].height;
311
312                            let textures =
313                                graph.create_screen_widgets(w as usize, h as usize, assets);
314
315                            for i in 0..2 {
316                                ctx.draw.blend_slice(
317                                    buffer.pixels_mut(),
318                                    &textures[i as usize].data,
319                                    &self.icon_rects[i as usize].to_buffer_utuple(),
320                                    stride,
321                                );
322                            }
323                        }
324                    }
325                }
326            }
327
328            //let mut stack = ShapeStack::new(Vec2::new(-5.0, -5.0), Vec2::new(5.0, 5.0));
329            //stack.render_geometry(&mut texture, map, assets, false, &FxHashMap::default());
330
331            /*
332            if let Some(Value::Texture(texture)) = map.properties.get("shape") {
333                let w = texture.width as i32;
334                let h = texture.height as i32;
335                let preview_rect = TheDim::rect(width as i32 - w - 1, height as i32 - h - 1, w, h);
336                ctx.draw.copy_slice(
337                    buffer.pixels_mut(),
338                    &texture.data,
339                    &preview_rect.to_buffer_utuple(),
340                    stride,
341                );
342            }*/
343        }
344
345        // Draw plane picker widget in 3D mode when editing geometry
346        if server_ctx.editor_view_mode != EditorViewMode::D2 && server_ctx.show_editing_geometry {
347            self.draw_plane_picker(buffer, ctx, server_ctx, width, height, stride);
348        }
349    }
350
351    fn draw_plane_picker(
352        &mut self,
353        buffer: &mut TheRGBABuffer,
354        _ctx: &mut TheContext,
355        server_ctx: &ServerContext,
356        width: usize,
357        height: usize,
358        stride: usize,
359    ) {
360        self.plane_picker_rects.clear();
361
362        let widget_size = 80;
363        let widget_margin = 20;
364
365        // Position in lower-right corner
366        let x = width as i32 - widget_size - widget_margin;
367        let y = height as i32 - widget_size - widget_margin;
368
369        // Draw isometric cube with three visible planes
370        let cx = x + widget_size / 2;
371        let cy = y + widget_size / 2 + 25; // Offset down to fit in widget
372        let size = 15;
373
374        // Define the 8 corners of the isometric cube
375        // Standard isometric projection (30-degree angles)
376        let iso_x = |ix: i32, iy: i32, _iz: i32| cx + (ix - iy) * size;
377        let iso_y = |ix: i32, iy: i32, iz: i32| cy + (ix + iy) * size / 2 - iz * size;
378
379        // Cube corners (centered at origin, extending from -1 to +1 to avoid cancellation)
380        let p = [
381            (iso_x(-1, -1, 0), iso_y(-1, -1, 0)), // 0: bottom back left
382            (iso_x(1, -1, 0), iso_y(1, -1, 0)),   // 1: bottom back right
383            (iso_x(1, 1, 0), iso_y(1, 1, 0)),     // 2: bottom front right
384            (iso_x(-1, 1, 0), iso_y(-1, 1, 0)),   // 3: bottom front left
385            (iso_x(-1, -1, 2), iso_y(-1, -1, 2)), // 4: top back left
386            (iso_x(1, -1, 2), iso_y(1, -1, 2)),   // 5: top back right
387            (iso_x(1, 1, 2), iso_y(1, 1, 2)),     // 6: top front right
388            (iso_x(-1, 1, 2), iso_y(-1, 1, 2)),   // 7: top front left
389        ];
390
391        // Define colors for each plane
392        let xz_color = if server_ctx.gizmo_mode == GizmoMode::XZ {
393            [120, 200, 120, 200]
394        } else {
395            [80, 140, 80, 150]
396        };
397
398        let xy_color = if server_ctx.gizmo_mode == GizmoMode::XY {
399            [120, 120, 200, 200]
400        } else {
401            [80, 80, 140, 150]
402        };
403
404        let yz_color = if server_ctx.gizmo_mode == GizmoMode::YZ {
405            [200, 120, 120, 200]
406        } else {
407            [140, 80, 80, 150]
408        };
409
410        // Helper to fill a quad
411        let widget_bounds = (x, y, x + widget_size, y + widget_size);
412        let fill_quad = |buffer: &mut [u8],
413                         p0: (i32, i32),
414                         p1: (i32, i32),
415                         p2: (i32, i32),
416                         p3: (i32, i32),
417                         color: [u8; 4]| {
418            // Simple scanline fill
419            let points = [p0, p1, p2, p3];
420            let min_y = points.iter().map(|p| p.1).min().unwrap();
421            let max_y = points.iter().map(|p| p.1).max().unwrap();
422
423            for scan_y in min_y..=max_y {
424                // Skip if outside widget bounds
425                if scan_y < widget_bounds.1 || scan_y >= widget_bounds.3 {
426                    continue;
427                }
428
429                let mut x_intersects = Vec::new();
430                for i in 0..4 {
431                    let a = points[i];
432                    let b = points[(i + 1) % 4];
433                    if (a.1 <= scan_y && b.1 > scan_y) || (b.1 <= scan_y && a.1 > scan_y) {
434                        let t = (scan_y - a.1) as f32 / (b.1 - a.1) as f32;
435                        let x = a.0 as f32 + t * (b.0 - a.0) as f32;
436                        x_intersects.push(x as i32);
437                    }
438                }
439                x_intersects.sort();
440                for i in (0..x_intersects.len()).step_by(2) {
441                    if i + 1 < x_intersects.len() {
442                        for px in x_intersects[i]..=x_intersects[i + 1] {
443                            // Clip to widget bounds
444                            if px >= widget_bounds.0
445                                && px < widget_bounds.2
446                                && scan_y >= widget_bounds.1
447                                && scan_y < widget_bounds.3
448                                && px >= 0
449                                && scan_y >= 0
450                                && (px as usize) < width
451                                && (scan_y as usize) < height
452                            {
453                                let offset = scan_y as usize * stride * 4 + px as usize * 4;
454                                if offset + 3 < buffer.len() {
455                                    buffer[offset] = color[0];
456                                    buffer[offset + 1] = color[1];
457                                    buffer[offset + 2] = color[2];
458                                    buffer[offset + 3] = color[3];
459                                }
460                            }
461                        }
462                    }
463                }
464            }
465        };
466
467        // Draw the three visible front faces (XZ top, YZ front-left, XY front-right)
468        // XZ plane (top/horizontal) - points 4,5,6,7
469        fill_quad(buffer.pixels_mut(), p[4], p[5], p[6], p[7], xz_color);
470
471        // YZ plane (front-left vertical) - points 3,7,6,2
472        fill_quad(buffer.pixels_mut(), p[3], p[7], p[6], p[2], yz_color);
473
474        // XY plane (front-right vertical) - points 1,5,6,2
475        fill_quad(buffer.pixels_mut(), p[1], p[5], p[6], p[2], xy_color);
476
477        // Draw edges for clarity
478        let edge_color = [200, 200, 200, 255];
479        let draw_edge = |buf: &mut [u8], p0: (i32, i32), p1: (i32, i32)| {
480            // Bresenham line
481            let mut x0 = p0.0;
482            let mut y0 = p0.1;
483            let x1 = p1.0;
484            let y1 = p1.1;
485            let dx = (x1 - x0).abs();
486            let dy = (y1 - y0).abs();
487            let sx = if x0 < x1 { 1 } else { -1 };
488            let sy = if y0 < y1 { 1 } else { -1 };
489            let mut err = dx - dy;
490            loop {
491                // Clip to widget bounds
492                if x0 >= widget_bounds.0
493                    && x0 < widget_bounds.2
494                    && y0 >= widget_bounds.1
495                    && y0 < widget_bounds.3
496                    && x0 >= 0
497                    && y0 >= 0
498                    && (x0 as usize) < width
499                    && (y0 as usize) < height
500                {
501                    let offset = y0 as usize * stride * 4 + x0 as usize * 4;
502                    if offset + 3 < buf.len() {
503                        buf[offset] = edge_color[0];
504                        buf[offset + 1] = edge_color[1];
505                        buf[offset + 2] = edge_color[2];
506                        buf[offset + 3] = edge_color[3];
507                    }
508                }
509                if x0 == x1 && y0 == y1 {
510                    break;
511                }
512                let e2 = 2 * err;
513                if e2 > -dy {
514                    err -= dy;
515                    x0 += sx;
516                }
517                if e2 < dx {
518                    err += dx;
519                    y0 += sy;
520                }
521            }
522        };
523
524        // Draw cube edges
525        let buf = buffer.pixels_mut();
526        draw_edge(buf, p[4], p[5]);
527        draw_edge(buf, p[5], p[6]);
528        draw_edge(buf, p[6], p[7]);
529        draw_edge(buf, p[7], p[4]);
530        draw_edge(buf, p[1], p[5]);
531        draw_edge(buf, p[2], p[6]);
532        draw_edge(buf, p[3], p[7]);
533        draw_edge(buf, p[1], p[2]);
534        draw_edge(buf, p[2], p[3]);
535
536        // Store clickable regions for each plane
537        // XZ plane (top)
538        self.plane_picker_rects.push(TheDim::rect(
539            p[4].0.min(p[5].0).min(p[6].0).min(p[7].0),
540            p[4].1.min(p[5].1).min(p[6].1).min(p[7].1),
541            p[4].0.max(p[5].0).max(p[6].0).max(p[7].0) - p[4].0.min(p[5].0).min(p[6].0).min(p[7].0),
542            p[4].1.max(p[5].1).max(p[6].1).max(p[7].1) - p[4].1.min(p[5].1).min(p[6].1).min(p[7].1),
543        ));
544
545        // YZ plane (front-left)
546        self.plane_picker_rects.push(TheDim::rect(
547            p[3].0.min(p[7].0).min(p[6].0).min(p[2].0),
548            p[3].1.min(p[7].1).min(p[6].1).min(p[2].1),
549            p[3].0.max(p[7].0).max(p[6].0).max(p[2].0) - p[3].0.min(p[7].0).min(p[6].0).min(p[2].0),
550            p[3].1.max(p[7].1).max(p[6].1).max(p[2].1) - p[3].1.min(p[7].1).min(p[6].1).min(p[2].1),
551        ));
552
553        // XY plane (front-right)
554        self.plane_picker_rects.push(TheDim::rect(
555            p[1].0.min(p[5].0).min(p[6].0).min(p[2].0),
556            p[1].1.min(p[5].1).min(p[6].1).min(p[2].1),
557            p[1].0.max(p[5].0).max(p[6].0).max(p[2].0) - p[1].0.min(p[5].0).min(p[6].0).min(p[2].0),
558            p[1].1.max(p[5].1).max(p[6].1).max(p[2].1) - p[1].1.min(p[5].1).min(p[6].1).min(p[2].1),
559        ));
560    }
561
562    pub fn clicked(
563        &mut self,
564        x: i32,
565        y: i32,
566        map: &mut Map,
567        _ui: &mut TheUI,
568        ctx: &mut TheContext,
569        server_ctx: &mut ServerContext,
570    ) -> bool {
571        if server_ctx.get_map_context() != MapContext::Region
572            && server_ctx.get_map_context() != MapContext::Screen
573            && server_ctx.get_map_context() != MapContext::Character
574            && server_ctx.get_map_context() != MapContext::Item
575        {
576            return false;
577        }
578
579        for (i, rect) in self.icon_rects.iter().enumerate() {
580            if rect.contains(Vec2::new(x, y)) {
581                self.selected_icon_index = i as i32;
582                server_ctx.selected_hud_icon_index = i as i32;
583                if self.mode == HudMode::Linedef {
584                    server_ctx.selected_wall_row = Some(i as i32);
585                    ctx.ui.send(TheEvent::Custom(
586                        TheId::named("Map Selection Changed"),
587                        TheValue::Empty,
588                    ));
589                }
590                return true;
591            }
592        }
593        if self.mode != HudMode::Rect {
594            for (i, rect) in self.subdiv_rects.iter().enumerate() {
595                if rect.contains(Vec2::new(x, y)) {
596                    map.subdivisions = (i + 1) as f32;
597                    return true;
598                }
599            }
600        }
601
602        // Check plane picker clicks in 3D mode
603        if server_ctx.editor_view_mode != EditorViewMode::D2 && server_ctx.show_editing_geometry {
604            for (i, rect) in self.plane_picker_rects.iter().enumerate() {
605                if rect.contains(Vec2::new(x, y)) {
606                    server_ctx.gizmo_mode = match i {
607                        0 => {
608                            println!("Plane picker: XZ plane selected");
609                            GizmoMode::XZ
610                        }
611                        1 => {
612                            println!("Plane picker: YZ plane selected");
613                            GizmoMode::YZ
614                        }
615                        2 => {
616                            println!("Plane picker: XY plane selected");
617                            GizmoMode::XY
618                        }
619                        _ => server_ctx.gizmo_mode,
620                    };
621                    return true;
622                }
623            }
624        }
625
626        if map.camera == MapCamera::TwoD && y < 20 {
627            return true;
628        }
629
630        false
631    }
632
633    pub fn dragged(
634        &mut self,
635        _x: i32,
636        _y: i32,
637        _map: &mut Map,
638        _ui: &mut TheUI,
639        _ctx: &mut TheContext,
640        _server_ctx: &mut ServerContext,
641    ) -> bool {
642        /*
643        if self.timeline_rect.contains(Vec2::new(x, y)) {
644            let offset = x - self.timeline_rect.x;
645            let progress = offset as f32 / self.timeline_rect.width as f32;
646            map.animation.transition_progress = progress;
647            return true;
648        }*/
649
650        false
651    }
652
653    pub fn hovered(
654        &mut self,
655        x: i32,
656        y: i32,
657        _map: &mut Map,
658        _ui: &mut TheUI,
659        _ctx: &mut TheContext,
660        _server_ctx: &mut ServerContext,
661    ) -> bool {
662        self.mouse_pos = Vec2::new(x, y);
663
664        /*
665        if self.rect_geo_rect.contains(self.mouse_pos) {
666            ctx.ui.send(TheEvent::SetStatusText(
667                TheId::empty(),
668                "Show or hide geometry created with the Rect tool.".to_string(),
669            ));
670        } else {
671            ctx.ui
672                .send(TheEvent::SetStatusText(TheId::empty(), "".into()));
673        }*/
674        false
675    }
676
677    #[allow(clippy::collapsible_if)]
678    pub fn get_icon_text(&self, index: i32, server_ctx: &mut ServerContext) -> String {
679        let mut text: String = "".into();
680        if server_ctx.get_map_context() == MapContext::Region {
681            if self.mode == HudMode::Sector {
682                if index == 0 {
683                    text = "TILE".into();
684                }
685            }
686        } else if server_ctx.get_map_context() == MapContext::Screen {
687            if index == 0 {
688                text = "NORM".into();
689            } else if index == 1 {
690                text = "ACTIVE".into();
691            }
692        }
693
694        text
695    }
696
697    #[allow(clippy::collapsible_if)]
698    pub fn get_icon(
699        &self,
700        index: i32,
701        map: &Map,
702        id: u32,
703        icon_size: usize,
704    ) -> (Option<rusterix::Tile>, bool) {
705        if self.mode == HudMode::Sector {
706            if let Some(sector) = map.find_sector(id) {
707                if index == 0 {
708                    let has_light = sector.properties.get("floor_light").is_some();
709                    if let Some(pixelsource) = sector.properties.get_default_source() {
710                        if let Some(tile) = pixelsource.to_tile(
711                            &RUSTERIX.read().unwrap().assets,
712                            icon_size,
713                            &sector.properties,
714                            map,
715                        ) {
716                            return (Some(tile), has_light);
717                        }
718                    }
719                    return (None, has_light);
720                } else if index == 1 {
721                    let has_light = sector.properties.get("ceiling_light").is_some();
722                    if let Some(Value::Source(pixelsource)) =
723                        &sector.properties.get("ceiling_source")
724                    {
725                        if let Some(tile) = pixelsource.to_tile(
726                            &RUSTERIX.read().unwrap().assets,
727                            icon_size,
728                            &sector.properties,
729                            map,
730                        ) {
731                            return (Some(tile), has_light);
732                        }
733                    }
734                    return (None, has_light);
735                }
736            }
737        }
738
739        (None, false)
740    }
741}