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