1 /*****************************************************************************
2 * Copyright (c) 2014-2020 OpenRCT2 developers
3 *
4 * For a complete list of all authors, please refer to contributors.md
5 * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
6 *
7 * OpenRCT2 is licensed under the GNU General Public License version 3.
8 *****************************************************************************/
9
10 #include "Viewport.h"
11
12 #include "../Context.h"
13 #include "../Game.h"
14 #include "../Input.h"
15 #include "../OpenRCT2.h"
16 #include "../config/Config.h"
17 #include "../core/Guard.hpp"
18 #include "../core/JobPool.h"
19 #include "../drawing/Drawing.h"
20 #include "../drawing/IDrawingEngine.h"
21 #include "../paint/Paint.h"
22 #include "../peep/Guest.h"
23 #include "../peep/Staff.h"
24 #include "../ride/Ride.h"
25 #include "../ride/TrackDesign.h"
26 #include "../ride/Vehicle.h"
27 #include "../ui/UiContext.h"
28 #include "../ui/WindowManager.h"
29 #include "../util/Math.hpp"
30 #include "../world/Climate.h"
31 #include "../world/EntityList.h"
32 #include "../world/Map.h"
33 #include "Colour.h"
34 #include "Window.h"
35 #include "Window_internal.h"
36
37 #include <algorithm>
38 #include <cstring>
39 #include <list>
40 #include <unordered_map>
41
42 using namespace OpenRCT2;
43
44 //#define DEBUG_SHOW_DIRTY_BOX
45 uint8_t gShowGridLinesRefCount;
46 uint8_t gShowLandRightsRefCount;
47 uint8_t gShowConstuctionRightsRefCount;
48
49 static std::list<rct_viewport> _viewports;
50 rct_viewport* g_music_tracking_viewport;
51
52 static std::unique_ptr<JobPool> _paintJobs;
53 static std::vector<paint_session*> _paintColumns;
54
55 ScreenCoordsXY gSavedView;
56 ZoomLevel gSavedViewZoom;
57 uint8_t gSavedViewRotation;
58
59 paint_entry* gNextFreePaintStruct;
60 uint8_t gCurrentRotation;
61
62 static uint32_t _currentImageType;
InteractionInfo(const paint_struct * ps)63 InteractionInfo::InteractionInfo(const paint_struct* ps)
64 : Loc(ps->map_x, ps->map_y)
65 , Element(ps->tileElement)
66 , SpriteType(ps->sprite_type)
67 {
68 }
69 static void viewport_paint_weather_gloom(rct_drawpixelinfo* dpi);
70
71 /**
72 * This is not a viewport function. It is used to setup many variables for
73 * multiple things.
74 * rct2: 0x006E6EAC
75 */
viewport_init_all()76 void viewport_init_all()
77 {
78 if (!gOpenRCT2NoGraphics)
79 {
80 colours_init_maps();
81 }
82
83 window_init_all();
84
85 // ?
86 input_reset_flags();
87 input_set_state(InputState::Reset);
88 gPressedWidget.window_classification = 255;
89 gPickupPeepImage = UINT32_MAX;
90 reset_tooltip_not_shown();
91 gMapSelectFlags = 0;
92 gStaffDrawPatrolAreas = 0xFFFF;
93 textinput_cancel();
94 }
95
96 /**
97 * Converts between 3d point of a sprite to 2d coordinates for centring on that
98 * sprite
99 * rct2: 0x006EB0C1
100 * x : ax
101 * y : bx
102 * z : cx
103 * out_x : ax
104 * out_y : bx
105 */
centre_2d_coordinates(const CoordsXYZ & loc,rct_viewport * viewport)106 std::optional<ScreenCoordsXY> centre_2d_coordinates(const CoordsXYZ& loc, rct_viewport* viewport)
107 {
108 // If the start location was invalid
109 // propagate the invalid location to the output.
110 // This fixes a bug that caused the game to enter an infinite loop.
111 if (loc.IsNull())
112 {
113 return std::nullopt;
114 }
115
116 auto screenCoord = translate_3d_to_2d_with_z(get_current_rotation(), loc);
117 screenCoord.x -= viewport->view_width / 2;
118 screenCoord.y -= viewport->view_height / 2;
119 return { screenCoord };
120 }
121
GetPos() const122 CoordsXYZ Focus::GetPos() const
123 {
124 return std::visit(
125 [](auto&& arg) {
126 using T = std::decay_t<decltype(arg)>;
127 if constexpr (std::is_same_v<T, Focus::CoordinateFocus>)
128 return arg;
129 else if constexpr (std::is_same_v<T, Focus::EntityFocus>)
130 {
131 auto* centreEntity = GetEntity(arg);
132 if (centreEntity != nullptr)
133 {
134 return CoordsXYZ{ centreEntity->x, centreEntity->y, centreEntity->z };
135 }
136 else
137 {
138 log_error("Invalid entity for focus.");
139 return CoordsXYZ{};
140 }
141 }
142 },
143 data);
144 }
145
146 /**
147 * Viewport will look at sprite or at coordinates as specified in flags 0b_1X
148 * for sprite 0b_0X for coordinates
149 *
150 * rct2: 0x006EB009
151 * x: ax
152 * y: eax (top 16)
153 * width: bx
154 * height: ebx (top 16)
155 * zoom: cl (8 bits)
156 * centre_x: edx lower 16 bits
157 * centre_y: edx upper 16 bits
158 * centre_z: ecx upper 16 bits
159 * sprite: edx lower 16 bits
160 * flags: edx top most 2 bits 0b_X1 for zoom clear see below for 2nd bit.
161 * w: esi
162 */
viewport_create(rct_window * w,const ScreenCoordsXY & screenCoords,int32_t width,int32_t height,const Focus & focus)163 void viewport_create(rct_window* w, const ScreenCoordsXY& screenCoords, int32_t width, int32_t height, const Focus& focus)
164 {
165 rct_viewport* viewport = nullptr;
166 if (_viewports.size() >= MAX_VIEWPORT_COUNT)
167 {
168 log_error("No more viewport slots left to allocate.");
169 return;
170 }
171
172 auto itViewport = _viewports.insert(_viewports.end(), rct_viewport{});
173
174 viewport = &*itViewport;
175 viewport->pos = screenCoords;
176 viewport->width = width;
177 viewport->height = height;
178 const auto zoom = focus.zoom;
179
180 viewport->view_width = width << zoom;
181 viewport->view_height = height << zoom;
182 viewport->zoom = zoom;
183 viewport->flags = 0;
184
185 if (gConfigGeneral.always_show_gridlines)
186 viewport->flags |= VIEWPORT_FLAG_GRIDLINES;
187 w->viewport = viewport;
188
189 CoordsXYZ centrePos = focus.GetPos();
190 w->viewport_target_sprite = std::visit(
191 [](auto&& arg) {
192 using T = std::decay_t<decltype(arg)>;
193 if constexpr (std::is_same_v<T, Focus::CoordinateFocus>)
194 return SPRITE_INDEX_NULL;
195 else if constexpr (std::is_same_v<T, Focus::EntityFocus>)
196 return arg;
197 },
198 focus.data);
199
200 auto centreLoc = centre_2d_coordinates(centrePos, viewport);
201 if (!centreLoc.has_value())
202 {
203 log_error("Invalid location for viewport.");
204 return;
205 }
206 w->savedViewPos = *centreLoc;
207 viewport->viewPos = *centreLoc;
208 }
209
viewport_remove(rct_viewport * viewport)210 void viewport_remove(rct_viewport* viewport)
211 {
212 auto it = std::find_if(_viewports.begin(), _viewports.end(), [viewport](const auto& vp) { return &vp == viewport; });
213 if (it == _viewports.end())
214 {
215 log_error("Unable to remove viewport: %p", viewport);
216 return;
217 }
218 _viewports.erase(it);
219 }
220
viewports_invalidate(const ScreenRect & screenRect,int32_t maxZoom)221 void viewports_invalidate(const ScreenRect& screenRect, int32_t maxZoom)
222 {
223 for (auto& vp : _viewports)
224 {
225 if (maxZoom == -1 || vp.zoom <= maxZoom)
226 {
227 viewport_invalidate(&vp, screenRect);
228 }
229 }
230 }
231
232 /**
233 *
234 * rct2: 0x00689174
235 * edx is assumed to be (and always is) the current rotation, so it is not
236 * needed as parameter.
237 */
viewport_adjust_for_map_height(const ScreenCoordsXY & startCoords)238 CoordsXYZ viewport_adjust_for_map_height(const ScreenCoordsXY& startCoords)
239 {
240 int32_t height = 0;
241
242 uint32_t rotation = get_current_rotation();
243 CoordsXY pos{};
244 for (int32_t i = 0; i < 6; i++)
245 {
246 pos = viewport_coord_to_map_coord(startCoords, height);
247 height = tile_element_height(pos);
248
249 // HACK: This is to prevent the x and y values being set to values outside
250 // of the map. This can happen when the height is larger than the map size.
251 auto max = GetMapSizeMinus2();
252 if (pos.x > max && pos.y > max)
253 {
254 static constexpr CoordsXY corr[] = {
255 { -1, -1 },
256 { 1, -1 },
257 { 1, 1 },
258 { -1, 1 },
259 };
260 pos.x += corr[rotation].x * height;
261 pos.y += corr[rotation].y * height;
262 }
263 }
264
265 return { pos, height };
266 }
267
268 /*
269 * rct2: 0x006E7FF3
270 */
viewport_redraw_after_shift(rct_drawpixelinfo * dpi,rct_window * window,rct_viewport * viewport,const ScreenCoordsXY & coords)271 static void viewport_redraw_after_shift(
272 rct_drawpixelinfo* dpi, rct_window* window, rct_viewport* viewport, const ScreenCoordsXY& coords)
273 {
274 // sub-divide by intersecting windows
275 if (window != nullptr)
276 {
277 // skip current window and non-intersecting windows
278 if (viewport == window->viewport || viewport->pos.x + viewport->width <= window->windowPos.x
279 || viewport->pos.x >= window->windowPos.x + window->width
280 || viewport->pos.y + viewport->height <= window->windowPos.y
281 || viewport->pos.y >= window->windowPos.y + window->height)
282 {
283 auto itWindowPos = window_get_iterator(window);
284 auto itNextWindow = itWindowPos != g_window_list.end() ? std::next(itWindowPos) : g_window_list.end();
285 viewport_redraw_after_shift(
286 dpi, itNextWindow == g_window_list.end() ? nullptr : itNextWindow->get(), viewport, coords);
287 return;
288 }
289
290 // save viewport
291 rct_viewport view_copy = *viewport;
292
293 if (viewport->pos.x < window->windowPos.x)
294 {
295 viewport->width = window->windowPos.x - viewport->pos.x;
296 viewport->view_width = viewport->width * viewport->zoom;
297 viewport_redraw_after_shift(dpi, window, viewport, coords);
298
299 viewport->pos.x += viewport->width;
300 viewport->viewPos.x += viewport->width * viewport->zoom;
301 viewport->width = view_copy.width - viewport->width;
302 viewport->view_width = viewport->width * viewport->zoom;
303 viewport_redraw_after_shift(dpi, window, viewport, coords);
304 }
305 else if (viewport->pos.x + viewport->width > window->windowPos.x + window->width)
306 {
307 viewport->width = window->windowPos.x + window->width - viewport->pos.x;
308 viewport->view_width = viewport->width * viewport->zoom;
309 viewport_redraw_after_shift(dpi, window, viewport, coords);
310
311 viewport->pos.x += viewport->width;
312 viewport->viewPos.x += viewport->width * viewport->zoom;
313 viewport->width = view_copy.width - viewport->width;
314 viewport->view_width = viewport->width * viewport->zoom;
315 viewport_redraw_after_shift(dpi, window, viewport, coords);
316 }
317 else if (viewport->pos.y < window->windowPos.y)
318 {
319 viewport->height = window->windowPos.y - viewport->pos.y;
320 viewport->view_width = viewport->width * viewport->zoom;
321 viewport_redraw_after_shift(dpi, window, viewport, coords);
322
323 viewport->pos.y += viewport->height;
324 viewport->viewPos.y += viewport->height * viewport->zoom;
325 viewport->height = view_copy.height - viewport->height;
326 viewport->view_width = viewport->width * viewport->zoom;
327 viewport_redraw_after_shift(dpi, window, viewport, coords);
328 }
329 else if (viewport->pos.y + viewport->height > window->windowPos.y + window->height)
330 {
331 viewport->height = window->windowPos.y + window->height - viewport->pos.y;
332 viewport->view_width = viewport->width * viewport->zoom;
333 viewport_redraw_after_shift(dpi, window, viewport, coords);
334
335 viewport->pos.y += viewport->height;
336 viewport->viewPos.y += viewport->height * viewport->zoom;
337 viewport->height = view_copy.height - viewport->height;
338 viewport->view_width = viewport->width * viewport->zoom;
339 viewport_redraw_after_shift(dpi, window, viewport, coords);
340 }
341
342 // restore viewport
343 *viewport = view_copy;
344 }
345 else
346 {
347 auto left = viewport->pos.x;
348 auto right = viewport->pos.x + viewport->width;
349 auto top = viewport->pos.y;
350 auto bottom = viewport->pos.y + viewport->height;
351
352 // if moved more than the viewport size
353 if (abs(coords.x) < viewport->width && abs(coords.y) < viewport->height)
354 {
355 // update whole block ?
356 drawing_engine_copy_rect(viewport->pos.x, viewport->pos.y, viewport->width, viewport->height, coords.x, coords.y);
357
358 if (coords.x > 0)
359 {
360 // draw left
361 auto _right = viewport->pos.x + coords.x;
362 window_draw_all(dpi, left, top, _right, bottom);
363 left += coords.x;
364 }
365 else if (coords.x < 0)
366 {
367 // draw right
368 auto _left = viewport->pos.x + viewport->width + coords.x;
369 window_draw_all(dpi, _left, top, right, bottom);
370 right += coords.x;
371 }
372
373 if (coords.y > 0)
374 {
375 // draw top
376 bottom = viewport->pos.y + coords.y;
377 window_draw_all(dpi, left, top, right, bottom);
378 }
379 else if (coords.y < 0)
380 {
381 // draw bottom
382 top = viewport->pos.y + viewport->height + coords.y;
383 window_draw_all(dpi, left, top, right, bottom);
384 }
385 }
386 else
387 {
388 // redraw whole viewport
389 window_draw_all(dpi, left, top, right, bottom);
390 }
391 }
392 }
393
viewport_shift_pixels(rct_drawpixelinfo * dpi,rct_window * window,rct_viewport * viewport,int32_t x_diff,int32_t y_diff)394 static void viewport_shift_pixels(
395 rct_drawpixelinfo* dpi, rct_window* window, rct_viewport* viewport, int32_t x_diff, int32_t y_diff)
396 {
397 auto it = window_get_iterator(window);
398 for (; it != g_window_list.end(); it++)
399 {
400 auto w = it->get();
401 if (!(w->flags & WF_TRANSPARENT))
402 continue;
403 if (w->viewport == viewport)
404 continue;
405
406 if (viewport->pos.x + viewport->width <= w->windowPos.x)
407 continue;
408 if (w->windowPos.x + w->width <= viewport->pos.x)
409 continue;
410
411 if (viewport->pos.y + viewport->height <= w->windowPos.y)
412 continue;
413 if (w->windowPos.y + w->height <= viewport->pos.y)
414 continue;
415
416 auto left = w->windowPos.x;
417 auto right = w->windowPos.x + w->width;
418 auto top = w->windowPos.y;
419 auto bottom = w->windowPos.y + w->height;
420
421 if (left < viewport->pos.x)
422 left = viewport->pos.x;
423 if (right > viewport->pos.x + viewport->width)
424 right = viewport->pos.x + viewport->width;
425
426 if (top < viewport->pos.y)
427 top = viewport->pos.y;
428 if (bottom > viewport->pos.y + viewport->height)
429 bottom = viewport->pos.y + viewport->height;
430
431 if (left >= right)
432 continue;
433 if (top >= bottom)
434 continue;
435
436 window_draw_all(dpi, left, top, right, bottom);
437 }
438
439 viewport_redraw_after_shift(dpi, window, viewport, { x_diff, y_diff });
440 }
441
viewport_move(const ScreenCoordsXY & coords,rct_window * w,rct_viewport * viewport)442 static void viewport_move(const ScreenCoordsXY& coords, rct_window* w, rct_viewport* viewport)
443 {
444 auto zoom = viewport->zoom;
445
446 // Note: do not do the subtraction and then divide!
447 // Note: Due to arithmetic shift != /zoom a shift will have to be used
448 // hopefully when 0x006E7FF3 is finished this can be converted to /zoom.
449 auto x_diff = (viewport->viewPos.x / viewport->zoom) - (coords.x / viewport->zoom);
450 auto y_diff = (viewport->viewPos.y / viewport->zoom) - (coords.y / viewport->zoom);
451
452 viewport->viewPos = coords;
453
454 // If no change in viewing area
455 if ((!x_diff) && (!y_diff))
456 return;
457
458 if (w->flags & WF_7)
459 {
460 int32_t left = std::max<int32_t>(viewport->pos.x, 0);
461 int32_t top = std::max<int32_t>(viewport->pos.y, 0);
462 int32_t right = std::min<int32_t>(viewport->pos.x + viewport->width, context_get_width());
463 int32_t bottom = std::min<int32_t>(viewport->pos.y + viewport->height, context_get_height());
464
465 if (left >= right)
466 return;
467 if (top >= bottom)
468 return;
469
470 if (drawing_engine_has_dirty_optimisations())
471 {
472 rct_drawpixelinfo* dpi = drawing_engine_get_dpi();
473 window_draw_all(dpi, left, top, right, bottom);
474 return;
475 }
476 }
477
478 rct_viewport view_copy = *viewport;
479
480 if (viewport->pos.x < 0)
481 {
482 viewport->width += viewport->pos.x;
483 viewport->view_width += viewport->pos.x * zoom;
484 viewport->viewPos.x -= viewport->pos.x * zoom;
485 viewport->pos.x = 0;
486 }
487
488 int32_t eax = viewport->pos.x + viewport->width - context_get_width();
489 if (eax > 0)
490 {
491 viewport->width -= eax;
492 viewport->view_width -= eax * zoom;
493 }
494
495 if (viewport->width <= 0)
496 {
497 *viewport = view_copy;
498 return;
499 }
500
501 if (viewport->pos.y < 0)
502 {
503 viewport->height += viewport->pos.y;
504 viewport->view_height += viewport->pos.y * zoom;
505 viewport->viewPos.y -= viewport->pos.y * zoom;
506 viewport->pos.y = 0;
507 }
508
509 eax = viewport->pos.y + viewport->height - context_get_height();
510 if (eax > 0)
511 {
512 viewport->height -= eax;
513 viewport->view_height -= eax * zoom;
514 }
515
516 if (viewport->height <= 0)
517 {
518 *viewport = view_copy;
519 return;
520 }
521
522 if (drawing_engine_has_dirty_optimisations())
523 {
524 rct_drawpixelinfo* dpi = drawing_engine_get_dpi();
525 viewport_shift_pixels(dpi, w, viewport, x_diff, y_diff);
526 }
527
528 *viewport = view_copy;
529 }
530
531 // rct2: 0x006E7A15
viewport_set_underground_flag(int32_t underground,rct_window * window,rct_viewport * viewport)532 static void viewport_set_underground_flag(int32_t underground, rct_window* window, rct_viewport* viewport)
533 {
534 if (window->classification != WC_MAIN_WINDOW
535 || (window->classification == WC_MAIN_WINDOW && window->viewport_smart_follow_sprite != SPRITE_INDEX_NULL))
536 {
537 if (!underground)
538 {
539 int32_t bit = viewport->flags & VIEWPORT_FLAG_UNDERGROUND_INSIDE;
540 viewport->flags &= ~VIEWPORT_FLAG_UNDERGROUND_INSIDE;
541 if (!bit)
542 return;
543 }
544 else
545 {
546 int32_t bit = viewport->flags & VIEWPORT_FLAG_UNDERGROUND_INSIDE;
547 viewport->flags |= VIEWPORT_FLAG_UNDERGROUND_INSIDE;
548 if (bit)
549 return;
550 }
551 window->Invalidate();
552 }
553 }
554
555 /**
556 *
557 * rct2: 0x006E7A3A
558 */
viewport_update_position(rct_window * window)559 void viewport_update_position(rct_window* window)
560 {
561 window_event_resize_call(window);
562
563 rct_viewport* viewport = window->viewport;
564 if (viewport == nullptr)
565 return;
566
567 if (window->viewport_smart_follow_sprite != SPRITE_INDEX_NULL)
568 {
569 viewport_update_smart_sprite_follow(window);
570 }
571
572 if (window->viewport_target_sprite != SPRITE_INDEX_NULL)
573 {
574 viewport_update_sprite_follow(window);
575 return;
576 }
577
578 viewport_set_underground_flag(0, window, viewport);
579
580 auto viewportMidPoint = ScreenCoordsXY{ window->savedViewPos.x + viewport->view_width / 2,
581 window->savedViewPos.y + viewport->view_height / 2 };
582
583 auto mapCoord = viewport_coord_to_map_coord(viewportMidPoint, 0);
584
585 // Clamp to the map minimum value
586 int32_t at_map_edge = 0;
587 if (mapCoord.x < MAP_MINIMUM_X_Y)
588 {
589 mapCoord.x = MAP_MINIMUM_X_Y;
590 at_map_edge = 1;
591 }
592 if (mapCoord.y < MAP_MINIMUM_X_Y)
593 {
594 mapCoord.y = MAP_MINIMUM_X_Y;
595 at_map_edge = 1;
596 }
597
598 // Clamp to the map maximum value (scenario specific)
599 if (mapCoord.x > GetMapSizeMinus2())
600 {
601 mapCoord.x = GetMapSizeMinus2();
602 at_map_edge = 1;
603 }
604 if (mapCoord.y > GetMapSizeMinus2())
605 {
606 mapCoord.y = GetMapSizeMinus2();
607 at_map_edge = 1;
608 }
609
610 if (at_map_edge)
611 {
612 auto centreLoc = centre_2d_coordinates({ mapCoord, 0 }, viewport);
613 if (centreLoc.has_value())
614 {
615 window->savedViewPos = centreLoc.value();
616 }
617 }
618
619 auto windowCoords = window->savedViewPos;
620 if (window->flags & WF_SCROLLING_TO_LOCATION)
621 {
622 // Moves the viewport if focusing in on an item
623 uint8_t flags = 0;
624 windowCoords.x -= viewport->viewPos.x;
625 if (windowCoords.x < 0)
626 {
627 windowCoords.x = -windowCoords.x;
628 flags |= 1;
629 }
630 windowCoords.y -= viewport->viewPos.y;
631 if (windowCoords.y < 0)
632 {
633 windowCoords.y = -windowCoords.y;
634 flags |= 2;
635 }
636 windowCoords.x = (windowCoords.x + 7) / 8;
637 windowCoords.y = (windowCoords.y + 7) / 8;
638
639 // If we are at the final zoom position
640 if (!windowCoords.x && !windowCoords.y)
641 {
642 window->flags &= ~WF_SCROLLING_TO_LOCATION;
643 }
644 if (flags & 1)
645 {
646 windowCoords.x = -windowCoords.x;
647 }
648 if (flags & 2)
649 {
650 windowCoords.y = -windowCoords.y;
651 }
652 windowCoords.x += viewport->viewPos.x;
653 windowCoords.y += viewport->viewPos.y;
654 }
655
656 viewport_move(windowCoords, window, viewport);
657 }
658
viewport_update_sprite_follow(rct_window * window)659 void viewport_update_sprite_follow(rct_window* window)
660 {
661 if (window->viewport_target_sprite != SPRITE_INDEX_NULL && window->viewport != nullptr)
662 {
663 auto* sprite = GetEntity(window->viewport_target_sprite);
664 if (sprite == nullptr)
665 {
666 return;
667 }
668 int32_t height = (tile_element_height({ sprite->x, sprite->y })) - 16;
669 int32_t underground = sprite->z < height;
670
671 viewport_set_underground_flag(underground, window, window->viewport);
672
673 auto centreLoc = centre_2d_coordinates(sprite->GetLocation(), window->viewport);
674 if (centreLoc.has_value())
675 {
676 window->savedViewPos = *centreLoc;
677 viewport_move(*centreLoc, window, window->viewport);
678 }
679 }
680 }
681
viewport_update_smart_sprite_follow(rct_window * window)682 void viewport_update_smart_sprite_follow(rct_window* window)
683 {
684 auto entity = TryGetEntity(window->viewport_smart_follow_sprite);
685 if (entity == nullptr || entity->Type == EntityType::Null)
686 {
687 window->viewport_smart_follow_sprite = SPRITE_INDEX_NULL;
688 window->viewport_target_sprite = SPRITE_INDEX_NULL;
689 return;
690 }
691
692 switch (entity->Type)
693 {
694 case EntityType::Vehicle:
695 viewport_update_smart_vehicle_follow(window);
696 break;
697
698 case EntityType::Guest:
699 viewport_update_smart_guest_follow(window, entity->As<Guest>());
700 break;
701
702 case EntityType::Staff:
703 viewport_update_smart_staff_follow(window, entity->As<Staff>());
704 break;
705
706 default: // All other types don't need any "smart" following; steam particle, duck, money effect, etc.
707 window->focus = Focus(window->viewport_smart_follow_sprite);
708 window->viewport_target_sprite = window->viewport_smart_follow_sprite;
709 break;
710 }
711 }
712
viewport_update_smart_guest_follow(rct_window * window,const Guest * peep)713 void viewport_update_smart_guest_follow(rct_window* window, const Guest* peep)
714 {
715 Focus focus = Focus(peep->sprite_index);
716 window->viewport_target_sprite = peep->sprite_index;
717
718 if (peep->State == PeepState::Picked)
719 {
720 window->viewport_smart_follow_sprite = SPRITE_INDEX_NULL;
721 window->viewport_target_sprite = SPRITE_INDEX_NULL;
722 window->focus = std::nullopt; // No focus
723 return;
724 }
725
726 bool overallFocus = true;
727 if (peep->State == PeepState::OnRide || peep->State == PeepState::EnteringRide
728 || (peep->State == PeepState::LeavingRide && peep->x == LOCATION_NULL))
729 {
730 auto ride = get_ride(peep->CurrentRide);
731 if (ride != nullptr && (ride->lifecycle_flags & RIDE_LIFECYCLE_ON_TRACK))
732 {
733 auto train = GetEntity<Vehicle>(ride->vehicles[peep->CurrentTrain]);
734 if (train != nullptr)
735 {
736 const auto car = train->GetCar(peep->CurrentCar);
737 if (car != nullptr)
738 {
739 focus = Focus(car->sprite_index);
740 overallFocus = false;
741 window->viewport_target_sprite = car->sprite_index;
742 }
743 }
744 }
745 }
746
747 if (peep->x == LOCATION_NULL && overallFocus)
748 {
749 auto ride = get_ride(peep->CurrentRide);
750 if (ride != nullptr)
751 {
752 auto xy = ride->overall_view.ToTileCentre();
753 CoordsXYZ coordFocus;
754 coordFocus.x = xy.x;
755 coordFocus.y = xy.y;
756 coordFocus.z = tile_element_height(xy) + (4 * COORDS_Z_STEP);
757 focus = Focus(coordFocus);
758 window->viewport_target_sprite = SPRITE_INDEX_NULL;
759 }
760 }
761
762 window->focus = focus;
763 }
764
viewport_update_smart_staff_follow(rct_window * window,const Staff * peep)765 void viewport_update_smart_staff_follow(rct_window* window, const Staff* peep)
766 {
767 if (peep->State == PeepState::Picked)
768 {
769 window->viewport_smart_follow_sprite = SPRITE_INDEX_NULL;
770 window->viewport_target_sprite = SPRITE_INDEX_NULL;
771 window->focus = std::nullopt;
772 return;
773 }
774
775 window->focus = Focus(window->viewport_smart_follow_sprite);
776 window->viewport_target_sprite = window->viewport_smart_follow_sprite;
777 }
778
viewport_update_smart_vehicle_follow(rct_window * window)779 void viewport_update_smart_vehicle_follow(rct_window* window)
780 {
781 window->focus = Focus(window->viewport_smart_follow_sprite);
782 window->viewport_target_sprite = window->viewport_smart_follow_sprite;
783 }
784
785 /**
786 *
787 * rct2: 0x00685C02
788 * ax: left
789 * bx: top
790 * dx: right
791 * esi: viewport
792 * edi: dpi
793 * ebp: bottom
794 */
viewport_render(rct_drawpixelinfo * dpi,const rct_viewport * viewport,const ScreenRect & screenRect,std::vector<RecordedPaintSession> * sessions)795 void viewport_render(
796 rct_drawpixelinfo* dpi, const rct_viewport* viewport, const ScreenRect& screenRect,
797 std::vector<RecordedPaintSession>* sessions)
798 {
799 auto [topLeft, bottomRight] = screenRect;
800
801 if (bottomRight.x <= viewport->pos.x)
802 return;
803 if (bottomRight.y <= viewport->pos.y)
804 return;
805 if (topLeft.x >= viewport->pos.x + viewport->width)
806 return;
807 if (topLeft.y >= viewport->pos.y + viewport->height)
808 return;
809
810 #ifdef DEBUG_SHOW_DIRTY_BOX
811 const auto dirtyBoxTopLeft = topLeft;
812 const auto dirtyBoxTopRight = bottomRight - ScreenCoordsXY{ 1, 1 };
813 #endif
814
815 topLeft -= viewport->pos;
816 topLeft = ScreenCoordsXY{
817 std::max(topLeft.x, 0) * viewport->zoom,
818 std::max(topLeft.y, 0) * viewport->zoom,
819 } + viewport->viewPos;
820
821 bottomRight -= viewport->pos;
822 bottomRight = ScreenCoordsXY{
823 std::min(bottomRight.x, viewport->width) * viewport->zoom,
824 std::min(bottomRight.y, viewport->height) * viewport->zoom,
825 } + viewport->viewPos;
826
827 viewport_paint(viewport, dpi, { topLeft, bottomRight }, sessions);
828
829 #ifdef DEBUG_SHOW_DIRTY_BOX
830 // FIXME g_viewport_list doesn't exist anymore
831 if (viewport != g_viewport_list)
832 gfx_fill_rect_inset(dpi, { dirtyBoxTopLeft, dirtyBoxTopRight }, 0x2, INSET_RECT_F_30);
833 #endif
834 }
835
record_session(const paint_session * session,std::vector<RecordedPaintSession> * recorded_sessions,size_t record_index)836 static void record_session(
837 const paint_session* session, std::vector<RecordedPaintSession>* recorded_sessions, size_t record_index)
838 {
839 // Perform a deep copy of the paint session, use relative offsets.
840 // This is done to extract the session for benchmark.
841 // Place the copied session at provided record_index, so the caller can decide which columns/paint sessions to copy;
842 // there is no column information embedded in the session itself.
843 auto& recordedSession = recorded_sessions->at(record_index);
844 recordedSession.Session = *session;
845 recordedSession.Entries.resize(session->PaintEntryChain.GetCount());
846
847 // Mind the offset needs to be calculated against the original `session`, not `session_copy`
848 std::unordered_map<paint_struct*, paint_struct*> entryRemap;
849
850 // Copy all entries
851 auto paintIndex = 0;
852 auto chain = session->PaintEntryChain.Head;
853 while (chain != nullptr)
854 {
855 for (size_t i = 0; i < chain->Count; i++)
856 {
857 auto& src = chain->PaintStructs[i];
858 auto& dst = recordedSession.Entries[paintIndex++];
859 dst = src;
860 entryRemap[&src.basic] = reinterpret_cast<paint_struct*>(i * sizeof(paint_entry));
861 }
862 chain = chain->Next;
863 }
864 entryRemap[nullptr] = reinterpret_cast<paint_struct*>(-1);
865
866 // Remap all entries
867 for (auto& ps : recordedSession.Entries)
868 {
869 auto& ptr = ps.basic.next_quadrant_ps;
870 auto it = entryRemap.find(ptr);
871 if (it == entryRemap.end())
872 {
873 assert(false);
874 ptr = nullptr;
875 }
876 else
877 {
878 ptr = it->second;
879 }
880 }
881 for (auto& ptr : recordedSession.Session.Quadrants)
882 {
883 auto it = entryRemap.find(ptr);
884 if (it == entryRemap.end())
885 {
886 assert(false);
887 ptr = nullptr;
888 }
889 else
890 {
891 ptr = it->second;
892 }
893 }
894 }
895
viewport_fill_column(paint_session * session,std::vector<RecordedPaintSession> * recorded_sessions,size_t record_index)896 static void viewport_fill_column(
897 paint_session* session, std::vector<RecordedPaintSession>* recorded_sessions, size_t record_index)
898 {
899 PaintSessionGenerate(session);
900 if (recorded_sessions != nullptr)
901 {
902 record_session(session, recorded_sessions, record_index);
903 }
904 PaintSessionArrange(session);
905 }
906
viewport_paint_column(paint_session * session)907 static void viewport_paint_column(paint_session* session)
908 {
909 if (session->ViewFlags
910 & (VIEWPORT_FLAG_HIDE_VERTICAL | VIEWPORT_FLAG_HIDE_BASE | VIEWPORT_FLAG_UNDERGROUND_INSIDE
911 | VIEWPORT_FLAG_CLIP_VIEW)
912 && (~session->ViewFlags & VIEWPORT_FLAG_TRANSPARENT_BACKGROUND))
913 {
914 uint8_t colour = COLOUR_AQUAMARINE;
915 if (session->ViewFlags & VIEWPORT_FLAG_INVISIBLE_SPRITES)
916 {
917 colour = COLOUR_BLACK;
918 }
919 gfx_clear(&session->DPI, colour);
920 }
921
922 PaintDrawStructs(session);
923
924 if (gConfigGeneral.render_weather_gloom && !gTrackDesignSaveMode && !(session->ViewFlags & VIEWPORT_FLAG_INVISIBLE_SPRITES)
925 && !(session->ViewFlags & VIEWPORT_FLAG_HIGHLIGHT_PATH_ISSUES))
926 {
927 viewport_paint_weather_gloom(&session->DPI);
928 }
929
930 if (session->PSStringHead != nullptr)
931 {
932 PaintDrawMoneyStructs(&session->DPI, session->PSStringHead);
933 }
934 }
935
936 /**
937 *
938 * rct2: 0x00685CBF
939 * eax: left
940 * ebx: top
941 * edx: right
942 * esi: viewport
943 * edi: dpi
944 * ebp: bottom
945 */
viewport_paint(const rct_viewport * viewport,rct_drawpixelinfo * dpi,const ScreenRect & screenRect,std::vector<RecordedPaintSession> * recorded_sessions)946 void viewport_paint(
947 const rct_viewport* viewport, rct_drawpixelinfo* dpi, const ScreenRect& screenRect,
948 std::vector<RecordedPaintSession>* recorded_sessions)
949 {
950 uint32_t viewFlags = viewport->flags;
951 uint32_t width = screenRect.GetWidth();
952 uint32_t height = screenRect.GetHeight();
953 uint32_t bitmask = viewport->zoom >= 0 ? 0xFFFFFFFF & (0xFFFFFFFF * viewport->zoom) : 0xFFFFFFFF;
954 ScreenCoordsXY topLeft = screenRect.Point1;
955
956 width &= bitmask;
957 height &= bitmask;
958 topLeft.x &= bitmask;
959 topLeft.y &= bitmask;
960
961 auto x = topLeft.x - static_cast<int32_t>(viewport->viewPos.x & bitmask);
962 x = x / viewport->zoom;
963 x += viewport->pos.x;
964
965 auto y = topLeft.y - static_cast<int32_t>(viewport->viewPos.y & bitmask);
966 y = y / viewport->zoom;
967 y += viewport->pos.y;
968
969 rct_drawpixelinfo dpi1;
970 dpi1.DrawingEngine = dpi->DrawingEngine;
971 dpi1.bits = dpi->bits + (x - dpi->x) + ((y - dpi->y) * (dpi->width + dpi->pitch));
972 dpi1.x = topLeft.x;
973 dpi1.y = topLeft.y;
974 dpi1.width = width;
975 dpi1.height = height;
976 dpi1.pitch = (dpi->width + dpi->pitch) - (width / viewport->zoom);
977 dpi1.zoom_level = viewport->zoom;
978 dpi1.remX = std::max(0, dpi->x - x);
979 dpi1.remY = std::max(0, dpi->y - y);
980
981 // make sure, the compare operation is done in int32_t to avoid the loop becoming an infiniteloop.
982 // this as well as the [x += 32] in the loop causes signed integer overflow -> undefined behaviour.
983 auto rightBorder = dpi1.x + dpi1.width;
984 auto alignedX = floor2(dpi1.x, 32);
985
986 _paintColumns.clear();
987
988 bool useMultithreading = gConfigGeneral.multithreading;
989 if (useMultithreading && _paintJobs == nullptr)
990 {
991 _paintJobs = std::make_unique<JobPool>();
992 }
993 else if (useMultithreading == false && _paintJobs != nullptr)
994 {
995 _paintJobs.reset();
996 }
997
998 bool useParallelDrawing = false;
999 if (useMultithreading && (dpi->DrawingEngine->GetFlags() & DEF_PARALLEL_DRAWING))
1000 {
1001 useParallelDrawing = true;
1002 }
1003
1004 // Create space to record sessions and keep track which index is being drawn
1005 size_t index = 0;
1006 if (recorded_sessions != nullptr)
1007 {
1008 auto columnSize = rightBorder - alignedX;
1009 auto columnCount = (columnSize + 31) / 32;
1010 recorded_sessions->resize(columnCount);
1011 }
1012
1013 // Generate and sort columns.
1014 for (x = alignedX; x < rightBorder; x += 32, index++)
1015 {
1016 paint_session* session = PaintSessionAlloc(&dpi1, viewFlags);
1017 _paintColumns.push_back(session);
1018
1019 rct_drawpixelinfo& dpi2 = session->DPI;
1020 if (x >= dpi2.x)
1021 {
1022 auto leftPitch = x - dpi2.x;
1023 dpi2.width -= leftPitch;
1024 dpi2.bits += leftPitch / dpi2.zoom_level;
1025 dpi2.pitch += leftPitch / dpi2.zoom_level;
1026 dpi2.x = x;
1027 }
1028
1029 auto paintRight = dpi2.x + dpi2.width;
1030 if (paintRight >= x + 32)
1031 {
1032 auto rightPitch = paintRight - x - 32;
1033 paintRight -= rightPitch;
1034 dpi2.pitch += rightPitch / dpi2.zoom_level;
1035 }
1036 dpi2.width = paintRight - dpi2.x;
1037
1038 if (useMultithreading)
1039 {
1040 _paintJobs->AddTask(
1041 [session, recorded_sessions, index]() -> void { viewport_fill_column(session, recorded_sessions, index); });
1042 }
1043 else
1044 {
1045 viewport_fill_column(session, recorded_sessions, index);
1046 }
1047 }
1048
1049 if (useMultithreading)
1050 {
1051 _paintJobs->Join();
1052 }
1053
1054 // Paint columns.
1055 for (auto* session : _paintColumns)
1056 {
1057 if (useParallelDrawing)
1058 {
1059 _paintJobs->AddTask([session]() -> void { viewport_paint_column(session); });
1060 }
1061 else
1062 {
1063 viewport_paint_column(session);
1064 }
1065 }
1066 if (useParallelDrawing)
1067 {
1068 _paintJobs->Join();
1069 }
1070
1071 // Release resources.
1072 for (auto* session : _paintColumns)
1073 {
1074 PaintSessionFree(session);
1075 }
1076 }
1077
viewport_paint_weather_gloom(rct_drawpixelinfo * dpi)1078 static void viewport_paint_weather_gloom(rct_drawpixelinfo* dpi)
1079 {
1080 auto paletteId = climate_get_weather_gloom_palette_id(gClimateCurrent);
1081 if (paletteId != FilterPaletteID::PaletteNull)
1082 {
1083 // Only scale width if zoomed in more than 1:1
1084 auto zoomLevel = dpi->zoom_level < 0 ? dpi->zoom_level : 0;
1085 auto x = dpi->x;
1086 auto y = dpi->y;
1087 auto w = (dpi->width / zoomLevel) - 1;
1088 auto h = (dpi->height / zoomLevel) - 1;
1089 gfx_filter_rect(dpi, ScreenRect(x, y, x + w, y + h), paletteId);
1090 }
1091 }
1092
1093 /**
1094 *
1095 * rct2: 0x0068958D
1096 */
screen_pos_to_map_pos(const ScreenCoordsXY & screenCoords,int32_t * direction)1097 std::optional<CoordsXY> screen_pos_to_map_pos(const ScreenCoordsXY& screenCoords, int32_t* direction)
1098 {
1099 auto mapCoords = screen_get_map_xy(screenCoords, nullptr);
1100 if (!mapCoords.has_value())
1101 return std::nullopt;
1102
1103 int32_t my_direction;
1104 int32_t dist_from_centre_x = abs(mapCoords->x % 32);
1105 int32_t dist_from_centre_y = abs(mapCoords->y % 32);
1106 if (dist_from_centre_x > 8 && dist_from_centre_x < 24 && dist_from_centre_y > 8 && dist_from_centre_y < 24)
1107 {
1108 my_direction = 4;
1109 }
1110 else
1111 {
1112 auto mod_x = mapCoords->x & 0x1F;
1113 auto mod_y = mapCoords->y & 0x1F;
1114 if (mod_x <= 16)
1115 {
1116 if (mod_y < 16)
1117 {
1118 my_direction = 2;
1119 }
1120 else
1121 {
1122 my_direction = 3;
1123 }
1124 }
1125 else
1126 {
1127 if (mod_y < 16)
1128 {
1129 my_direction = 1;
1130 }
1131 else
1132 {
1133 my_direction = 0;
1134 }
1135 }
1136 }
1137
1138 if (direction != nullptr)
1139 *direction = my_direction;
1140 return { mapCoords->ToTileStart() };
1141 }
1142
ScreenToViewportCoord(const ScreenCoordsXY & screenCoords) const1143 [[nodiscard]] ScreenCoordsXY rct_viewport::ScreenToViewportCoord(const ScreenCoordsXY& screenCoords) const
1144 {
1145 ScreenCoordsXY ret;
1146 ret.x = ((screenCoords.x - pos.x) * zoom) + viewPos.x;
1147 ret.y = ((screenCoords.y - pos.y) * zoom) + viewPos.y;
1148 return ret;
1149 }
1150
Invalidate() const1151 void rct_viewport::Invalidate() const
1152 {
1153 viewport_invalidate(this, { viewPos, viewPos + ScreenCoordsXY{ view_width, view_height } });
1154 }
1155
viewport_coord_to_map_coord(const ScreenCoordsXY & coords,int32_t z)1156 CoordsXY viewport_coord_to_map_coord(const ScreenCoordsXY& coords, int32_t z)
1157 {
1158 // Reverse of translate_3d_to_2d_with_z
1159 CoordsXY ret = { coords.y - coords.x / 2 + z, coords.y + coords.x / 2 + z };
1160 auto inverseRotation = DirectionFlipXAxis(get_current_rotation());
1161 return ret.Rotate(inverseRotation);
1162 }
1163
1164 /**
1165 *
1166 * rct2: 0x00664689
1167 */
show_gridlines()1168 void show_gridlines()
1169 {
1170 if (gShowGridLinesRefCount == 0)
1171 {
1172 rct_window* mainWindow = window_get_main();
1173 if (mainWindow != nullptr)
1174 {
1175 if (!(mainWindow->viewport->flags & VIEWPORT_FLAG_GRIDLINES))
1176 {
1177 mainWindow->viewport->flags |= VIEWPORT_FLAG_GRIDLINES;
1178 mainWindow->Invalidate();
1179 }
1180 }
1181 }
1182 gShowGridLinesRefCount++;
1183 }
1184
1185 /**
1186 *
1187 * rct2: 0x006646B4
1188 */
hide_gridlines()1189 void hide_gridlines()
1190 {
1191 gShowGridLinesRefCount--;
1192 if (gShowGridLinesRefCount == 0)
1193 {
1194 rct_window* mainWindow = window_get_main();
1195 if (mainWindow != nullptr)
1196 {
1197 if (!gConfigGeneral.always_show_gridlines)
1198 {
1199 mainWindow->viewport->flags &= ~VIEWPORT_FLAG_GRIDLINES;
1200 mainWindow->Invalidate();
1201 }
1202 }
1203 }
1204 }
1205
1206 /**
1207 *
1208 * rct2: 0x00664E8E
1209 */
show_land_rights()1210 void show_land_rights()
1211 {
1212 if (gShowLandRightsRefCount == 0)
1213 {
1214 rct_window* mainWindow = window_get_main();
1215 if (mainWindow != nullptr)
1216 {
1217 if (!(mainWindow->viewport->flags & VIEWPORT_FLAG_LAND_OWNERSHIP))
1218 {
1219 mainWindow->viewport->flags |= VIEWPORT_FLAG_LAND_OWNERSHIP;
1220 mainWindow->Invalidate();
1221 }
1222 }
1223 }
1224 gShowLandRightsRefCount++;
1225 }
1226
1227 /**
1228 *
1229 * rct2: 0x00664EB9
1230 */
hide_land_rights()1231 void hide_land_rights()
1232 {
1233 gShowLandRightsRefCount--;
1234 if (gShowLandRightsRefCount == 0)
1235 {
1236 rct_window* mainWindow = window_get_main();
1237 if (mainWindow != nullptr)
1238 {
1239 if (mainWindow->viewport->flags & VIEWPORT_FLAG_LAND_OWNERSHIP)
1240 {
1241 mainWindow->viewport->flags &= ~VIEWPORT_FLAG_LAND_OWNERSHIP;
1242 mainWindow->Invalidate();
1243 }
1244 }
1245 }
1246 }
1247
1248 /**
1249 *
1250 * rct2: 0x00664EDD
1251 */
show_construction_rights()1252 void show_construction_rights()
1253 {
1254 if (gShowConstuctionRightsRefCount == 0)
1255 {
1256 rct_window* mainWindow = window_get_main();
1257 if (mainWindow != nullptr)
1258 {
1259 if (!(mainWindow->viewport->flags & VIEWPORT_FLAG_CONSTRUCTION_RIGHTS))
1260 {
1261 mainWindow->viewport->flags |= VIEWPORT_FLAG_CONSTRUCTION_RIGHTS;
1262 mainWindow->Invalidate();
1263 }
1264 }
1265 }
1266 gShowConstuctionRightsRefCount++;
1267 }
1268
1269 /**
1270 *
1271 * rct2: 0x00664F08
1272 */
hide_construction_rights()1273 void hide_construction_rights()
1274 {
1275 gShowConstuctionRightsRefCount--;
1276 if (gShowConstuctionRightsRefCount == 0)
1277 {
1278 rct_window* mainWindow = window_get_main();
1279 if (mainWindow != nullptr)
1280 {
1281 if (mainWindow->viewport->flags & VIEWPORT_FLAG_CONSTRUCTION_RIGHTS)
1282 {
1283 mainWindow->viewport->flags &= ~VIEWPORT_FLAG_CONSTRUCTION_RIGHTS;
1284 mainWindow->Invalidate();
1285 }
1286 }
1287 }
1288 }
1289
1290 /**
1291 *
1292 * rct2: 0x006CB70A
1293 */
viewport_set_visibility(uint8_t mode)1294 void viewport_set_visibility(uint8_t mode)
1295 {
1296 rct_window* window = window_get_main();
1297
1298 if (window != nullptr)
1299 {
1300 rct_viewport* vp = window->viewport;
1301 uint32_t invalidate = 0;
1302
1303 switch (mode)
1304 {
1305 case 0:
1306 { // Set all these flags to 0, and invalidate if any were active
1307 uint32_t mask = VIEWPORT_FLAG_UNDERGROUND_INSIDE | VIEWPORT_FLAG_SEETHROUGH_RIDES
1308 | VIEWPORT_FLAG_SEETHROUGH_SCENERY | VIEWPORT_FLAG_SEETHROUGH_PATHS | VIEWPORT_FLAG_INVISIBLE_SUPPORTS
1309 | VIEWPORT_FLAG_LAND_HEIGHTS | VIEWPORT_FLAG_TRACK_HEIGHTS | VIEWPORT_FLAG_PATH_HEIGHTS
1310 | VIEWPORT_FLAG_INVISIBLE_PEEPS | VIEWPORT_FLAG_HIDE_BASE | VIEWPORT_FLAG_HIDE_VERTICAL;
1311
1312 invalidate += vp->flags & mask;
1313 vp->flags &= ~mask;
1314 break;
1315 }
1316 case 1: // 6CB79D
1317 case 4: // 6CB7C4
1318 // Set underground on, invalidate if it was off
1319 invalidate += !(vp->flags & VIEWPORT_FLAG_UNDERGROUND_INSIDE);
1320 vp->flags |= VIEWPORT_FLAG_UNDERGROUND_INSIDE;
1321 break;
1322 case 2: // 6CB7EB
1323 // Set track heights on, invalidate if off
1324 invalidate += !(vp->flags & VIEWPORT_FLAG_TRACK_HEIGHTS);
1325 vp->flags |= VIEWPORT_FLAG_TRACK_HEIGHTS;
1326 break;
1327 case 3: // 6CB7B1
1328 case 5: // 6CB7D8
1329 // Set underground off, invalidate if it was on
1330 invalidate += vp->flags & VIEWPORT_FLAG_UNDERGROUND_INSIDE;
1331 vp->flags &= ~(static_cast<uint16_t>(VIEWPORT_FLAG_UNDERGROUND_INSIDE));
1332 break;
1333 }
1334 if (invalidate != 0)
1335 window->Invalidate();
1336 }
1337 }
1338
1339 /**
1340 * Checks if a paint_struct sprite type is in the filter mask.
1341 */
PSSpriteTypeIsInFilter(paint_struct * ps,uint16_t filter)1342 static bool PSSpriteTypeIsInFilter(paint_struct* ps, uint16_t filter)
1343 {
1344 if (ps->sprite_type != ViewportInteractionItem::None && ps->sprite_type != ViewportInteractionItem::Label
1345 && ps->sprite_type <= ViewportInteractionItem::Banner)
1346 {
1347 auto mask = EnumToFlag(ps->sprite_type);
1348 if (filter & mask)
1349 {
1350 return true;
1351 }
1352 }
1353 return false;
1354 }
1355
1356 /**
1357 * rct2: 0x00679236, 0x00679662, 0x00679B0D, 0x00679FF1
1358 */
is_pixel_present_bmp(uint32_t imageType,const rct_g1_element * g1,const uint8_t * index,const PaletteMap & paletteMap)1359 static bool is_pixel_present_bmp(
1360 uint32_t imageType, const rct_g1_element* g1, const uint8_t* index, const PaletteMap& paletteMap)
1361 {
1362 // Probably used to check for corruption
1363 if (!(g1->flags & G1_FLAG_BMP))
1364 {
1365 return false;
1366 }
1367
1368 if (imageType & IMAGE_TYPE_REMAP)
1369 {
1370 return paletteMap[*index] != 0;
1371 }
1372
1373 if (imageType & IMAGE_TYPE_TRANSPARENT)
1374 {
1375 return false;
1376 }
1377
1378 return (*index != 0);
1379 }
1380
1381 /**
1382 * rct2: 0x0067933B, 0x00679788, 0x00679C4A, 0x0067A117
1383 */
is_pixel_present_rle(const uint8_t * esi,int32_t x_start_point,int32_t y_start_point,int32_t round)1384 static bool is_pixel_present_rle(const uint8_t* esi, int32_t x_start_point, int32_t y_start_point, int32_t round)
1385 {
1386 uint32_t start_offset = esi[y_start_point * 2] | (esi[y_start_point * 2 + 1] << 8);
1387 const uint8_t* ebx = esi + start_offset;
1388
1389 uint8_t last_data_line = 0;
1390 while (!last_data_line)
1391 {
1392 int32_t no_pixels = *ebx++;
1393 uint8_t gap_size = *ebx++;
1394
1395 last_data_line = no_pixels & 0x80;
1396
1397 no_pixels &= 0x7F;
1398
1399 ebx += no_pixels;
1400
1401 if (round > 1)
1402 {
1403 if (gap_size % 2)
1404 {
1405 gap_size++;
1406 no_pixels--;
1407 if (no_pixels == 0)
1408 {
1409 continue;
1410 }
1411 }
1412 }
1413
1414 if (round == 4)
1415 {
1416 if (gap_size % 4)
1417 {
1418 gap_size += 2;
1419 no_pixels -= 2;
1420 if (no_pixels <= 0)
1421 {
1422 continue;
1423 }
1424 }
1425 }
1426
1427 int32_t x_start = gap_size - x_start_point;
1428 if (x_start <= 0)
1429 {
1430 no_pixels += x_start;
1431 if (no_pixels <= 0)
1432 {
1433 continue;
1434 }
1435
1436 x_start = 0;
1437 }
1438 else
1439 {
1440 // Do nothing?
1441 }
1442
1443 x_start += no_pixels;
1444 x_start--;
1445 if (x_start > 0)
1446 {
1447 no_pixels -= x_start;
1448 if (no_pixels <= 0)
1449 {
1450 continue;
1451 }
1452 }
1453
1454 if (round > 1)
1455 {
1456 // This matches the original implementation, but allows empty lines to cause false positives on zoom 0
1457 if (ceil2(no_pixels, round) == 0)
1458 continue;
1459 }
1460
1461 return true;
1462 }
1463
1464 return false;
1465 }
1466
1467 /**
1468 * rct2: 0x00679074
1469 *
1470 * @param dpi (edi)
1471 * @param imageId (ebx)
1472 * @param x (cx)
1473 * @param y (dx)
1474 * @return value originally stored in 0x00141F569
1475 */
is_sprite_interacted_with_palette_set(rct_drawpixelinfo * dpi,int32_t imageId,const ScreenCoordsXY & coords,const PaletteMap & paletteMap)1476 static bool is_sprite_interacted_with_palette_set(
1477 rct_drawpixelinfo* dpi, int32_t imageId, const ScreenCoordsXY& coords, const PaletteMap& paletteMap)
1478 {
1479 const rct_g1_element* g1 = gfx_get_g1_element(imageId & 0x7FFFF);
1480 if (g1 == nullptr)
1481 {
1482 return false;
1483 }
1484
1485 if (dpi->zoom_level > 0)
1486 {
1487 if (g1->flags & G1_FLAG_NO_ZOOM_DRAW)
1488 {
1489 return false;
1490 }
1491
1492 if (g1->flags & G1_FLAG_HAS_ZOOM_SPRITE)
1493 {
1494 // TODO: SAR in dpi done with `>> 1`, in coordinates with `/ 2`
1495 rct_drawpixelinfo zoomed_dpi = {
1496 /* .bits = */ dpi->bits,
1497 /* .x = */ dpi->x >> 1,
1498 /* .y = */ dpi->y >> 1,
1499 /* .height = */ dpi->height,
1500 /* .width = */ dpi->width,
1501 /* .pitch = */ dpi->pitch,
1502 /* .zoom_level = */ dpi->zoom_level - 1,
1503 };
1504
1505 return is_sprite_interacted_with_palette_set(
1506 &zoomed_dpi, imageId - g1->zoomed_offset, { coords.x / 2, coords.y / 2 }, paletteMap);
1507 }
1508 }
1509
1510 int32_t round = std::max(1, 1 * dpi->zoom_level);
1511
1512 auto origin = coords;
1513 if (g1->flags & G1_FLAG_RLE_COMPRESSION)
1514 {
1515 origin.y -= (round - 1);
1516 }
1517
1518 origin.y += g1->y_offset;
1519 int32_t yStartPoint = 0;
1520 int32_t height = g1->height;
1521 if (dpi->zoom_level != 0)
1522 {
1523 if (height % 2)
1524 {
1525 height--;
1526 yStartPoint++;
1527 }
1528
1529 if (dpi->zoom_level == 2)
1530 {
1531 if (height % 4)
1532 {
1533 height -= 2;
1534 yStartPoint += 2;
1535 }
1536 }
1537
1538 if (height == 0)
1539 {
1540 return false;
1541 }
1542 }
1543
1544 origin.y = floor2(origin.y, round);
1545 int32_t yEndPoint = height;
1546 origin.y -= dpi->y;
1547 if (origin.y < 0)
1548 {
1549 yEndPoint += origin.y;
1550 if (yEndPoint <= 0)
1551 {
1552 return false;
1553 }
1554
1555 yStartPoint -= origin.y;
1556 origin.y = 0;
1557 }
1558
1559 origin.y += yEndPoint;
1560 origin.y--;
1561 if (origin.y > 0)
1562 {
1563 yEndPoint -= origin.y;
1564 if (yEndPoint <= 0)
1565 {
1566 return false;
1567 }
1568 }
1569
1570 int32_t xStartPoint = 0;
1571 int32_t xEndPoint = g1->width;
1572
1573 origin.x += g1->x_offset;
1574 origin.x = floor2(origin.x, round);
1575 origin.x -= dpi->x;
1576 if (origin.x < 0)
1577 {
1578 xEndPoint += origin.x;
1579 if (xEndPoint <= 0)
1580 {
1581 return false;
1582 }
1583
1584 xStartPoint -= origin.x;
1585 origin.x = 0;
1586 }
1587
1588 origin.x += xEndPoint;
1589 origin.x--;
1590 if (origin.x > 0)
1591 {
1592 xEndPoint -= origin.x;
1593 if (xEndPoint <= 0)
1594 {
1595 return false;
1596 }
1597 }
1598
1599 if (g1->flags & G1_FLAG_RLE_COMPRESSION)
1600 {
1601 return is_pixel_present_rle(g1->offset, xStartPoint, yStartPoint, round);
1602 }
1603
1604 uint8_t* offset = g1->offset + (yStartPoint * g1->width) + xStartPoint;
1605 uint32_t imageType = _currentImageType;
1606
1607 if (!(g1->flags & G1_FLAG_1))
1608 {
1609 return is_pixel_present_bmp(imageType, g1, offset, paletteMap);
1610 }
1611
1612 Guard::Assert(false, "Invalid image type encountered.");
1613 return false;
1614 }
1615
1616 /**
1617 *
1618 * rct2: 0x00679023
1619 */
1620
is_sprite_interacted_with(rct_drawpixelinfo * dpi,int32_t imageId,const ScreenCoordsXY & coords)1621 static bool is_sprite_interacted_with(rct_drawpixelinfo* dpi, int32_t imageId, const ScreenCoordsXY& coords)
1622 {
1623 auto paletteMap = PaletteMap::GetDefault();
1624 imageId &= ~IMAGE_TYPE_TRANSPARENT;
1625 if (imageId & IMAGE_TYPE_REMAP)
1626 {
1627 _currentImageType = IMAGE_TYPE_REMAP;
1628 int32_t index = (imageId >> 19) & 0x7F;
1629 if (imageId & IMAGE_TYPE_REMAP_2_PLUS)
1630 {
1631 index &= 0x1F;
1632 }
1633 if (auto pm = GetPaletteMapForColour(index); pm.has_value())
1634 {
1635 paletteMap = pm.value();
1636 }
1637 }
1638 else
1639 {
1640 _currentImageType = 0;
1641 }
1642 return is_sprite_interacted_with_palette_set(dpi, imageId, coords, paletteMap);
1643 }
1644
1645 /**
1646 *
1647 * rct2: 0x0068862C
1648 */
set_interaction_info_from_paint_session(paint_session * session,uint16_t filter)1649 InteractionInfo set_interaction_info_from_paint_session(paint_session* session, uint16_t filter)
1650 {
1651 paint_struct* ps = &session->PaintHead;
1652 rct_drawpixelinfo* dpi = &session->DPI;
1653 InteractionInfo info{};
1654
1655 while ((ps = ps->next_quadrant_ps) != nullptr)
1656 {
1657 paint_struct* old_ps = ps;
1658 paint_struct* next_ps = ps;
1659 while (next_ps != nullptr)
1660 {
1661 ps = next_ps;
1662 if (is_sprite_interacted_with(dpi, ps->image_id, { ps->x, ps->y }))
1663 {
1664 if (PSSpriteTypeIsInFilter(ps, filter))
1665 {
1666 info = { ps };
1667 }
1668 }
1669 next_ps = ps->children;
1670 }
1671
1672 for (attached_paint_struct* attached_ps = ps->attached_ps; attached_ps != nullptr; attached_ps = attached_ps->next)
1673 {
1674 if (is_sprite_interacted_with(dpi, attached_ps->image_id, { (attached_ps->x + ps->x), (attached_ps->y + ps->y) }))
1675 {
1676 if (PSSpriteTypeIsInFilter(ps, filter))
1677 {
1678 info = { ps };
1679 }
1680 }
1681 }
1682
1683 ps = old_ps;
1684 }
1685 return info;
1686 }
1687
1688 /**
1689 *
1690 * rct2: 0x00685ADC
1691 * screenX: eax
1692 * screenY: ebx
1693 * flags: edx
1694 * x: ax
1695 * y: cx
1696 * interactionType: bl
1697 * tileElement: edx
1698 * viewport: edi
1699 */
get_map_coordinates_from_pos(const ScreenCoordsXY & screenCoords,int32_t flags)1700 InteractionInfo get_map_coordinates_from_pos(const ScreenCoordsXY& screenCoords, int32_t flags)
1701 {
1702 rct_window* window = window_find_from_point(screenCoords);
1703 return get_map_coordinates_from_pos_window(window, screenCoords, flags);
1704 }
1705
get_map_coordinates_from_pos_window(rct_window * window,const ScreenCoordsXY & screenCoords,int32_t flags)1706 InteractionInfo get_map_coordinates_from_pos_window(rct_window* window, const ScreenCoordsXY& screenCoords, int32_t flags)
1707 {
1708 InteractionInfo info{};
1709 if (window == nullptr || window->viewport == nullptr)
1710 {
1711 return info;
1712 }
1713
1714 rct_viewport* myviewport = window->viewport;
1715 auto viewLoc = screenCoords;
1716 viewLoc -= myviewport->pos;
1717 if (viewLoc.x >= 0 && viewLoc.x < static_cast<int32_t>(myviewport->width) && viewLoc.y >= 0
1718 && viewLoc.y < static_cast<int32_t>(myviewport->height))
1719 {
1720 viewLoc.x = viewLoc.x * myviewport->zoom;
1721 viewLoc.y = viewLoc.y * myviewport->zoom;
1722 viewLoc += myviewport->viewPos;
1723 if (myviewport->zoom > 0)
1724 {
1725 viewLoc.x &= (0xFFFFFFFF * myviewport->zoom) & 0xFFFFFFFF;
1726 viewLoc.y &= (0xFFFFFFFF * myviewport->zoom) & 0xFFFFFFFF;
1727 }
1728 rct_drawpixelinfo dpi;
1729 dpi.x = viewLoc.x;
1730 dpi.y = viewLoc.y;
1731 dpi.height = 1;
1732 dpi.zoom_level = myviewport->zoom;
1733 dpi.width = 1;
1734
1735 paint_session* session = PaintSessionAlloc(&dpi, myviewport->flags);
1736 PaintSessionGenerate(session);
1737 PaintSessionArrange(session);
1738 info = set_interaction_info_from_paint_session(session, flags & 0xFFFF);
1739 PaintSessionFree(session);
1740 }
1741 return info;
1742 }
1743
1744 /**
1745 * screenRect represents 2D map coordinates at zoom 0.
1746 */
viewport_invalidate(const rct_viewport * viewport,const ScreenRect & screenRect)1747 void viewport_invalidate(const rct_viewport* viewport, const ScreenRect& screenRect)
1748 {
1749 // if unknown viewport visibility, use the containing window to discover the status
1750 if (viewport->visibility == VisibilityCache::Unknown)
1751 {
1752 auto windowManager = GetContext()->GetUiContext()->GetWindowManager();
1753 auto owner = windowManager->GetOwner(viewport);
1754 if (owner != nullptr && owner->classification != WC_MAIN_WINDOW)
1755 {
1756 // note, window_is_visible will update viewport->visibility, so this should have a low hit count
1757 if (!window_is_visible(owner))
1758 {
1759 return;
1760 }
1761 }
1762 }
1763
1764 if (viewport->visibility == VisibilityCache::Covered)
1765 return;
1766
1767 auto [topLeft, bottomRight] = screenRect;
1768 const auto [viewportRight, viewportBottom] = viewport->viewPos
1769 + ScreenCoordsXY{ viewport->view_width, viewport->view_height };
1770
1771 if (bottomRight.x > viewport->viewPos.x && bottomRight.y > viewport->viewPos.y)
1772 {
1773 topLeft = { std::max(topLeft.x, viewport->viewPos.x), std::max(topLeft.y, viewport->viewPos.y) };
1774 topLeft -= viewport->viewPos;
1775 topLeft = { topLeft.x / viewport->zoom, topLeft.y / viewport->zoom };
1776 topLeft += viewport->pos;
1777
1778 bottomRight = { std::max(bottomRight.x, viewportRight), std::max(bottomRight.y, viewportBottom) };
1779 bottomRight -= viewport->viewPos;
1780 bottomRight = { bottomRight.x / viewport->zoom, bottomRight.y / viewport->zoom };
1781 bottomRight += viewport->pos;
1782
1783 gfx_set_dirty_blocks({ topLeft, bottomRight });
1784 }
1785 }
1786
viewport_find_from_point(const ScreenCoordsXY & screenCoords)1787 static rct_viewport* viewport_find_from_point(const ScreenCoordsXY& screenCoords)
1788 {
1789 rct_window* w = window_find_from_point(screenCoords);
1790 if (w == nullptr)
1791 return nullptr;
1792
1793 rct_viewport* viewport = w->viewport;
1794 if (viewport == nullptr)
1795 return nullptr;
1796
1797 if (viewport->ContainsScreen(screenCoords))
1798 return viewport;
1799
1800 return nullptr;
1801 }
1802
1803 /**
1804 *
1805 * rct2: 0x00688972
1806 * In:
1807 * screen_x: eax
1808 * screen_y: ebx
1809 * Out:
1810 * x: ax
1811 * y: bx
1812 * tile_element: edx ?
1813 * viewport: edi
1814 */
screen_get_map_xy(const ScreenCoordsXY & screenCoords,rct_viewport ** viewport)1815 std::optional<CoordsXY> screen_get_map_xy(const ScreenCoordsXY& screenCoords, rct_viewport** viewport)
1816 {
1817 // This will get the tile location but we will need the more accuracy
1818 rct_window* window = window_find_from_point(screenCoords);
1819 if (window == nullptr || window->viewport == nullptr)
1820 {
1821 return std::nullopt;
1822 }
1823 auto myViewport = window->viewport;
1824 auto info = get_map_coordinates_from_pos_window(window, screenCoords, EnumsToFlags(ViewportInteractionItem::Terrain));
1825 if (info.SpriteType == ViewportInteractionItem::None)
1826 {
1827 return std::nullopt;
1828 }
1829
1830 auto start_vp_pos = myViewport->ScreenToViewportCoord(screenCoords);
1831 CoordsXY cursorMapPos = info.Loc.ToTileCentre();
1832
1833 // Iterates the cursor location to work out exactly where on the tile it is
1834 for (int32_t i = 0; i < 5; i++)
1835 {
1836 int32_t z = tile_element_height(cursorMapPos);
1837 cursorMapPos = viewport_coord_to_map_coord(start_vp_pos, z);
1838 cursorMapPos.x = std::clamp(cursorMapPos.x, info.Loc.x, info.Loc.x + 31);
1839 cursorMapPos.y = std::clamp(cursorMapPos.y, info.Loc.y, info.Loc.y + 31);
1840 }
1841
1842 if (viewport != nullptr)
1843 *viewport = myViewport;
1844
1845 return cursorMapPos;
1846 }
1847
1848 /**
1849 *
1850 * rct2: 0x006894D4
1851 */
screen_get_map_xy_with_z(const ScreenCoordsXY & screenCoords,int32_t z)1852 std::optional<CoordsXY> screen_get_map_xy_with_z(const ScreenCoordsXY& screenCoords, int32_t z)
1853 {
1854 rct_viewport* viewport = viewport_find_from_point(screenCoords);
1855 if (viewport == nullptr)
1856 {
1857 return std::nullopt;
1858 }
1859
1860 auto vpCoords = viewport->ScreenToViewportCoord(screenCoords);
1861 auto mapPosition = viewport_coord_to_map_coord(vpCoords, z);
1862 if (!map_is_location_valid(mapPosition))
1863 {
1864 return std::nullopt;
1865 }
1866
1867 return mapPosition;
1868 }
1869
1870 /**
1871 *
1872 * rct2: 0x00689604
1873 */
screen_get_map_xy_quadrant(const ScreenCoordsXY & screenCoords,uint8_t * quadrant)1874 std::optional<CoordsXY> screen_get_map_xy_quadrant(const ScreenCoordsXY& screenCoords, uint8_t* quadrant)
1875 {
1876 auto mapCoords = screen_get_map_xy(screenCoords, nullptr);
1877 if (!mapCoords.has_value())
1878 return std::nullopt;
1879
1880 *quadrant = map_get_tile_quadrant(*mapCoords);
1881 return mapCoords->ToTileStart();
1882 }
1883
1884 /**
1885 *
1886 * rct2: 0x0068964B
1887 */
screen_get_map_xy_quadrant_with_z(const ScreenCoordsXY & screenCoords,int32_t z,uint8_t * quadrant)1888 std::optional<CoordsXY> screen_get_map_xy_quadrant_with_z(const ScreenCoordsXY& screenCoords, int32_t z, uint8_t* quadrant)
1889 {
1890 auto mapCoords = screen_get_map_xy_with_z(screenCoords, z);
1891 if (!mapCoords.has_value())
1892 return std::nullopt;
1893
1894 *quadrant = map_get_tile_quadrant(*mapCoords);
1895 return mapCoords->ToTileStart();
1896 }
1897
1898 /**
1899 *
1900 * rct2: 0x00689692
1901 */
screen_get_map_xy_side(const ScreenCoordsXY & screenCoords,uint8_t * side)1902 std::optional<CoordsXY> screen_get_map_xy_side(const ScreenCoordsXY& screenCoords, uint8_t* side)
1903 {
1904 auto mapCoords = screen_get_map_xy(screenCoords, nullptr);
1905 if (!mapCoords.has_value())
1906 return std::nullopt;
1907
1908 *side = map_get_tile_side(*mapCoords);
1909 return mapCoords->ToTileStart();
1910 }
1911
1912 /**
1913 *
1914 * rct2: 0x006896DC
1915 */
screen_get_map_xy_side_with_z(const ScreenCoordsXY & screenCoords,int32_t z,uint8_t * side)1916 std::optional<CoordsXY> screen_get_map_xy_side_with_z(const ScreenCoordsXY& screenCoords, int32_t z, uint8_t* side)
1917 {
1918 auto mapCoords = screen_get_map_xy_with_z(screenCoords, z);
1919 if (!mapCoords.has_value())
1920 return std::nullopt;
1921
1922 *side = map_get_tile_side(*mapCoords);
1923 return mapCoords->ToTileStart();
1924 }
1925
1926 /**
1927 * Get current viewport rotation.
1928 *
1929 * If an invalid rotation is detected and DEBUG_LEVEL_1 is enabled, an error
1930 * will be reported.
1931 *
1932 * @returns rotation in range 0-3 (inclusive)
1933 */
get_current_rotation()1934 uint8_t get_current_rotation()
1935 {
1936 uint8_t rotation = gCurrentRotation;
1937 uint8_t rotation_masked = rotation & 3;
1938 #if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1
1939 if (rotation != rotation_masked)
1940 {
1941 log_error(
1942 "Found wrong rotation %d! Will return %d instead.", static_cast<uint32_t>(rotation),
1943 static_cast<uint32_t>(rotation_masked));
1944 }
1945 #endif // DEBUG_LEVEL_1
1946 return rotation_masked;
1947 }
1948
get_height_marker_offset()1949 int32_t get_height_marker_offset()
1950 {
1951 // Height labels in units
1952 if (gConfigGeneral.show_height_as_units)
1953 return 0;
1954
1955 // Height labels in feet
1956 if (gConfigGeneral.measurement_format == MeasurementFormat::Imperial)
1957 return 1 * 256;
1958
1959 // Height labels in metres
1960 return 2 * 256;
1961 }
1962
viewport_set_saved_view()1963 void viewport_set_saved_view()
1964 {
1965 rct_window* w = window_get_main();
1966 if (w != nullptr)
1967 {
1968 rct_viewport* viewport = w->viewport;
1969
1970 gSavedView = ScreenCoordsXY{ viewport->view_width / 2, viewport->view_height / 2 } + viewport->viewPos;
1971
1972 gSavedViewZoom = viewport->zoom;
1973 gSavedViewRotation = get_current_rotation();
1974 }
1975 }
1976
min()1977 ZoomLevel ZoomLevel::min()
1978 {
1979 if (drawing_engine_get_type() == DrawingEngine::OpenGL)
1980 {
1981 return -2;
1982 }
1983
1984 return 0;
1985 }
1986