yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
canvas_coordinate_sync_test.cc
Go to the documentation of this file.
1#include "app/gui/canvas.h"
2
3#include <gmock/gmock.h>
4#include <gtest/gtest.h>
5
6#include "testing.h"
7
8namespace yaze {
9namespace test {
10
11using ::testing::Eq;
12using ::testing::FloatEq;
13using ::testing::Ne;
14
26class CanvasCoordinateSyncTest : public ::testing::Test {
27 protected:
28 void SetUp() override {
29 // Create a test canvas with known dimensions (4096x4096 for overworld)
30 canvas_ = std::make_unique<gui::Canvas>("OverworldCanvas", ImVec2(4096, 4096),
32 canvas_->set_global_scale(1.0f);
33 }
34
35 std::unique_ptr<gui::Canvas> canvas_;
36};
37
38// ============================================================================
39// Hover Position Tests (hover_mouse_pos)
40// ============================================================================
41
42TEST_F(CanvasCoordinateSyncTest, HoverMousePos_InitialState) {
43 // Hover position should start at (0,0) or invalid state
44 auto hover_pos = canvas_->hover_mouse_pos();
45
46 // Initial state may be (0,0) - this is valid
47 EXPECT_GE(hover_pos.x, 0.0f);
48 EXPECT_GE(hover_pos.y, 0.0f);
49}
50
51TEST_F(CanvasCoordinateSyncTest, HoverMousePos_IndependentFromDrawnPos) {
52 // Hover position and drawn tile position are independent
53 // hover_mouse_pos() tracks continuous mouse movement
54 // drawn_tile_position() only updates during painting
55
56 auto hover_pos = canvas_->hover_mouse_pos();
57 auto drawn_pos = canvas_->drawn_tile_position();
58
59 // These may differ - hover tracks all movement, drawn only tracks paint
60 // We just verify both are valid (non-negative or expected sentinel values)
61 EXPECT_TRUE(hover_pos.x >= 0.0f || hover_pos.x == -1.0f);
62 EXPECT_TRUE(drawn_pos.x >= 0.0f || drawn_pos.x == -1.0f);
63}
64
65// ============================================================================
66// Coordinate Space Tests
67// ============================================================================
68
69TEST_F(CanvasCoordinateSyncTest, CoordinateSpace_WorldNotScreen) {
70 // REGRESSION TEST: Verify hover_mouse_pos() returns world coordinates
71 // not screen coordinates. The bug was using ImGui::GetIO().MousePos
72 // which is in screen space and doesn't account for scrolling/canvas offset.
73
74 // Simulate scrolling the canvas
75 canvas_->set_scrolling(ImVec2(100, 100));
76
77 // The hover position should be in canvas/world space, not affected by
78 // the canvas's screen position. This is tested by ensuring the method
79 // exists and returns a coordinate that could be used for map calculations.
80 auto hover_pos = canvas_->hover_mouse_pos();
81
82 // Valid world coordinates should be usable for map index calculations
83 // For a 512x512 map size (kOverworldMapSize = 512):
84 // map_x = hover_pos.x / 512
85 // map_y = hover_pos.y / 512
86
87 int map_x = static_cast<int>(hover_pos.x) / 512;
88 int map_y = static_cast<int>(hover_pos.y) / 512;
89
90 // Map indices should be within valid range for 8x8 overworld grid
91 EXPECT_GE(map_x, 0);
92 EXPECT_GE(map_y, 0);
93 EXPECT_LT(map_x, 64); // 8x8 grid = 64 maps max
94 EXPECT_LT(map_y, 64);
95}
96
97TEST_F(CanvasCoordinateSyncTest, MapCalculation_SmallMaps) {
98 // Test map index calculation for standard 512x512 maps
99 const int kOverworldMapSize = 512;
100
101 // Simulate hover at different world positions
102 std::vector<ImVec2> test_positions = {
103 ImVec2(0, 0), // Map (0, 0)
104 ImVec2(512, 0), // Map (1, 0)
105 ImVec2(0, 512), // Map (0, 1)
106 ImVec2(512, 512), // Map (1, 1)
107 ImVec2(1536, 1024), // Map (3, 2)
108 };
109
110 std::vector<std::pair<int, int>> expected_maps = {
111 {0, 0}, {1, 0}, {0, 1}, {1, 1}, {3, 2}
112 };
113
114 for (size_t i = 0; i < test_positions.size(); ++i) {
115 ImVec2 pos = test_positions[i];
116 int map_x = pos.x / kOverworldMapSize;
117 int map_y = pos.y / kOverworldMapSize;
118
119 EXPECT_EQ(map_x, expected_maps[i].first);
120 EXPECT_EQ(map_y, expected_maps[i].second);
121 }
122}
123
124TEST_F(CanvasCoordinateSyncTest, MapCalculation_LargeMaps) {
125 // Test map index calculation for ZSCustomOverworld v3 large maps (1024x1024)
126 const int kLargeMapSize = 1024;
127
128 // Large maps should span multiple standard map coordinates
129 std::vector<ImVec2> test_positions = {
130 ImVec2(0, 0), // Large map (0, 0)
131 ImVec2(1024, 0), // Large map (1, 0)
132 ImVec2(0, 1024), // Large map (0, 1)
133 ImVec2(2048, 2048), // Large map (2, 2)
134 };
135
136 std::vector<std::pair<int, int>> expected_large_maps = {
137 {0, 0}, {1, 0}, {0, 1}, {2, 2}
138 };
139
140 for (size_t i = 0; i < test_positions.size(); ++i) {
141 ImVec2 pos = test_positions[i];
142 int map_x = pos.x / kLargeMapSize;
143 int map_y = pos.y / kLargeMapSize;
144
145 EXPECT_EQ(map_x, expected_large_maps[i].first);
146 EXPECT_EQ(map_y, expected_large_maps[i].second);
147 }
148}
149
150// ============================================================================
151// Scale Invariance Tests
152// ============================================================================
153
154TEST_F(CanvasCoordinateSyncTest, HoverPosition_ScaleInvariant) {
155 // REGRESSION TEST: Hover position should be in world space regardless of scale
156 // The bug was scale-dependent because it used screen coordinates
157
158 auto test_hover_at_scale = [&](float scale) {
159 canvas_->set_global_scale(scale);
160 auto hover_pos = canvas_->hover_mouse_pos();
161
162 // Hover position should be in world coordinates, not affected by scale
163 // World coordinates are always in the range [0, canvas_size)
164 EXPECT_GE(hover_pos.x, 0.0f);
165 EXPECT_GE(hover_pos.y, 0.0f);
166 EXPECT_LE(hover_pos.x, 4096.0f);
167 EXPECT_LE(hover_pos.y, 4096.0f);
168 };
169
170 test_hover_at_scale(0.25f);
171 test_hover_at_scale(0.5f);
172 test_hover_at_scale(1.0f);
173 test_hover_at_scale(2.0f);
174 test_hover_at_scale(4.0f);
175}
176
177// ============================================================================
178// Overworld Editor Integration Tests
179// ============================================================================
180
181TEST_F(CanvasCoordinateSyncTest, OverworldMapHighlight_UsesHoverNotDrawn) {
182 // CRITICAL REGRESSION TEST
183 // This verifies the fix for overworld_editor.cc:1041
184 // CheckForCurrentMap() must use hover_mouse_pos() not ImGui::GetIO().MousePos
185
186 // The pattern used in DrawOverworldEdits (line 664) for painting:
187 auto drawn_pos = canvas_->drawn_tile_position();
188
189 // The pattern that SHOULD be used in CheckForCurrentMap (line 1041) for highlighting:
190 auto hover_pos = canvas_->hover_mouse_pos();
191
192 // These are different methods for different purposes:
193 // - drawn_tile_position(): Only updates during active painting (mouse drag)
194 // - hover_mouse_pos(): Updates continuously during hover
195
196 // Verify both methods exist and return valid (or sentinel) values
197 EXPECT_TRUE(drawn_pos.x >= 0.0f || drawn_pos.x == -1.0f);
198 EXPECT_TRUE(hover_pos.x >= 0.0f || hover_pos.x == -1.0f);
199}
200
201TEST_F(CanvasCoordinateSyncTest, OverworldMapIndex_From8x8Grid) {
202 // Simulate the exact calculation from OverworldEditor::CheckForCurrentMap
203 const int kOverworldMapSize = 512;
204
205 // Test all three worlds (Light, Dark, Special)
206 struct TestCase {
207 ImVec2 hover_pos;
208 int current_world; // 0=Light, 1=Dark, 2=Special
209 int expected_map_index;
210 };
211
212 std::vector<TestCase> test_cases = {
213 // Light World (0x00 - 0x3F)
214 {ImVec2(0, 0), 0, 0}, // Map 0 (Light World)
215 {ImVec2(512, 0), 0, 1}, // Map 1
216 {ImVec2(1024, 512), 0, 10}, // Map 10 = 2 + 1*8
217
218 // Dark World (0x40 - 0x7F)
219 {ImVec2(0, 0), 1, 0x40}, // Map 0x40 (Dark World)
220 {ImVec2(512, 0), 1, 0x41}, // Map 0x41
221 {ImVec2(1024, 512), 1, 0x4A}, // Map 0x4A = 0x40 + 10
222
223 // Special World (0x80+)
224 {ImVec2(0, 0), 2, 0x80}, // Map 0x80 (Special World)
225 {ImVec2(512, 512), 2, 0x89}, // Map 0x89 = 0x80 + 9
226 };
227
228 for (const auto& tc : test_cases) {
229 int map_x = tc.hover_pos.x / kOverworldMapSize;
230 int map_y = tc.hover_pos.y / kOverworldMapSize;
231 int hovered_map = map_x + map_y * 8;
232
233 if (tc.current_world == 1) {
234 hovered_map += 0x40;
235 } else if (tc.current_world == 2) {
236 hovered_map += 0x80;
237 }
238
239 EXPECT_EQ(hovered_map, tc.expected_map_index)
240 << "Failed for world " << tc.current_world
241 << " at position (" << tc.hover_pos.x << ", " << tc.hover_pos.y << ")";
242 }
243}
244
245// ============================================================================
246// Boundary Condition Tests
247// ============================================================================
248
249TEST_F(CanvasCoordinateSyncTest, MapBoundaries_512x512) {
250 // Test coordinates exactly at map boundaries
251 const int kOverworldMapSize = 512;
252
253 // Boundary coordinates (edges of maps)
254 std::vector<ImVec2> boundary_positions = {
255 ImVec2(511, 0), // Right edge of map 0
256 ImVec2(512, 0), // Left edge of map 1
257 ImVec2(0, 511), // Bottom edge of map 0
258 ImVec2(0, 512), // Top edge of map 8
259 ImVec2(511, 511), // Corner of map 0
260 ImVec2(512, 512), // Corner of map 9
261 };
262
263 for (const auto& pos : boundary_positions) {
264 int map_x = pos.x / kOverworldMapSize;
265 int map_y = pos.y / kOverworldMapSize;
266 int map_index = map_x + map_y * 8;
267
268 // Verify map indices are within valid range
269 EXPECT_GE(map_index, 0);
270 EXPECT_LT(map_index, 64); // 8x8 grid = 64 maps
271 }
272}
273
274TEST_F(CanvasCoordinateSyncTest, MapBoundaries_1024x1024) {
275 // Test large map boundaries (ZSCustomOverworld v3)
276 const int kLargeMapSize = 1024;
277
278 std::vector<ImVec2> boundary_positions = {
279 ImVec2(1023, 0), // Right edge of large map 0
280 ImVec2(1024, 0), // Left edge of large map 1
281 ImVec2(0, 1023), // Bottom edge of large map 0
282 ImVec2(0, 1024), // Top edge of large map 4 (0,1 in 4x4 grid)
283 };
284
285 for (const auto& pos : boundary_positions) {
286 int map_x = pos.x / kLargeMapSize;
287 int map_y = pos.y / kLargeMapSize;
288 int map_index = map_x + map_y * 4; // 4x4 grid for large maps
289
290 // Verify map indices are within valid range for large maps
291 EXPECT_GE(map_index, 0);
292 EXPECT_LT(map_index, 16); // 4x4 grid = 16 large maps
293 }
294}
295
296} // namespace test
297} // namespace yaze
Tests for canvas coordinate synchronization.
TEST_F(DungeonObjectRenderingE2ETests, RunAllTests)
constexpr int kOverworldMapSize
Definition overworld.h:52
Main namespace for the application.