1use crate::prelude::*;
2use rusterix::{Surface, ValueContainer};
3use std::collections::hash_map::DefaultHasher;
4use vek::Vec2;
5
6use crate::editor::{PALETTE, RUSTERIX, SIDEBARMODE};
7use std::hash::{Hash, Hasher};
8
9pub static MINIMAPBUFFER: LazyLock<RwLock<TheRGBABuffer>> =
10 LazyLock::new(|| RwLock::new(TheRGBABuffer::default()));
11
12pub static MINIMAPBOX: LazyLock<RwLock<Vec4<f32>>> = LazyLock::new(|| RwLock::new(Vec4::one()));
13pub static MINIMAPCACHEKEY: LazyLock<RwLock<u64>> = LazyLock::new(|| RwLock::new(0));
14
15fn minimap_context_key(server_ctx: &ServerContext) -> u64 {
16 let mut hasher = DefaultHasher::default();
17 match server_ctx.get_map_context() {
18 MapContext::Region => 0_u8.hash(&mut hasher),
19 MapContext::Screen => 1_u8.hash(&mut hasher),
20 MapContext::Character => 2_u8.hash(&mut hasher),
21 MapContext::Item => 3_u8.hash(&mut hasher),
22 }
23 server_ctx.curr_region.hash(&mut hasher);
24 server_ctx.curr_screen.hash(&mut hasher);
25 if let Some(surface) = &server_ctx.editing_surface {
26 surface.id.hash(&mut hasher);
27 }
28 hasher.finish()
29}
30
31pub fn minimap_bbox_for_map(map: &Map) -> Option<Vec4<f32>> {
32 let mut bbox = map.bounding_box()?;
33 if let Some(tbbox) = map.terrain.compute_bounds() {
34 let bbox_min = Vec2::new(bbox.x, bbox.y);
35 let bbox_max = bbox_min + Vec2::new(bbox.z, bbox.w);
36
37 let new_min = bbox_min.map2(tbbox.min, f32::min);
38 let new_max = bbox_max.map2(tbbox.max, f32::max);
39
40 bbox.x = new_min.x;
41 bbox.y = new_min.y;
42 bbox.z = new_max.x - new_min.x;
43 bbox.w = new_max.y - new_min.y;
44 }
45
46 bbox.x -= 0.5;
47 bbox.y -= 0.5;
48 bbox.z += 1.0;
49 bbox.w += 1.0;
50 Some(bbox)
51}
52
53fn surface_uv_outline(surface: &Surface) -> Option<Vec<Vec2<f32>>> {
54 if surface.world_vertices.len() < 2 {
55 return None;
56 }
57 let mut points = Vec::with_capacity(surface.world_vertices.len());
58 for p in &surface.world_vertices {
59 let mut uv = surface.world_to_uv(*p);
60 uv.y = -uv.y;
61 points.push(uv);
62 }
63 Some(points)
64}
65
66fn minimap_bbox_for_surface(surface: &Surface) -> Option<Vec4<f32>> {
67 let points = surface_uv_outline(surface)?;
68 let mut min = points[0];
69 let mut max = points[0];
70 for p in points.iter().skip(1) {
71 min = min.map2(*p, f32::min);
72 max = max.map2(*p, f32::max);
73 }
74 let mut bbox = Vec4::new(min.x, min.y, max.x - min.x, max.y - min.y);
75 bbox.x -= 0.5;
76 bbox.y -= 0.5;
77 bbox.z += 1.0;
78 bbox.w += 1.0;
79 Some(bbox)
80}
81
82fn draw_surface_outline_on_minimap(buffer: &mut TheRGBABuffer, surface: &Surface, bbox: Vec4<f32>) {
83 let Some(points) = surface_uv_outline(surface) else {
84 return;
85 };
86 if points.len() < 2 {
87 return;
88 }
89 let dim = *buffer.dim();
90 let render_dim = Vec2::new(dim.width as f32, dim.height as f32);
91 let line_color = [235, 235, 235, 255];
92 for i in 0..points.len() {
93 let p0 = world_to_minimap_pixel(points[i], render_dim, bbox);
94 let p1 = world_to_minimap_pixel(points[(i + 1) % points.len()], render_dim, bbox);
95 buffer.draw_line(
96 p0.x.round() as i32,
97 p0.y.round() as i32,
98 p1.x.round() as i32,
99 p1.y.round() as i32,
100 line_color,
101 );
102 }
103}
104
105pub fn draw_camera_marker(
106 map: &Map,
107 region: Option<&Region>,
108 buffer: &mut TheRGBABuffer,
109 server_ctx: &ServerContext,
110) {
111 let camera_pos = if server_ctx.editor_view_mode == EditorViewMode::D2 || region.is_none() {
112 Vec2::new(-map.offset.x / map.grid_size, map.offset.y / map.grid_size)
114 } else if let Some(region) = region {
115 Vec2::new(region.editing_position_3d.x, region.editing_position_3d.z)
117 } else {
118 Vec2::zero()
119 };
120
121 let dim = *buffer.dim();
122 let bbox = *MINIMAPBOX.read().unwrap();
123
124 let pos = world_to_minimap_pixel(
125 camera_pos,
126 Vec2::new(dim.width as f32, dim.height as f32),
127 bbox,
128 );
129
130 let w = 4;
131 buffer.draw_rect_outline(
132 &TheDim::rect(pos.x as i32 - w, pos.y as i32 - w, w * 2, w * 2),
133 &vek::Rgba::red().into_array(),
134 );
135
136 if server_ctx.editor_view_mode == EditorViewMode::FirstP
137 && let Some(region) = region
138 {
139 let look_at_pos = Vec2::new(region.editing_look_at_3d.x, region.editing_look_at_3d.z);
140
141 let pos = world_to_minimap_pixel(
142 look_at_pos,
143 Vec2::new(dim.width as f32, dim.height as f32),
144 bbox,
145 );
146
147 buffer.draw_rect_outline(
148 &TheDim::rect(pos.x as i32 - w, pos.y as i32 - w, w * 2, w * 2),
149 &vek::Rgba::yellow().into_array(),
150 );
151 }
152}
153
154pub fn draw_minimap_context_label(
155 buffer: &mut TheRGBABuffer,
156 ctx: &mut TheContext,
157 server_ctx: &ServerContext,
158) {
159 let label = if server_ctx.get_map_context() == MapContext::Region {
160 if server_ctx.editing_surface.is_some() {
161 "Profile"
162 } else {
163 "Region"
164 }
165 } else if server_ctx.get_map_context() == MapContext::Screen {
166 "Screen"
167 } else if server_ctx.get_map_context() == MapContext::Character {
168 "Character"
169 } else if server_ctx.get_map_context() == MapContext::Item {
170 "Item"
171 } else {
172 "Map"
173 };
174
175 let stride = buffer.stride();
176 let bg = [36, 36, 36, 180];
177 let fg = [215, 215, 215, 255];
178 let text_pad_left = 4;
179 let approx_char_w = 7;
180 let label_w = (label.len() as i32 * approx_char_w + text_pad_left * 2).max(24);
181 ctx.draw.rect(
182 buffer.pixels_mut(),
183 &(0, 0, label_w as usize, 16),
184 stride,
185 &bg,
186 );
187 ctx.draw.text_rect(
188 buffer.pixels_mut(),
189 &(
190 text_pad_left as usize,
191 0,
192 (label_w - text_pad_left) as usize,
193 16,
194 ),
195 stride,
196 label,
197 TheFontSettings {
198 size: 11.0,
199 ..Default::default()
200 },
201 &fg,
202 &bg,
203 TheHorizontalAlign::Left,
204 TheVerticalAlign::Center,
205 );
206}
207
208pub fn draw_minimap(
209 project: &Project,
210 buffer: &mut TheRGBABuffer,
211 server_ctx: &ServerContext,
212 hard: bool,
213) {
214 if *SIDEBARMODE.read().unwrap() == SidebarMode::Palette {
215 buffer.render_hsl_hue_waveform();
216
217 if let Some(color) = PALETTE.read().unwrap().get_current_color() {
218 if let Some(pos) = buffer.find_closest_color_position(color.to_u8_array_3()) {
219 let w = 4;
220 buffer.draw_rect_outline(
221 &TheDim::rect(pos.x - w, pos.y - w, w * 2, w * 2),
222 &vek::Rgba::white().into_array(),
223 );
224 }
225 }
226
227 return;
228 }
229
230 let cache_key = minimap_context_key(server_ctx);
231 let mut hard = hard;
232 if !hard {
233 let cached_key = *MINIMAPCACHEKEY.read().unwrap();
234 let cached = MINIMAPBUFFER.read().unwrap();
235 let cached_dim = *cached.dim();
236 let dim = *buffer.dim();
237 if cached_key != cache_key
238 || cached_dim.width != dim.width
239 || cached_dim.height != dim.height
240 {
241 hard = true;
242 }
243 }
244
245 if !hard {
246 buffer.copy_into(0, 0, &MINIMAPBUFFER.read().unwrap());
247 if let Some(map) = project.get_map(server_ctx) {
248 let region_marker = if server_ctx.get_map_context() == MapContext::Region {
249 project.get_region(&server_ctx.curr_region)
250 } else {
251 None
252 };
253 draw_camera_marker(map, region_marker, buffer, server_ctx);
254 }
255 return;
256 }
257
258 let dim = buffer.dim();
259
260 let width = dim.width as f32;
261 let height = dim.height as f32;
262 let background = [42, 42, 42, 255];
263
264 if let Some(map) = project.get_map(server_ctx) {
265 let bbox_from_surface = server_ctx
266 .editing_surface
267 .as_ref()
268 .and_then(minimap_bbox_for_surface);
269 let Some(bbox) = minimap_bbox_for_map(map).or(bbox_from_surface) else {
270 buffer.fill(background);
271 return;
272 };
273
274 *MINIMAPBOX.write().unwrap() = bbox;
275
276 let scale_x = width / bbox.z;
277 let scale_y = height / bbox.w;
278
279 let mut rusterix = RUSTERIX.write().unwrap();
280 let mut map_copy = map.clone();
281 map_copy.selected_linedefs.clear();
282 map_copy.selected_sectors.clear();
283 map_copy.grid_size = scale_x.min(scale_y);
284 map_copy.camera = MapCamera::TwoD;
285
286 let bbox_center_x = bbox.x + bbox.z / 2.0;
287 let bbox_center_y = bbox.y + bbox.w / 2.0;
288 map_copy.offset.x = -bbox_center_x * scale_x;
289 map_copy.offset.y = bbox_center_y * scale_y;
290
291 let translation_matrix = Mat3::<f32>::translation_2d(Vec2::new(
292 map_copy.offset.x + width / 2.0,
293 -map_copy.offset.y + height / 2.0,
294 ));
295 let scale_matrix = Mat3::new(scale_x, 0.0, 0.0, 0.0, scale_y, 0.0, 0.0, 0.0, 1.0);
296 let transform = translation_matrix * scale_matrix;
297
298 let use_scenevm_region = server_ctx.get_map_context() == MapContext::Region
299 && server_ctx.editing_surface.is_none();
300
301 if use_scenevm_region {
302 let hour = 12.0;
304 let anim_counter = rusterix.client.animation_frame;
305 let scenevm_mode_2d = rusterix.scene_handler.settings.scenevm_mode_2d();
306 let scene_handler = &mut rusterix.scene_handler;
307 let layer_count = scene_handler.vm.vm_layer_count();
308 let mut layer_enabled_before = Vec::with_capacity(layer_count);
309 for i in 0..layer_count {
310 layer_enabled_before.push(scene_handler.vm.is_layer_enabled(i).unwrap_or(true));
311 }
312 for i in 1..layer_count {
314 scene_handler.vm.set_layer_enabled(i, false);
315 }
316 if matches!(scenevm_mode_2d, scenevm::RenderMode::Compute2D) {
317 scene_handler.vm.execute(scenevm::Atom::SetGP0(Vec4::new(
318 map_copy.grid_size,
319 map_copy.subdivisions,
320 map_copy.offset.x,
321 -map_copy.offset.y,
322 )));
323 }
324 scene_handler
325 .vm
326 .execute(scenevm::Atom::SetRenderMode(scenevm_mode_2d));
327 scene_handler.settings.apply_hour(hour);
328 scene_handler.settings.apply_2d(&mut scene_handler.vm);
329 scene_handler
331 .vm
332 .execute(scenevm::Atom::SetGP0(Vec4::zero()));
333 scene_handler
334 .vm
335 .execute(scenevm::Atom::SetTransform2D(transform));
336 scene_handler
337 .vm
338 .execute(scenevm::Atom::SetAnimationCounter(anim_counter));
339 scene_handler
340 .vm
341 .execute(scenevm::Atom::SetBackground(Vec4::zero()));
342 scene_handler
343 .vm
344 .render_frame(buffer.pixels_mut(), width as u32, height as u32);
345 for (i, enabled) in layer_enabled_before.into_iter().enumerate() {
346 scene_handler.vm.set_layer_enabled(i, enabled);
347 }
348 } else {
349 let mut builder = rusterix::D2PreviewBuilder::new();
350 let mut scene = builder.build(
351 &map_copy,
352 &rusterix.assets,
353 Vec2::new(width, height),
354 &ValueContainer::default(),
355 );
356
357 rusterix::Rasterizer::setup(Some(transform), Mat4::identity(), Mat4::identity())
358 .background(background)
359 .ambient(Vec4::one())
360 .render_mode(rusterix::RenderMode::render_2d().ignore_background_shader(true))
361 .rasterize(
362 &mut scene,
363 buffer.pixels_mut(),
364 width as usize,
365 height as usize,
366 40,
367 &rusterix.assets,
368 );
369 }
370
371 let show_profile_lines = server_ctx.get_map_context() == MapContext::Region
373 && server_ctx.editor_view_mode == EditorViewMode::D2
374 && server_ctx.editing_surface.is_some();
375 if show_profile_lines && !map_copy.linedefs.is_empty() {
376 let dim = Vec2::new(width, height);
377 let line_color = [235, 235, 235, 255];
378 for linedef in &map_copy.linedefs {
379 if let Some(start) = map_copy.get_vertex(linedef.start_vertex)
380 && let Some(end) = map_copy.get_vertex(linedef.end_vertex)
381 {
382 let p0 = world_to_minimap_pixel(start, dim, bbox);
383 let p1 = world_to_minimap_pixel(end, dim, bbox);
384 buffer.draw_line(
385 p0.x.round() as i32,
386 p0.y.round() as i32,
387 p1.x.round() as i32,
388 p1.y.round() as i32,
389 line_color,
390 );
391 }
392 }
393 }
394 if map_copy.sectors.is_empty()
395 && map_copy.linedefs.is_empty()
396 && let Some(surface) = server_ctx.editing_surface.as_ref()
397 {
398 draw_surface_outline_on_minimap(buffer, surface, bbox);
399 }
400
401 MINIMAPBUFFER
402 .write()
403 .unwrap()
404 .resize(buffer.dim().width, buffer.dim().height);
405
406 MINIMAPBUFFER.write().unwrap().copy_into(0, 0, buffer);
407 *MINIMAPCACHEKEY.write().unwrap() = cache_key;
408 let region_marker = if server_ctx.get_map_context() == MapContext::Region {
409 project.get_region(&server_ctx.curr_region)
410 } else {
411 None
412 };
413 draw_camera_marker(map, region_marker, buffer, server_ctx);
414 }
415}
416
417fn world_to_minimap_pixel(
418 world_pos: Vec2<f32>,
419 render_dim: Vec2<f32>,
420 bbox: Vec4<f32>, ) -> Vec2<f32> {
422 let width = render_dim.x;
423 let height = render_dim.y;
424
425 let scale_x = width / bbox.z;
426 let scale_y = height / bbox.w;
427
428 let bbox_center_x = bbox.x + bbox.z / 2.0;
429 let bbox_center_y = bbox.y + bbox.w / 2.0;
430
431 let offset_x = -bbox_center_x * scale_x;
432 let offset_y = bbox_center_y * scale_y;
433
434 let pixel_x = (world_pos.x * scale_x) + offset_x + (width / 2.0);
435 let pixel_y = (-world_pos.y * scale_y) + offset_y + (height / 2.0);
436
437 Vec2::new(pixel_x, render_dim.y - pixel_y)
438}