yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
object_dimensions.cc
Go to the documentation of this file.
1#include "object_dimensions.h"
2
3#include <limits>
4
5#include "core/features.h"
9
10namespace yaze {
11namespace zelda3 {
12
13namespace {
14bool GetSomariaLineDimensions(int object_id, int* width, int* height) {
15 if (!width || !height) {
16 return false;
17 }
18 if (!((object_id >= 0xF83 && object_id <= 0xF8C) || object_id == 0xF8E ||
19 object_id == 0xF8F)) {
20 return false;
21 }
22
23 *width = 1;
24 *height = 1;
25 return true;
26}
27
28std::pair<int, int> GetClosedChestPlatformDimensions(int size) {
29 const int size_x = (size >> 2) & 0x03;
30 const int size_y = size & 0x03;
31 return {size_x * 2 + 14, size_y * 2 + 8};
32}
33
34std::pair<int, int> GetOpenChestPlatformDimensions(int size) {
35 const int size_x = (size >> 2) & 0x03;
36 const int size_y = size & 0x03;
37 return {size_x * 2 + 10, size_y * 2 + 7};
38}
39
40std::pair<int, int> GetMovingWallDimensions(int size) {
41 const int direction = moving_wall::DirectionForSize(size);
42 return {moving_wall::ObjectCountForSize(size) + 3, direction * 2 + 6};
43}
44} // namespace
45
47 static ObjectDimensionTable instance;
48 return instance;
49}
50
52 if (!rom || !rom->is_loaded()) {
53 return absl::FailedPreconditionError("ROM not loaded");
54 }
55
56 dimensions_.clear();
58
59 // Parse ROM tables for refinement
63
64 loaded_ = true;
65 return absl::OkStatus();
66}
67
69 int object_id) const {
70 auto it = dimensions_.find(object_id);
71 if (it != dimensions_.end()) {
72 return {it->second.base_width, it->second.base_height};
73 }
74 return {2, 2}; // Default 16x16 pixels (2x2 tiles)
75}
76
78 int size) const {
79 if (size != 0) {
80 return size;
81 }
82 if (entry.zero_size_override > 0) {
83 return entry.zero_size_override;
84 }
85 if (entry.use_32_when_zero) {
86 return 32;
87 }
88 return 0;
89}
90
91std::pair<int, int> ObjectDimensionTable::GetDimensions(int object_id,
92 int size) const {
93 int somaria_width = 0;
94 int somaria_height = 0;
95 if (GetSomariaLineDimensions(object_id, &somaria_width, &somaria_height)) {
96 return {somaria_width, somaria_height};
97 }
98 if (object_id == 0xC1) {
99 return GetClosedChestPlatformDimensions(size);
100 }
101 if (object_id == 0xDC) {
102 return GetOpenChestPlatformDimensions(size);
103 }
104 if (object_id == 0xCD || object_id == 0xCE) {
105 return GetMovingWallDimensions(size);
106 }
107 if (object_id == 0xD8 || object_id == 0xDA) {
108 int size_x = ((size >> 2) & 0x03);
109 int size_y = (size & 0x03);
110 int width = (size_x + 2) * 4;
111 int height = (size_y + 2) * 4;
112 return {width, height};
113 }
114 if (object_id == 0xDD) {
115 int size_x = ((size >> 2) & 0x03);
116 int size_y = (size & 0x03);
117 int width = 4 + (size_x * 2);
118 int height = 4 + (size_y * 2);
119 return {width, height};
120 }
121 auto it = dimensions_.find(object_id);
122 if (it == dimensions_.end()) {
123 // Unknown object - estimate from size
124 // ASM: When size is 0, default to 32 (not 1)
125 int s = (size == 0) ? 32 : size + 1;
126 return {2 * s, 2};
127 }
128
129 const auto& entry = it->second;
130 int w = entry.base_width;
131 int h = entry.base_height;
132
133 int effective_size = ResolveEffectiveSize(entry, size);
134
135 switch (entry.extend_dir) {
137 w += effective_size * entry.extend_multiplier;
138 break;
140 h += effective_size * entry.extend_multiplier;
141 break;
143 w += effective_size * entry.extend_multiplier;
144 h += effective_size * entry.extend_multiplier;
145 break;
147 // Diagonals extend both dimensions based on count
148 // Width = base_width + size, Height = base_height + size
149 // ASM: Each iteration draws 5 tiles vertically and advances X by 1
150 // Bounding box height = 5 + (count - 1) = count + 4
151 w += effective_size * entry.extend_multiplier;
152 h += effective_size * entry.extend_multiplier;
153 break;
155 // SuperSquare objects use subtype1 size's 2-bit X/Y fields
156 // size_x = ((size >> 2) & 0x03) + 1, size_y = (size & 0x03) + 1
157 int size_x = ((size >> 2) & 0x03) + 1;
158 int size_y = (size & 0x03) + 1;
159 w = size_x * entry.extend_multiplier;
160 h = size_y * entry.extend_multiplier;
161 break;
162 }
164 default:
165 break;
166 }
167
168 return {w, h};
169}
170
172 int object_id, int size) const {
173 int somaria_width = 0;
174 int somaria_height = 0;
175 if (GetSomariaLineDimensions(object_id, &somaria_width, &somaria_height)) {
176 return {somaria_width, somaria_height};
177 }
178 if (object_id == 0xC1) {
179 return GetClosedChestPlatformDimensions(size);
180 }
181 if (object_id == 0xDC) {
182 return GetOpenChestPlatformDimensions(size);
183 }
184 if (object_id == 0xCD || object_id == 0xCE) {
185 return GetMovingWallDimensions(size);
186 }
187 if (object_id == 0xD8 || object_id == 0xDA) {
188 int size_x = ((size >> 2) & 0x03);
189 int size_y = (size & 0x03);
190 int width = (size_x + 2) * 4;
191 int height = (size_y + 2) * 4;
192 return {width, height};
193 }
194 if (object_id == 0xDD) {
195 int size_x = ((size >> 2) & 0x03);
196 int size_y = (size & 0x03);
197 int width = 4 + (size_x * 2);
198 int height = 4 + (size_y * 2);
199 return {width, height};
200 }
201 auto it = dimensions_.find(object_id);
202 if (it == dimensions_.end()) {
203 // Unknown object - use reasonable default based on size
204 int s = std::max(1, size + 1);
205 return {std::min(s * 2, 16), 2}; // Cap at 16 tiles wide for selection
206 }
207
208 const auto& entry = it->second;
209 int w = entry.base_width;
210 int h = entry.base_height;
211
212 // Selection bounds should match draw-size semantics.
213 int effective_size = ResolveEffectiveSize(entry, size);
214
215 switch (entry.extend_dir) {
217 w += effective_size * entry.extend_multiplier;
218 break;
220 h += effective_size * entry.extend_multiplier;
221 break;
223 w += effective_size * entry.extend_multiplier;
224 h += effective_size * entry.extend_multiplier;
225 break;
227 // Diagonals: both dimensions scale with size
228 w += effective_size * entry.extend_multiplier;
229 h += effective_size * entry.extend_multiplier;
230 break;
232 // SuperSquare: subtype1 size uses 2-bit X/Y fields
233 int size_x = ((size >> 2) & 0x03) + 1;
234 int size_y = (size & 0x03) + 1;
235 w = size_x * entry.extend_multiplier;
236 h = size_y * entry.extend_multiplier;
237 break;
238 }
240 default:
241 break;
242 }
243
244 // Ensure minimum visible size (1 tile)
245 w = std::max(w, 1);
246 h = std::max(h, 1);
247
248 return {w, h};
249}
250
252 int object_id, int size) const {
253 if (core::FeatureFlags::get().kEnableCustomObjects) {
254 int subtype = size & 0x1F;
255 auto custom_or =
256 CustomObjectManager::Get().GetObjectInternal(object_id, subtype);
257 if (custom_or.ok()) {
258 auto custom = custom_or.value();
259 if (custom && !custom->IsEmpty()) {
260 int min_x = std::numeric_limits<int>::max();
261 int min_y = std::numeric_limits<int>::max();
262 int max_x = std::numeric_limits<int>::min();
263 int max_y = std::numeric_limits<int>::min();
264
265 for (const auto& entry : custom->tiles) {
266 min_x = std::min(min_x, entry.rel_x);
267 min_y = std::min(min_y, entry.rel_y);
268 max_x = std::max(max_x, entry.rel_x);
269 max_y = std::max(max_y, entry.rel_y);
270 }
271
272 if (min_x != std::numeric_limits<int>::max()) {
273 SelectionBounds bounds;
274 bounds.offset_x = min_x;
275 bounds.offset_y = min_y;
276 bounds.width = (max_x - min_x) + 1;
277 bounds.height = (max_y - min_y) + 1;
278 return bounds;
279 }
280 }
281 }
282 }
283
284 auto [w, h] = GetSelectionDimensions(object_id, size);
285 SelectionBounds bounds{0, 0, w, h};
286
287 switch (object_id) {
288 // Offset +3 (1x1 solid +3)
289 case 0x34:
290 bounds.offset_x = 3;
291 break;
292
293 // Rightwards corners +13
294 case 0x2F:
295 bounds.offset_x = 13;
296 break;
297 case 0x30:
298 bounds.offset_x = 13;
299 bounds.offset_y = 1;
300 break;
301
302 // Downwards corners +12
303 case 0x6C:
304 case 0x6D:
305 bounds.offset_x = 12;
306 break;
307
308 // Downwards solid +3 writes from y+3 to y+(size+6).
309 case 0x71:
310 bounds.offset_y = 3;
311 break;
312
313 // Moving wall west grows its fill left from the three-column platform.
314 case 0xCD:
315 bounds.offset_x = -moving_wall::ObjectCountForSize(size);
316 break;
317
318 default:
319 break;
320 }
321
322 // Acute diagonals extend upward relative to origin.
323 if (object_id == 0x09 || object_id == 0x0C || object_id == 0x0D ||
324 object_id == 0x10 || object_id == 0x11 || object_id == 0x14) {
325 bounds.offset_y = -(bounds.width - 1);
326 }
327 if (object_id == 0x15 || object_id == 0x18 || object_id == 0x19 ||
328 object_id == 0x1C || object_id == 0x1D || object_id == 0x20) {
329 bounds.offset_y = -(bounds.width - 1);
330 }
331
332 // Diagonal ceilings: offsets depend on which corner is the origin.
333 // TopLeft (0xA0, 0xA5, 0xA9): extends down-right - no offset needed.
334 // BottomLeft (0xA1, 0xA6, 0xAA): extends up-right - offset_y negative.
335 if (object_id == 0xA1 || object_id == 0xA6 || object_id == 0xAA) {
336 bounds.offset_y = -(bounds.width - 1);
337 }
338 // TopRight (0xA2, 0xA7, 0xAB): extends down-left - offset_x negative.
339 if (object_id == 0xA2 || object_id == 0xA7 || object_id == 0xAB) {
340 bounds.offset_x = -(bounds.width - 1);
341 }
342 // BottomRight (0xA3, 0xA8, 0xAC): extends up-left - both offsets negative.
343 if (object_id == 0xA3 || object_id == 0xA8 || object_id == 0xAC) {
344 bounds.offset_x = -(bounds.width - 1);
345 bounds.offset_y = -(bounds.width - 1);
346 }
347
348 return bounds;
349}
350
351std::tuple<int, int, int, int> ObjectDimensionTable::GetHitTestBounds(
352 const RoomObject& obj) const {
353 auto bounds = GetSelectionBounds(obj.id_, obj.size_);
354 return {obj.x_ + bounds.offset_x, obj.y_ + bounds.offset_y, bounds.width,
355 bounds.height};
356}
357
359 using Dir = DimensionEntry::ExtendDir;
360
361 // ============================================================================
362 // Subtype 1 objects (0x00-0xF7)
363 // ============================================================================
364
365 // 0x00: Ceiling 2x2 - uses GetSize_1to15or32
366 dimensions_[0x00] = {0, 2, Dir::Horizontal, 2, true};
367
368 // 0x01-0x02: Wall 2x4 - uses GetSize_1to15or26
369 for (int id = 0x01; id <= 0x02; id++) {
370 dimensions_[id] = {0, 4, Dir::Horizontal, 2, false}; // Use 26 when zero
371 }
372 for (int id = 0x01; id <= 0x02; id++) {
373 dimensions_[id].zero_size_override = 26;
374 }
375
376 // 0x03-0x04: Wall 2x4 spaced 4 - GetSize_1to16
377 for (int id = 0x03; id <= 0x04; id++) {
378 dimensions_[id] = {2, 4, Dir::Horizontal, 2, false};
379 }
380
381 // 0x05-0x06: Wall 2x4 spaced 4 BothBG - step is 6 tiles between starts
382 for (int id = 0x05; id <= 0x06; id++) {
383 dimensions_[id] = {2, 4, Dir::Horizontal, 6, false};
384 }
385
386 // 0x07-0x08: Floor 2x2 - GetSize_1to16
387 for (int id = 0x07; id <= 0x08; id++) {
388 dimensions_[id] = {2, 2, Dir::Horizontal, 2, false};
389 }
390
391 // 0x09-0x14: Diagonal walls - non-BothBG (count = size + 7)
392 // Height = count + 4 tiles (5 tiles per column + diagonal extent)
393 for (int id = 0x09; id <= 0x14; id++) {
394 // Diagonal pattern: width = count tiles, height = count + 4 tiles
395 dimensions_[id] = {7, 11, Dir::Diagonal, 1,
396 false}; // base 7 + size*1, height 11 + size*1
397 }
398
399 // 0x15-0x20: Diagonal walls - BothBG (count = size + 6)
400 for (int id = 0x15; id <= 0x20; id++) {
401 dimensions_[id] = {6, 10, Dir::Diagonal, 1,
402 false}; // base 6 + size*1, height 10 + size*1
403 }
404
405 // 0x21: Edge 1x3 +2 (width = size*2 + 4)
406 dimensions_[0x21] = {4, 3, Dir::Horizontal, 2, false};
407
408 // 0x22: Edge 1x1 +3 (width = size + 4)
409 dimensions_[0x22] = {4, 1, Dir::Horizontal, 1, false};
410
411 // 0x23-0x2E: Edge 1x1 +2 (width = size + 3)
412 for (int id = 0x23; id <= 0x2E; id++) {
413 dimensions_[id] = {3, 1, Dir::Horizontal, 1, false};
414 }
415
416 // 0x2F: Top corners 1x2 +13
417 dimensions_[0x2F] = {10, 2, Dir::Horizontal, 1, false};
418
419 // 0x30: Bottom corners 1x2 +13
420 dimensions_[0x30] = {10, 2, Dir::Horizontal, 1, false};
421
422 // 0x31-0x32: Nothing
423 dimensions_[0x31] = {1, 1, Dir::None, 0, false};
424 dimensions_[0x32] = {1, 1, Dir::None, 0, false};
425
426 // 0x33: 4x4 block - GetSize_1to16
427 dimensions_[0x33] = {4, 4, Dir::Horizontal, 4, false};
428
429 // 0x34: Solid 1x1 +3 (count = size + 4)
430 dimensions_[0x34] = {4, 1, Dir::Horizontal, 1, false};
431
432 // 0x35: Door switcher - uses a single tile in ROM data
433 dimensions_[0x35] = {1, 1, Dir::None, 0, false};
434
435 // 0x36-0x37: Decor 4x4 spaced 2 - spacing 6 tiles
436 for (int id = 0x36; id <= 0x37; id++) {
437 dimensions_[id] = {4, 4, Dir::Horizontal, 6, false};
438 }
439
440 // 0x38: Statue 2x3 spaced 2 - spacing 4 tiles
441 dimensions_[0x38] = {2, 3, Dir::Horizontal, 4, false};
442
443 // 0x39: Pillar 2x4 spaced 4 - step is 6 tiles between starts
444 dimensions_[0x39] = {2, 4, Dir::Horizontal, 6, false};
445
446 // 0x3A-0x3B: Decor 4x3 spaced 4 - step is 8 tiles between starts
447 for (int id = 0x3A; id <= 0x3B; id++) {
448 dimensions_[id] = {4, 3, Dir::Horizontal, 8, false};
449 }
450
451 // 0x3C: Doubled 2x2 (rendered as 4x2) with 6-tile horizontal step
452 dimensions_[0x3C] = {4, 2, Dir::Horizontal, 6, false};
453
454 // 0x3D: Pillar 2x4 spaced 4 - step is 6 tiles between starts
455 dimensions_[0x3D] = {2, 4, Dir::Horizontal, 6, false};
456
457 // 0x3E: Decor 2x2 spaced 12 - spacing 14 tiles
458 dimensions_[0x3E] = {2, 2, Dir::Horizontal, 14, false};
459
460 // 0x3F-0x46: Edge 1x1 +2 (width = size + 3)
461 for (int id = 0x3F; id <= 0x46; id++) {
462 dimensions_[id] = {3, 1, Dir::Horizontal, 1, false};
463 }
464
465 // 0x47: Waterfall47 - 1x5 columns, width = 2 + (size+1)*2
466 dimensions_[0x47] = {4, 5, Dir::Horizontal, 2, false};
467
468 // 0x48: Waterfall48 - 1x3 columns, width = 2 + (size+1)*2
469 dimensions_[0x48] = {4, 3, Dir::Horizontal, 2, false};
470
471 // 0x49-0x4A: Floor Tile 4x2 - GetSize_1to16
472 for (int id = 0x49; id <= 0x4A; id++) {
473 dimensions_[id] = {4, 2, Dir::Horizontal, 4, false};
474 }
475
476 // 0x4B: Decor 2x2 spaced 12 - spacing 14 tiles
477 dimensions_[0x4B] = {2, 2, Dir::Horizontal, 14, false};
478
479 // 0x4C: Bar 4x3 - count=(size+1), step=4
480 dimensions_[0x4C] = {4, 3, Dir::Horizontal, 4, false};
481
482 // 0x4D-0x4F: Shelf 4x4 - count=(size+1), step=4
483 for (int id = 0x4D; id <= 0x4F; id++) {
484 dimensions_[id] = {4, 4, Dir::Horizontal, 4, false};
485 }
486
487 // 0x50: Line 1x1 +1 (count = size + 2)
488 dimensions_[0x50] = {2, 1, Dir::Horizontal, 1, false};
489
490 // 0x51-0x52: Cannon Hole 4x3 (left segment repeats by 2 tiles, right cap once)
491 for (int id = 0x51; id <= 0x52; id++) {
492 dimensions_[id] = {4, 3, Dir::Horizontal, 2, false};
493 }
494
495 // 0x53: Floor 2x2 - GetSize_1to16
496 dimensions_[0x53] = {2, 2, Dir::Horizontal, 2, false};
497
498 // 0x54-0x5A: Mostly unused, but 0x55-0x56 are wall torches.
499 for (int id = 0x54; id <= 0x5A; id++) {
500 dimensions_[id] = {1, 1, Dir::None, 0, false};
501 }
502 // 0x55-0x56: Decor 4x2 spaced 12
503 for (int id = 0x55; id <= 0x56; id++) {
504 dimensions_[id] = {4, 2, Dir::Horizontal, 12, false};
505 }
506
507 // 0x5B-0x5C: Cannon Hole 4x3 (same as 0x51-0x52)
508 for (int id = 0x5B; id <= 0x5C; id++) {
509 dimensions_[id] = {4, 3, Dir::Horizontal, 2, false};
510 }
511
512 // 0x5D: Big Rail 1x3 +5 (count = size + 6)
513 dimensions_[0x5D] = {6, 3, Dir::Horizontal, 1, false};
514
515 // 0x5E: Block 2x2 spaced 2
516 dimensions_[0x5E] = {2, 2, Dir::Horizontal, 4, false};
517
518 // 0x5F: Edge 1x1 +23 (width = size + 23)
519 dimensions_[0x5F] = {23, 1, Dir::Horizontal, 1, false};
520
521 // 0x60: Downwards 2x2 - GetSize_1to15or32
522 dimensions_[0x60] = {2, 0, Dir::Vertical, 2, true};
523
524 // 0x61-0x62: Downwards 4x2 - GetSize_1to15or26
525 for (int id = 0x61; id <= 0x62; id++) {
526 dimensions_[id] = {4, 0, Dir::Vertical, 2, false};
527 }
528 for (int id = 0x61; id <= 0x62; id++) {
529 dimensions_[id].zero_size_override = 26;
530 }
531
532 // 0x63-0x64: Downwards 4x2 - GetSize_1to16 (BothBG)
533 for (int id = 0x63; id <= 0x64; id++) {
534 dimensions_[id] = {4, 2, Dir::Vertical, 2, false};
535 }
536 // 0x65-0x66: Downwards Decor 4x2 spaced 4 (6-tile spacing)
537 for (int id = 0x65; id <= 0x66; id++) {
538 dimensions_[id] = {4, 2, Dir::Vertical, 6, false};
539 }
540 // 0x67-0x68: Downwards 2x2 - GetSize_1to16
541 for (int id = 0x67; id <= 0x68; id++) {
542 dimensions_[id] = {2, 2, Dir::Vertical, 2, false};
543 }
544
545 // 0x69: Downwards edge +3 (height = size + 4, matching horizontal 0x22).
546 // ASM RoomDraw_DownwardsHasEdge1x1_1to16_plus3 ($01:8EC3) uses A=2 in
547 // GetSize_1to16_timesA so middle count = size + 2; total span = corner +
548 // (size+2) middles + end = size + 4 tiles.
549 dimensions_[0x69] = {1, 4, Dir::Vertical, 1, false};
550
551 // 0x6A-0x6B: Downwards edge
552 for (int id = 0x6A; id <= 0x6B; id++) {
553 dimensions_[id] = {1, 1, Dir::Vertical, 1, false};
554 }
555
556 // 0x6C-0x6D: Downwards corners (+12 offset in draw routine).
557 // Canonical empty-canvas render:
558 // - 2 opening rows
559 // - (size + 10) body rows
560 // - 2 closing rows
561 // => total height = size + 14
562 for (int id = 0x6C; id <= 0x6D; id++) {
563 dimensions_[id] = {2, 14, Dir::Vertical, 1, false};
564 }
565
566 // 0x6E-0x6F: Nothing
567 dimensions_[0x6E] = {1, 1, Dir::None, 0, false};
568 dimensions_[0x6F] = {1, 1, Dir::None, 0, false};
569
570 // 0x70: Downwards Floor 4x4
571 dimensions_[0x70] = {4, 4, Dir::Vertical, 4, false};
572
573 // 0x71: Downwards Solid 1x1 +3
574 dimensions_[0x71] = {1, 4, Dir::Vertical, 1, false};
575
576 // 0x72: Nothing
577 dimensions_[0x72] = {1, 1, Dir::None, 0, false};
578
579 // 0x73-0x74: Downwards Decor 4x4 spaced 2
580 for (int id = 0x73; id <= 0x74; id++) {
581 dimensions_[id] = {4, 4, Dir::Vertical, 6, false};
582 }
583
584 // 0x75: Downwards Pillar 2x4 spaced 2
585 dimensions_[0x75] = {2, 4, Dir::Vertical, 6, false};
586
587 // 0x76-0x77: Downwards Decor 3x4 spaced 4 (8-tile spacing)
588 for (int id = 0x76; id <= 0x77; id++) {
589 dimensions_[id] = {3, 4, Dir::Vertical, 8, false};
590 }
591
592 // 0x78, 0x7B: Downwards Decor 2x2 spaced 12
593 dimensions_[0x78] = {2, 2, Dir::Vertical, 14, false};
594 dimensions_[0x7B] = {2, 2, Dir::Vertical, 14, false};
595
596 // 0x79-0x7A: Downwards Edge 1x1
597 for (int id = 0x79; id <= 0x7A; id++) {
598 dimensions_[id] = {1, 1, Dir::Vertical, 1, false};
599 }
600
601 // 0x7C: Downwards Line 1x1 +1
602 dimensions_[0x7C] = {1, 2, Dir::Vertical, 1, false};
603
604 // 0x7D: Downwards 2x2
605 dimensions_[0x7D] = {2, 2, Dir::Vertical, 2, false};
606
607 // 0x7E: Nothing
608 dimensions_[0x7E] = {1, 1, Dir::None, 0, false};
609
610 // 0x7F-0x80: Downwards Decor 2x4 spaced 8 (12-tile step)
611 for (int id = 0x7F; id <= 0x80; id++) {
612 dimensions_[id] = {2, 4, Dir::Vertical, 12, false};
613 }
614
615 // 0x81-0x84: Downwards Decor 3x4 spaced 2 (6-tile spacing)
616 for (int id = 0x81; id <= 0x84; id++) {
617 dimensions_[id] = {3, 4, Dir::Vertical, 6, false};
618 }
619
620 // 0x85-0x86: Downwards Cannon Hole 3x4 (height = 2*(size+2))
621 for (int id = 0x85; id <= 0x86; id++) {
622 dimensions_[id] = {3, 4, Dir::Vertical, 2, false};
623 }
624
625 // 0x87: Downwards Pillar 2x4 spaced 2
626 dimensions_[0x87] = {2, 4, Dir::Vertical, 6, false};
627
628 // 0x88: Downwards Big Rail 3x1 +5
629 dimensions_[0x88] = {2, 6, Dir::Vertical, 1, false};
630
631 // 0x89: Downwards Block 2x2 spaced 2
632 dimensions_[0x89] = {2, 2, Dir::Vertical, 4, false};
633
634 // 0x8A: long rail with corner/middle/end (+23 total base span).
635 dimensions_[0x8A] = {1, 23, Dir::Vertical, 1, false};
636
637 // 0x8B-0x8C: single-tile jump ledges (+7 => size + 8 rows).
638 for (int id = 0x8B; id <= 0x8C; id++) {
639 dimensions_[id] = {1, 8, Dir::Vertical, 1, false};
640 }
641
642 // 0x8D-0x8E: Downwards Edge 1x1
643 for (int id = 0x8D; id <= 0x8E; id++) {
644 dimensions_[id] = {1, 1, Dir::Vertical, 1, false};
645 }
646
647 // 0x8F: Downwards Bar 2x5 (height = (2*size)+5)
648 dimensions_[0x8F] = {2, 5, Dir::Vertical, 2, false};
649
650 // 0x90-0x91: Downwards 4x2 - GetSize_1to15or26
651 for (int id = 0x90; id <= 0x91; id++) {
652 dimensions_[id] = {4, 0, Dir::Vertical, 2, false};
653 dimensions_[id].zero_size_override = 26;
654 }
655 // 0x92-0x93: Downwards 2x2 - GetSize_1to15or32
656 for (int id = 0x92; id <= 0x93; id++) {
657 dimensions_[id] = {2, 0, Dir::Vertical, 2, true};
658 }
659 // 0x94: Downwards Floor 4x4 - GetSize_1to16
660 dimensions_[0x94] = {4, 4, Dir::Vertical, 4, false};
661
662 // 0x95: Downwards Pots 2x2
663 dimensions_[0x95] = {2, 2, Dir::Vertical, 2, false};
664
665 // 0x96: Downwards Hammer Pegs 2x2
666 dimensions_[0x96] = {2, 2, Dir::Vertical, 2, false};
667
668 // 0x97-0x9F: Nothing
669 for (int id = 0x97; id <= 0x9F; id++) {
670 dimensions_[id] = {1, 1, Dir::None, 0, false};
671 }
672
673 // ============================================================================
674 // Diagonal ceilings (0xA0-0xAC)
675 // ============================================================================
676 // ASM: These have fixed patterns, count = (size & 0x0F) + 4
677 // TopLeft: 0xA0, 0xA5, 0xA9
678 for (int id : {0xA0, 0xA5, 0xA9}) {
679 dimensions_[id] = {4, 4, Dir::Diagonal, 1, false};
680 }
681 // BottomLeft: 0xA1, 0xA6, 0xAA
682 for (int id : {0xA1, 0xA6, 0xAA}) {
683 dimensions_[id] = {4, 4, Dir::Diagonal, 1, false};
684 }
685 // TopRight: 0xA2, 0xA7, 0xAB
686 for (int id : {0xA2, 0xA7, 0xAB}) {
687 dimensions_[id] = {4, 4, Dir::Diagonal, 1, false};
688 }
689 // BottomRight: 0xA3, 0xA8, 0xAC
690 for (int id : {0xA3, 0xA8, 0xAC}) {
691 dimensions_[id] = {4, 4, Dir::Diagonal, 1, false};
692 }
693 // BigHole4x4: 0xA4 - extends both directions
694 dimensions_[0xA4] = {4, 4, Dir::Both, 1, false};
695
696 // 0xAD-0xAF, 0xBE-0xBF: Nothing
697 for (int id : {0xAD, 0xAE, 0xAF, 0xBE, 0xBF}) {
698 dimensions_[id] = {1, 1, Dir::None, 0, false};
699 }
700
701 // 0xB0-0xB1: Rightwards Edge 1x1 +7 (count = size + 8)
702 for (int id = 0xB0; id <= 0xB1; id++) {
703 dimensions_[id] = {8, 1, Dir::Horizontal, 1, false};
704 }
705
706 // 0xB2: 4x4 block
707 dimensions_[0xB2] = {4, 4, Dir::Horizontal, 4, false};
708
709 // 0xB3-0xB4: Edge 1x1 (+2 variant, width = size + 3)
710 for (int id = 0xB3; id <= 0xB4; id++) {
711 dimensions_[id] = {3, 1, Dir::Horizontal, 1, false};
712 }
713
714 // 0xB5: Weird 2x4 - GetSize_1to16, repeated horizontally.
715 dimensions_[0xB5] = {2, 4, Dir::Horizontal, 2, false};
716
717 // 0xB6-0xB7: Rightwards 2x4
718 for (int id = 0xB6; id <= 0xB7; id++) {
719 dimensions_[id] = {0, 4, Dir::Horizontal, 2, false};
720 }
721 for (int id = 0xB6; id <= 0xB7; id++) {
722 dimensions_[id].zero_size_override = 26;
723 }
724
725 // 0xB8-0xB9: Rightwards 2x2
726 for (int id = 0xB8; id <= 0xB9; id++) {
727 dimensions_[id] = {0, 2, Dir::Horizontal, 2, true};
728 }
729
730 // 0xBA: 4x4 block
731 dimensions_[0xBA] = {4, 4, Dir::Horizontal, 4, false};
732
733 // 0xBB: Rightwards Block 2x2 spaced 2
734 dimensions_[0xBB] = {2, 2, Dir::Horizontal, 4, false};
735
736 // 0xBC: Rightwards Pots 2x2
737 dimensions_[0xBC] = {2, 2, Dir::Horizontal, 2, false};
738
739 // 0xBD: Rightwards Hammer Pegs 2x2
740 dimensions_[0xBD] = {2, 2, Dir::Horizontal, 2, false};
741
742 // ============================================================================
743 // SuperSquare objects (0xC0-0xCF, 0xD0-0xDF, 0xE0-0xEF)
744 // These use both size nibbles for independent X/Y sizing.
745 // RoomDraw_4x4BlocksIn4x4SuperSquare, RoomDraw_4x4FloorIn4x4SuperSquare, etc.
746 // width = ((size & 0x0F) + 1) * 4, height = (((size >> 4) & 0x0F) + 1) * 4
747 // ============================================================================
748
749 // 0xC0: Large ceiling (4x4 blocks in super squares)
750 // ASM: RoomDraw_4x4BlocksIn4x4SuperSquare ($018B94)
751 dimensions_[0xC0] = {0, 0, Dir::SuperSquare, 4, false};
752
753 // 0xC2: 4x4 blocks variant (same routine as 0xC0)
754 dimensions_[0xC2] = {0, 0, Dir::SuperSquare, 4, false};
755
756 // 0xC3, 0xD7: 3x3 floor in super square (3x3 spacing)
757 dimensions_[0xC3] = {0, 0, Dir::SuperSquare, 3, false};
758 dimensions_[0xD7] = {0, 0, Dir::SuperSquare, 3, false};
759
760 // 0xC4: 4x4 floor one
761 dimensions_[0xC4] = {0, 0, Dir::SuperSquare, 4, false};
762
763 // 0xC5-0xCA: 4x4 floor patterns
764 for (int id = 0xC5; id <= 0xCA; id++) {
765 dimensions_[id] = {0, 0, Dir::SuperSquare, 4, false};
766 }
767
768 // 0xCB-0xCC: Nothing (RoomDraw_Nothing_E)
769 dimensions_[0xCB] = {1, 1, Dir::None, 0, false};
770 dimensions_[0xCC] = {1, 1, Dir::None, 0, false};
771 // 0xCD-0xCE: Moving walls. Size zero selects count=8 and direction=5,
772 // producing an 11x16 footprint; nonzero dimensions use the split selector
773 // tables in GetMovingWallDimensions.
774 dimensions_[0xCD] = {11, 16, Dir::None, 0, false};
775 dimensions_[0xCE] = {11, 16, Dir::None, 0, false};
776
777 // 0xD1-0xD2: 4x4 floor patterns
778 dimensions_[0xD1] = {0, 0, Dir::SuperSquare, 4, false};
779 dimensions_[0xD2] = {0, 0, Dir::SuperSquare, 4, false};
780
781 // 0xD9: 4x4 floor pattern
782 dimensions_[0xD9] = {0, 0, Dir::SuperSquare, 4, false};
783
784 // 0xDB: 4x4 floor two
785 dimensions_[0xDB] = {0, 0, Dir::SuperSquare, 4, false};
786
787 // 0xDD: Table rock 4x4 rightwards
788 dimensions_[0xDD] = {4, 4, Dir::Horizontal, 4, false};
789 // 0xDE: Spike 2x2 tiling
790 dimensions_[0xDE] = {0, 0, Dir::SuperSquare, 2, false};
791
792 // 0xDF-0xE8: 4x4 floor patterns
793 for (int id = 0xDF; id <= 0xE8; id++) {
794 dimensions_[id] = {0, 0, Dir::SuperSquare, 4, false};
795 }
796
797 // ============================================================================
798 // Chests - fixed 4x4 (matches DrawChest bounding size)
799 // ============================================================================
800 for (int id : {0xF9, 0xFA, 0xFB, 0xFC, 0xFD}) {
801 dimensions_[id] = {4, 4, Dir::None, 0, false};
802 }
803
804 // ============================================================================
805 // Subtype 2 objects (0x100-0x13F)
806 // ============================================================================
807 // Layout corners - 4x4 repeated horizontally
808 for (int id = 0x100; id <= 0x107; id++) {
809 dimensions_[id] = {4, 4, Dir::Horizontal, 4, false};
810 }
811
812 // Other 4x4 patterns
813 for (int id = 0x108; id <= 0x10F; id++) {
814 dimensions_[id] = {4, 4, Dir::None, 0, false};
815 }
816
817 // Weird corners - match 3x4 / 4x3 patterns
818 for (int id = 0x110; id <= 0x113; id++) {
819 dimensions_[id] = {3, 4, Dir::None, 0, false};
820 }
821 for (int id = 0x114; id <= 0x117; id++) {
822 dimensions_[id] = {4, 3, Dir::None, 0, false};
823 }
824 // 0x118-0x11B: Rightwards 2x2 (repeatable)
825 for (int id = 0x118; id <= 0x11B; id++) {
826 dimensions_[id] = {2, 2, Dir::Horizontal, 2, false};
827 }
828 // 0x11C: Rightwards 4x4 (repeatable)
829 dimensions_[0x11C] = {4, 4, Dir::Horizontal, 4, false};
830 // 0x11D: 2x3 pillar (repeated)
831 dimensions_[0x11D] = {2, 3, Dir::Horizontal, 4, false};
832 // 0x11E: Single 2x2
833 dimensions_[0x11E] = {2, 2, Dir::Horizontal, 2, false};
834 // 0x11F-0x120: Enabled star switch / lit torch (fixed 2x2)
835 dimensions_[0x11F] = {2, 2, Dir::None, 0, false};
836 dimensions_[0x120] = {2, 2, Dir::None, 0, false};
837 // 0x121: 2x3 pillar (repeated)
838 dimensions_[0x121] = {2, 3, Dir::Horizontal, 4, false};
839
840 // Tables, beds, etc
841 dimensions_[0x122] = {4, 5, Dir::None, 0, false}; // Bed
842 dimensions_[0x123] = {4, 3, Dir::Horizontal, 8,
843 false}; // Table (8-tile spacing)
844 // 0x124-0x125: 4x4
845 dimensions_[0x124] = {4, 4, Dir::Horizontal, 4, false};
846 dimensions_[0x125] = {4, 4, Dir::Horizontal, 4, false};
847 // 0x126: 2x3 pillar (repeated)
848 dimensions_[0x126] = {2, 3, Dir::Horizontal, 4, false};
849 // 0x127: Rightwards 2x2 (repeatable)
850 dimensions_[0x127] = {2, 2, Dir::Horizontal, 2, false};
851 dimensions_[0x128] = {4, 5, Dir::None, 0, false}; // Bed variant
852 // 0x129: 4x4
853 dimensions_[0x129] = {4, 4, Dir::Horizontal, 4, false};
854 // 0x12A: Mario portrait (fixed 4x2)
855 dimensions_[0x12A] = {4, 2, Dir::None, 0, false};
856 // 0x12B: Rightwards 2x2 (repeatable)
857 dimensions_[0x12B] = {2, 2, Dir::Horizontal, 2, false};
858 // 0x134: Rightwards 2x2 (repeatable)
859 dimensions_[0x134] = {2, 2, Dir::Horizontal, 2, false};
860 dimensions_[0x12C] = {6, 3, Dir::None, 0, false}; // 6x3 pattern
861 // 0x12D-0x133: Inter-room fat stairs + auto stairs (fixed 4x4)
862 for (int id = 0x12D; id <= 0x133; id++) {
863 dimensions_[id] = {4, 4, Dir::None, 0, false};
864 }
865 // 0x135-0x136: Water hop stairs (fixed 4x2)
866 dimensions_[0x135] = {4, 2, Dir::None, 0, false};
867 dimensions_[0x136] = {4, 2, Dir::None, 0, false};
868 // 0x137: Dam floodgate (fixed 10x4)
869 dimensions_[0x137] = {10, 4, Dir::None, 0, false};
870 // 0x138-0x13B: Spiral stairs (fixed 4x3)
871 for (int id = 0x138; id <= 0x13B; id++) {
872 dimensions_[id] = {4, 3, Dir::None, 0, false};
873 }
874 // 0x13C: Sanctuary wall (repeatable 4x4)
875 dimensions_[0x13C] = {4, 4, Dir::Horizontal, 4, false};
876 // 0x13D: Table 4x3 (repeatable with 8-tile spacing)
877 dimensions_[0x13D] = {4, 3, Dir::Horizontal, 8, false};
878 dimensions_[0x13E] = {6, 3, Dir::None, 0, false}; // Utility 6x3
879 // 0x13F: Magic Bat Altar (repeatable 4x4)
880 dimensions_[0x13F] = {4, 4, Dir::Horizontal, 4, false};
881
882 // ============================================================================
883 // Subtype 3 objects (0xF80-0xFFF)
884 // ============================================================================
885 // Default for Type 3: most are 2x2 fixed objects
886 for (int id = 0xF80; id <= 0xFFF; id++) {
887 dimensions_[id] = {2, 2, Dir::None, 0, false};
888 }
889
890 // Override specific Type 3 objects with known sizes
891 // Water face family:
892 // - Empty face can extend to 4x5 at runtime; use the larger stable
893 // footprint so selection/hit-testing matches the active branch.
894 // - Spitting face is 4x5
895 // - Drenching face is 4x7
896 dimensions_[0xF80] = {4, 5, Dir::None, 0, false};
897 dimensions_[0xF81] = {4, 5, Dir::None, 0, false};
898 dimensions_[0xF82] = {4, 7, Dir::None, 0, false};
899
900 // Somaria path pieces each write one tile at the object anchor.
901 for (int id = 0xF83; id <= 0xF8C; id++) {
902 dimensions_[id] = {1, 1, Dir::None, 0, false};
903 }
904 dimensions_[0xF8E] = {1, 1, Dir::None, 0, false};
905 dimensions_[0xF8F] = {1, 1, Dir::None, 0, false};
906
907 // Prison cell bars ($019C44 spans x..x+15 and four rows)
908 dimensions_[0xF8D] = {16, 4, Dir::None, 0, false};
909 dimensions_[0xF97] = {16, 4, Dir::None, 0, false};
910 // Rupee floor pattern
911 dimensions_[0xF92] = {5, 8, Dir::None, 0, false};
912 // Table/rock 4x3 repeated with 8-tile spacing
913 dimensions_[0xF94] = {4, 3, Dir::Horizontal, 8, false};
914 // Single hammer peg (fixed 2x2)
915 dimensions_[0xF96] = {2, 2, Dir::None, 0, false};
916 // Bombable floor (fixed 4x4 in both intact and open states)
917 dimensions_[0xFC7] = {4, 4, Dir::None, 0, false};
918 // Boss shells (single 4x4)
919 dimensions_[0xF95] = {4, 4, Dir::None, 0, false};
920 dimensions_[0xFF2] = {4, 4, Dir::None, 0, false};
921 dimensions_[0xFFB] = {4, 4, Dir::None, 0, false};
922 // Auto/straight stairs (fixed 4x4)
923 for (int id = 0xF9B; id <= 0xFA1; id++) {
924 dimensions_[id] = {4, 4, Dir::None, 0, false};
925 }
926 for (int id = 0xFA6; id <= 0xFA9; id++) {
927 dimensions_[id] = {4, 4, Dir::None, 0, false};
928 }
929 dimensions_[0xFB3] = {4, 4, Dir::None, 0, false};
930 // Big gray rock is a fixed 4x4 stamp; its encoded size is ignored.
931 dimensions_[0xFAC] = {4, 4, Dir::None, 0, false};
932 // Agahnim's altar is a fixed 14x14 mirrored stamp.
933 dimensions_[0xFAD] = {14, 14, Dir::None, 0, false};
934 // Fortune teller facade is a sparse fixed 14x14 BG1 stamp.
935 dimensions_[0xFD4] = {14, 14, Dir::None, 0, false};
936 // Repeatable 4x4 patterns
937 for (int id = 0xFB4; id <= 0xFB9; id++) {
938 dimensions_[id] = {4, 4, Dir::Horizontal, 4, false};
939 }
940 dimensions_[0xFAA] = {4, 4, Dir::Horizontal, 4, false};
941 dimensions_[0xFAE] = {4, 4, Dir::Horizontal, 4, false};
942 dimensions_[0xFE2] = {4, 4, Dir::Horizontal, 4, false};
943 // Big wall decor aliases are fixed 8x3 objects; their size nibble is ignored.
944 dimensions_[0xFCB] = {8, 3, Dir::None, 0, false};
945 dimensions_[0xFF6] = {8, 3, Dir::None, 0, false};
946 dimensions_[0xFF7] = {8, 3, Dir::None, 0, false};
947 // Smithy furnace is a fixed 6x8 stamp; its encoded size is ignored.
948 dimensions_[0xFCC] = {6, 8, Dir::None, 0, false};
949 // Utility patterns
950 dimensions_[0xFCD] = {6, 3, Dir::None, 0, false};
951 dimensions_[0xFDD] = {6, 3, Dir::None, 0, false};
952 dimensions_[0xFD5] = {3, 5, Dir::None, 0, false};
953 dimensions_[0xFDB] = {3, 5, Dir::None, 0, false};
954 // Table bowl is a fixed 4x2 stamp; its encoded size is ignored.
955 dimensions_[0xFDA] = {4, 2, Dir::None, 0, false};
956 // Archery patterns
957 dimensions_[0xFE0] = {3, 6, Dir::None, 0, false};
958 dimensions_[0xFE1] = {3, 6, Dir::None, 0, false};
959 // Solid wall decor 3x4
960 dimensions_[0xFE9] = {3, 4, Dir::None, 0, false};
961 dimensions_[0xFEA] = {3, 4, Dir::None, 0, false};
962 dimensions_[0xFEE] = {3, 4, Dir::None, 0, false};
963 dimensions_[0xFEF] = {3, 4, Dir::None, 0, false};
964 // Light beams + Triforce floor
965 dimensions_[0xFF0] = {4, 10, Dir::None, 0, false};
966 dimensions_[0xFF1] = {8, 8, Dir::None, 0, false};
967 dimensions_[0xFF4] = {8, 8, Dir::None, 0, false};
968 dimensions_[0xFF8] = {8, 8, Dir::None, 0, false};
969 // Table rock 4x3 (repeatable with 8-tile spacing)
970 dimensions_[0xFF9] = {4, 3, Dir::Horizontal, 8, false};
971 // Rightwards 4x4 repeated
972 dimensions_[0xFC8] = {4, 4, Dir::Horizontal, 4, false};
973 // Table/rock 4x3 repeated with 8-tile spacing
974 dimensions_[0xFCE] = {4, 3, Dir::Horizontal, 8, false};
975 // Actual 4x4 (no repetition)
976 dimensions_[0xFE6] = {4, 4, Dir::None, 0, false};
977 dimensions_[0xFE7] = {4, 3, Dir::Horizontal, 8, false};
978 dimensions_[0xFE8] = {4, 3, Dir::Horizontal, 8, false};
979 // Single 4x4 tile8 (large decor)
980 dimensions_[0xFEB] = {4, 4, Dir::None, 0, false};
981 // Single 4x3
982 dimensions_[0xFEC] = {4, 3, Dir::None, 0, false};
983 dimensions_[0xFED] = {4, 3, Dir::None, 0, false};
984 // Turtle Rock pipes
985 dimensions_[0xFBA] = {4, 6, Dir::None, 0, false};
986 dimensions_[0xFBB] = {4, 6, Dir::None, 0, false};
987 dimensions_[0xFBC] = {6, 4, Dir::None, 0, false};
988 dimensions_[0xFBD] = {6, 4, Dir::None, 0, false};
989 dimensions_[0xFDC] = {6, 4, Dir::None, 0, false};
990 // Rightwards 4x4 repeated
991 dimensions_[0xFFA] = {4, 4, Dir::Horizontal, 4, false};
992 // 0xFB1-0xFB2: Big Chest 4x3
993 dimensions_[0xFB1] = {4, 3, Dir::None, 0, false};
994 dimensions_[0xFB2] = {4, 3, Dir::None, 0, false};
995}
996
998 // ROM addresses from ZScream:
999 // Tile data offset table: $018000
1000 // Routine pointer table: $018200
1001 // These tables help determine object sizes based on tile counts
1002
1003 constexpr int kSubtype1TileOffsets = 0x8000;
1004 constexpr int kSubtype1Routines = 0x8200;
1005
1006 // Read tile count for each object to refine dimensions
1007 for (int id = 0; id < 0xF8; id++) {
1008 auto offset_result = rom->ReadWord(kSubtype1TileOffsets + id * 2);
1009 if (!offset_result.ok())
1010 continue;
1011
1012 // Tile count can inform base size
1013 // This is a simplified heuristic - full accuracy requires parsing
1014 // the actual tile data
1015 }
1016}
1017
1019 // Subtype 2 data offset: $0183F0
1020 // Subtype 2 routine ptr: $018470
1021 constexpr int kSubtype2TileOffsets = 0x83F0;
1022 (void)kSubtype2TileOffsets;
1023 (void)rom;
1024 // Similar parsing for subtype 2 objects
1025}
1026
1028 // Subtype 3 data offset: $0184F0
1029 // Subtype 3 routine ptr: $0185F0
1030 constexpr int kSubtype3TileOffsets = 0x84F0;
1031 (void)kSubtype3TileOffsets;
1032 (void)rom;
1033 // Similar parsing for subtype 3 objects
1034}
1035
1036} // namespace zelda3
1037} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:28
absl::StatusOr< uint16_t > ReadWord(int offset) const
Definition rom.cc:526
bool is_loaded() const
Definition rom.h:144
static Flags & get()
Definition features.h:119
static CustomObjectManager & Get()
absl::StatusOr< std::shared_ptr< CustomObject > > GetObjectInternal(int object_id, int subtype)
ROM-based object dimension lookup table.
std::pair< int, int > GetBaseDimensions(int object_id) const
std::tuple< int, int, int, int > GetHitTestBounds(const RoomObject &obj) const
std::pair< int, int > GetDimensions(int object_id, int size) const
std::unordered_map< int, DimensionEntry > dimensions_
SelectionBounds GetSelectionBounds(int object_id, int size) const
int ResolveEffectiveSize(const DimensionEntry &entry, int size) const
static ObjectDimensionTable & Get()
std::pair< int, int > GetSelectionDimensions(int object_id, int size) const
std::pair< int, int > GetClosedChestPlatformDimensions(int size)
bool GetSomariaLineDimensions(int object_id, int *width, int *height)
constexpr int DirectionForSize(int size)
constexpr int ObjectCountForSize(int size)