yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
object_drawer.cc
Go to the documentation of this file.
1#include "object_drawer.h"
2
3#include <cstdio>
4
5#include "absl/strings/str_format.h"
6#include "app/gfx/snes_tile.h"
7#include "util/log.h"
8
9namespace yaze {
10namespace zelda3 {
11
12ObjectDrawer::ObjectDrawer(Rom* rom, const uint8_t* room_gfx_buffer)
13 : rom_(rom), room_gfx_buffer_(room_gfx_buffer) {
15}
16
17absl::Status ObjectDrawer::DrawObject(const RoomObject& object,
20 const gfx::PaletteGroup& palette_group) {
21 LOG_DEBUG("ObjectDrawer", "Drawing object 0x%02X at (%d,%d) size=%d tiles=%zu",
22 object.id_, object.x_, object.y_, object.size_, object.tiles().size());
23
24 if (!rom_ || !rom_->is_loaded()) {
25 return absl::FailedPreconditionError("ROM not loaded");
26 }
27
29 return absl::FailedPreconditionError("Draw routines not initialized");
30 }
31
32 // Ensure object has tiles loaded
33 auto mutable_obj = const_cast<RoomObject&>(object);
34 mutable_obj.set_rom(rom_);
35 mutable_obj.EnsureTilesLoaded();
36
37 // Select buffer based on layer
38 auto& target_bg = (object.layer_ == RoomObject::LayerType::BG2) ? bg2 : bg1;
39
40 // Skip objects that don't have tiles loaded
41 if (mutable_obj.tiles().empty()) {
42 return absl::OkStatus();
43 }
44
45 // Look up draw routine for this object
46 int routine_id = GetDrawRoutineId(object.id_);
47
48 LOG_DEBUG("ObjectDrawer", "Object %04X -> routine_id=%d", object.id_, routine_id);
49
50 if (routine_id < 0 || routine_id >= static_cast<int>(draw_routines_.size())) {
51 LOG_DEBUG("ObjectDrawer", "Using fallback 1x1 drawing for object %04X", object.id_);
52 // Fallback to simple 1x1 drawing using first 8x8 tile
53 if (!mutable_obj.tiles().empty()) {
54 const auto& tile_info = mutable_obj.tiles()[0];
55 WriteTile8(target_bg, object.x_, object.y_, tile_info);
56 }
57 return absl::OkStatus();
58 }
59
60 LOG_DEBUG("ObjectDrawer", "Executing draw routine %d for object %04X", routine_id, object.id_);
61 // Execute the appropriate draw routine
62 draw_routines_[routine_id](this, object, target_bg, mutable_obj.tiles());
63
64 return absl::OkStatus();
65}
66
68 const std::vector<RoomObject>& objects,
71 const gfx::PaletteGroup& palette_group) {
72
73 for (const auto& object : objects) {
74 DrawObject(object, bg1, bg2, palette_group);
75 }
76
77 // CRITICAL: Sync bitmap data to SDL surfaces after all objects are drawn
78 // ObjectDrawer writes directly to bitmap.mutable_data(), but textures are created from SDL surfaces
79 auto& bg1_bmp = bg1.bitmap();
80 auto& bg2_bmp = bg2.bitmap();
81
82 if (bg1_bmp.modified() && bg1_bmp.surface() && bg1_bmp.mutable_data().size() > 0) {
83 SDL_LockSurface(bg1_bmp.surface());
84 memcpy(bg1_bmp.surface()->pixels, bg1_bmp.mutable_data().data(), bg1_bmp.mutable_data().size());
85 SDL_UnlockSurface(bg1_bmp.surface());
86 printf("[ObjectDrawer] Synced BG1 bitmap data to SDL surface (%zu bytes)\n", bg1_bmp.mutable_data().size());
87 }
88
89 if (bg2_bmp.modified() && bg2_bmp.surface() && bg2_bmp.mutable_data().size() > 0) {
90 SDL_LockSurface(bg2_bmp.surface());
91 memcpy(bg2_bmp.surface()->pixels, bg2_bmp.mutable_data().data(), bg2_bmp.mutable_data().size());
92 SDL_UnlockSurface(bg2_bmp.surface());
93 printf("[ObjectDrawer] Synced BG2 bitmap data to SDL surface (%zu bytes)\n", bg2_bmp.mutable_data().size());
94 }
95
96 return absl::OkStatus();
97}
98
99// ============================================================================
100// Draw Routine Registry Initialization
101// ============================================================================
102
104 // This function maps object IDs to their corresponding draw routines.
105 // The mapping is based on ZScream's DungeonObjectData.cs and the game's assembly code.
106 // The order of functions in draw_routines_ MUST match the indices used here.
107
109 draw_routines_.clear();
110
111 // Subtype 1 Object Mappings (Horizontal)
112 object_to_routine_map_[0x00] = 0;
113 for (int id = 0x01; id <= 0x02; id++) { object_to_routine_map_[id] = 1; }
114 for (int id = 0x03; id <= 0x04; id++) { object_to_routine_map_[id] = 2; }
115 for (int id = 0x05; id <= 0x06; id++) { object_to_routine_map_[id] = 3; }
116 for (int id = 0x07; id <= 0x08; id++) { object_to_routine_map_[id] = 4; }
117 object_to_routine_map_[0x09] = 5;
118 for (int id = 0x0A; id <= 0x0B; id++) { object_to_routine_map_[id] = 6; }
119
120 // Subtype 1 Object Mappings (Vertical)
121 object_to_routine_map_[0x60] = 7;
122 for (int id = 0x61; id <= 0x62; id++) { object_to_routine_map_[id] = 8; }
123 for (int id = 0x63; id <= 0x64; id++) { object_to_routine_map_[id] = 9; }
124 for (int id = 0x65; id <= 0x66; id++) { object_to_routine_map_[id] = 10; }
125 for (int id = 0x67; id <= 0x68; id++) { object_to_routine_map_[id] = 11; }
126 object_to_routine_map_[0x69] = 12;
127 for (int id = 0x6A; id <= 0x6B; id++) { object_to_routine_map_[id] = 13; }
128 object_to_routine_map_[0x6C] = 14;
129 object_to_routine_map_[0x6D] = 15;
130
131 // Subtype 2 Object Mappings
132 for (int id = 0x100; id <= 0x10F; id++) { object_to_routine_map_[id] = 16; }
133
134 // Additional object mappings from logs
135 object_to_routine_map_[0x33] = 16;
136 object_to_routine_map_[0xC6] = 7; // Vertical draw
137
138 // Initialize draw routine function array in the correct order
139 draw_routines_.reserve(35);
140
141 // Routine 0
142 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
143 self->DrawRightwards2x2_1to15or32(obj, bg, tiles);
144 });
145 // Routine 1
146 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
147 self->DrawRightwards2x4_1to15or26(obj, bg, tiles);
148 });
149 // Routine 2
150 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
151 self->DrawRightwards2x4spaced4_1to16(obj, bg, tiles);
152 });
153 // Routine 3
154 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
155 self->DrawRightwards2x4spaced4_1to16_BothBG(obj, bg, tiles);
156 });
157 // Routine 4
158 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
159 self->DrawRightwards2x2_1to16(obj, bg, tiles);
160 });
161 // Routine 5
162 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
163 self->DrawDiagonalAcute_1to16(obj, bg, tiles);
164 });
165 // Routine 6
166 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
167 self->DrawDiagonalGrave_1to16(obj, bg, tiles);
168 });
169 // Routine 7
170 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
171 self->DrawDownwards2x2_1to15or32(obj, bg, tiles);
172 });
173 // Routine 8
174 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
175 self->DrawDownwards4x2_1to15or26(obj, bg, tiles);
176 });
177 // Routine 9
178 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
179 self->DrawDownwards4x2_1to16_BothBG(obj, bg, tiles);
180 });
181 // Routine 10
182 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
183 self->DrawDownwardsDecor4x2spaced4_1to16(obj, bg, tiles);
184 });
185 // Routine 11
186 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
187 self->DrawDownwards2x2_1to16(obj, bg, tiles);
188 });
189 // Routine 12
190 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
191 self->DrawDownwardsHasEdge1x1_1to16_plus3(obj, bg, tiles);
192 });
193 // Routine 13
194 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
195 self->DrawDownwardsEdge1x1_1to16(obj, bg, tiles);
196 });
197 // Routine 14
198 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
199 self->DrawDownwardsLeftCorners2x1_1to16_plus12(obj, bg, tiles);
200 });
201 // Routine 15
202 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
203 self->DrawDownwardsRightCorners2x1_1to16_plus12(obj, bg, tiles);
204 });
205 // Routine 16
206 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
207 self->DrawRightwards4x4_1to16(obj, bg, tiles);
208 });
209
211}
212
213int ObjectDrawer::GetDrawRoutineId(int16_t object_id) const {
214 auto it = object_to_routine_map_.find(object_id);
215 if (it != object_to_routine_map_.end()) {
216 return it->second;
217 }
218
219 // Default to simple 1x1 solid for unmapped objects
220 return -1;
221}
222
223// ============================================================================
224// Draw Routine Implementations (Based on ZScream patterns)
225// ============================================================================
226
228 std::span<const gfx::TileInfo> tiles) {
229 // Pattern: Draws 2x2 tiles rightward (object 0x00)
230 // Size byte determines how many times to repeat (1-15 or 32)
231 int size = obj.size_;
232 if (size == 0) size = 32; // Special case for object 0x00
233
234 for (int s = 0; s < size; s++) {
235 if (tiles.size() >= 4) {
236 // Draw 2x2 pattern using 8x8 tiles from the span
237 WriteTile8(bg, obj.x_ + (s * 2), obj.y_, tiles[0]); // Top-left
238 WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_, tiles[1]); // Top-right
239 WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 1, tiles[2]); // Bottom-left
240 WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_ + 1, tiles[3]); // Bottom-right
241 }
242 }
243}
244
246 std::span<const gfx::TileInfo> tiles) {
247 // Pattern: Draws 2x4 tiles rightward (objects 0x01-0x02)
248 int size = obj.size_;
249 if (size == 0) size = 26; // Special case
250
251 for (int s = 0; s < size; s++) {
252 if (tiles.size() >= 4) {
253 // For 2x4, we'll use the same tile pattern repeated
254 WriteTile8(bg, obj.x_ + (s * 2), obj.y_, tiles[0]); // Top-left
255 WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_, tiles[1]); // Top-right
256 WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 1, tiles[2]); // Mid-left
257 WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_ + 1, tiles[3]); // Mid-right
258 WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 2, tiles[0]); // Bottom-left (repeat)
259 WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_ + 2, tiles[1]); // Bottom-right (repeat)
260 WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 3, tiles[2]); // Bottom-left (repeat)
261 WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_ + 3, tiles[3]); // Bottom-right (repeat)
262 }
263 }
264}
265
267 std::span<const gfx::TileInfo> tiles) {
268 // Pattern: Draws 2x4 tiles rightward with spacing (objects 0x03-0x04)
269 int size = obj.size_ & 0x0F;
270
271 for (int s = 0; s < size; s++) {
272 if (tiles.size() >= 4) {
273 // Draw 2x4 pattern with spacing using 8x8 tiles from span
274 WriteTile8(bg, obj.x_ + (s * 6), obj.y_, tiles[0]); // Top-left
275 WriteTile8(bg, obj.x_ + (s * 6) + 1, obj.y_, tiles[1]); // Top-right
276 WriteTile8(bg, obj.x_ + (s * 6), obj.y_ + 1, tiles[2]); // Mid-left
277 WriteTile8(bg, obj.x_ + (s * 6) + 1, obj.y_ + 1, tiles[3]); // Mid-right
278 WriteTile8(bg, obj.x_ + (s * 6), obj.y_ + 2, tiles[0]); // Bottom-left (repeat)
279 WriteTile8(bg, obj.x_ + (s * 6) + 1, obj.y_ + 2, tiles[1]); // Bottom-right (repeat)
280 WriteTile8(bg, obj.x_ + (s * 6), obj.y_ + 3, tiles[2]); // Bottom-left (repeat)
281 WriteTile8(bg, obj.x_ + (s * 6) + 1, obj.y_ + 3, tiles[3]); // Bottom-right (repeat)
282 }
283 }
284}
285
287 std::span<const gfx::TileInfo> tiles) {
288 // Pattern: Same as above but draws to both BG1 and BG2 (objects 0x05-0x06)
289 DrawRightwards2x4spaced4_1to16(obj, bg, tiles);
290 // Note: BothBG would require access to both buffers - simplified for now
291}
292
294 std::span<const gfx::TileInfo> tiles) {
295 // Pattern: Draws 2x2 tiles rightward (objects 0x07-0x08)
296 int size = obj.size_ & 0x0F;
297
298 for (int s = 0; s < size; s++) {
299 if (tiles.size() >= 4) {
300 // Draw 2x2 pattern using 8x8 tiles from span
301 WriteTile8(bg, obj.x_ + (s * 2), obj.y_, tiles[0]); // Top-left
302 WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_, tiles[1]); // Top-right
303 WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 1, tiles[2]); // Bottom-left
304 WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_ + 1, tiles[3]); // Bottom-right
305 }
306 }
307}
308
310 std::span<const gfx::TileInfo> tiles) {
311 // Pattern: Diagonal line going down-right (/) (object 0x09)
312 int size = obj.size_ & 0x0F;
313
314 for (int s = 0; s < size + 6; s++) {
315 if (tiles.size() >= 4) {
316 // Use first tile span for diagonal pattern
317 for (int i = 0; i < 5; i++) {
318 // Cycle through the 4 tiles in the span
319 const gfx::TileInfo& tile_info = tiles[i % 4];
320 WriteTile8(bg, obj.x_ + s, obj.y_ + (i - s), tile_info);
321 }
322 }
323 }
324}
325
327 std::span<const gfx::TileInfo> tiles) {
328 // Pattern: Diagonal line going down-left (\‍) (objects 0x0A-0x0B)
329 int size = obj.size_ & 0x0F;
330
331 for (int s = 0; s < size + 6; s++) {
332 if (tiles.size() >= 4) {
333 // Use first tile span for diagonal pattern
334 for (int i = 0; i < 5; i++) {
335 // Cycle through the 4 tiles in the span
336 const gfx::TileInfo& tile_info = tiles[i % 4];
337 WriteTile8(bg, obj.x_ + s, obj.y_ + (i + s), tile_info);
338 }
339 }
340 }
341}
342
344 std::span<const gfx::TileInfo> tiles) {
345 // Pattern: Diagonal acute for both BG layers (objects 0x15-0x1F)
346 DrawDiagonalAcute_1to16(obj, bg, tiles);
347}
348
350 std::span<const gfx::TileInfo> tiles) {
351 // Pattern: Diagonal grave for both BG layers (objects 0x16-0x20)
352 DrawDiagonalGrave_1to16(obj, bg, tiles);
353}
354
356 std::span<const gfx::TileInfo> tiles) {
357 // Pattern: 1x2 tiles rightward with +2 offset (object 0x21)
358 int size = obj.size_ & 0x0F;
359
360 for (int s = 0; s < size; s++) {
361 if (tiles.size() >= 2) {
362 // Use first tile span for 1x2 pattern
363 WriteTile8(bg, obj.x_ + s + 2, obj.y_, tiles[0]);
364 WriteTile8(bg, obj.x_ + s + 2, obj.y_ + 1, tiles[1]);
365 }
366 }
367}
368
370 std::span<const gfx::TileInfo> tiles) {
371 // Pattern: 1x1 tiles with edge detection +3 offset (object 0x22)
372 int size = obj.size_ & 0x0F;
373
374 for (int s = 0; s < size; s++) {
375 if (tiles.size() >= 1) {
376 // Use first 8x8 tile from span
377 WriteTile8(bg, obj.x_ + s + 3, obj.y_, tiles[0]);
378 }
379 }
380}
381
383 std::span<const gfx::TileInfo> tiles) {
384 // Pattern: 1x1 tiles with edge detection +2 offset (objects 0x23-0x2E)
385 int size = obj.size_ & 0x0F;
386
387 for (int s = 0; s < size; s++) {
388 if (tiles.size() >= 1) {
389 // Use first 8x8 tile from span
390 WriteTile8(bg, obj.x_ + s + 2, obj.y_, tiles[0]);
391 }
392 }
393}
394
396 std::span<const gfx::TileInfo> tiles) {
397 // Pattern: Top corner 1x2 tiles with +13 offset (object 0x2F)
398 int size = obj.size_ & 0x0F;
399
400 for (int s = 0; s < size; s++) {
401 if (tiles.size() >= 2) {
402 // Use first tile span for 1x2 pattern
403 WriteTile8(bg, obj.x_ + s + 13, obj.y_, tiles[0]);
404 WriteTile8(bg, obj.x_ + s + 13, obj.y_ + 1, tiles[1]);
405 }
406 }
407}
408
410 const std::span<const gfx::TileInfo> tiles) {
411 // Pattern: Bottom corner 1x2 tiles with +13 offset (object 0x30)
412 int size = obj.size_ & 0x0F;
413
414 for (int s = 0; s < size; s++) {
415 if (tiles.size() >= 2) {
416 // Use first tile span for 1x2 pattern
417 WriteTile8(bg, obj.x_ + s + 13, obj.y_ + 1, tiles[0]);
418 WriteTile8(bg, obj.x_ + s + 13, obj.y_ + 2, tiles[1]);
419 }
420 }
421}
422
424 std::span<const gfx::TileInfo> tiles) {
425 // Pattern: Custom draw routine (objects 0x31-0x32)
426 // For now, fall back to simple 1x1
427 if (tiles.size() >= 1) {
428 // Use first 8x8 tile from span
429 WriteTile8(bg, obj.x_, obj.y_, tiles[0]);
430 }
431}
432
434 std::span<const gfx::TileInfo> tiles) {
435 // Pattern: 4x4 block rightward (object 0x33)
436 int size = obj.size_ & 0x0F;
437
438 for (int s = 0; s < size; s++) {
439 if (tiles.size() >= 16) {
440 // Draw 4x4 pattern using 8x8 tiles from span
441 for (int y = 0; y < 4; ++y) {
442 for (int x = 0; x < 4; ++x) {
443 WriteTile8(bg, obj.x_ + (s * 4) + x, obj.y_ + y, tiles[y * 4 + x]);
444 }
445 }
446 }
447 }
448}
449
451 std::span<const gfx::TileInfo> tiles) {
452 // Pattern: 1x1 solid tiles +3 offset (object 0x34)
453 int size = obj.size_ & 0x0F;
454
455 for (int s = 0; s < size; s++) {
456 if (tiles.size() >= 1) {
457 // Use first 8x8 tile from span
458 WriteTile8(bg, obj.x_ + s + 3, obj.y_, tiles[0]);
459 }
460 }
461}
462
464 std::span<const gfx::TileInfo> tiles) {
465 // Pattern: Door switcher (object 0x35)
466 // Special door logic - simplified for now
467 if (tiles.size() >= 1) {
468 // Use first 8x8 tile from span
469 WriteTile8(bg, obj.x_, obj.y_, tiles[0]);
470 }
471}
472
474 std::span<const gfx::TileInfo> tiles) {
475 // Pattern: 4x4 decoration with spacing (objects 0x36-0x37)
476 int size = obj.size_ & 0x0F;
477
478 for (int s = 0; s < size; s++) {
479 if (tiles.size() >= 16) {
480 // Draw 4x4 pattern with spacing using 8x8 tiles from span
481 for (int y = 0; y < 4; ++y) {
482 for (int x = 0; x < 4; ++x) {
483 WriteTile8(bg, obj.x_ + (s * 6) + x, obj.y_ + y, tiles[y * 4 + x]);
484 }
485 }
486 }
487 }
488}
489
491 std::span<const gfx::TileInfo> tiles) {
492 // Pattern: 2x3 statue with spacing (object 0x38)
493 int size = obj.size_ & 0x0F;
494
495 for (int s = 0; s < size; s++) {
496 if (tiles.size() >= 6) {
497 // Draw 2x3 pattern using 8x8 tiles from span
498 for (int y = 0; y < 3; ++y) {
499 for (int x = 0; x < 2; ++x) {
500 WriteTile8(bg, obj.x_ + (s * 4) + x, obj.y_ + y, tiles[y * 2 + x]);
501 }
502 }
503 }
504 }
505}
506
508 std::span<const gfx::TileInfo> tiles) {
509 // Pattern: 2x4 pillar with spacing (objects 0x39, 0x3D)
510 int size = obj.size_ & 0x0F;
511
512 for (int s = 0; s < size; s++) {
513 if (tiles.size() >= 8) {
514 // Draw 2x4 pattern using 8x8 tiles from span
515 for (int y = 0; y < 4; ++y) {
516 for (int x = 0; x < 2; ++x) {
517 WriteTile8(bg, obj.x_ + (s * 6) + x, obj.y_ + y, tiles[y * 2 + x]);
518 }
519 }
520 }
521 }
522}
523
525 std::span<const gfx::TileInfo> tiles) {
526 // Pattern: 4x3 decoration with spacing (objects 0x3A-0x3B)
527 int size = obj.size_ & 0x0F;
528
529 for (int s = 0; s < size; s++) {
530 if (tiles.size() >= 12) {
531 // Draw 4x3 pattern using 8x8 tiles from span
532 for (int y = 0; y < 3; ++y) {
533 for (int x = 0; x < 4; ++x) {
534 WriteTile8(bg, obj.x_ + (s * 6) + x, obj.y_ + y, tiles[y * 4 + x]);
535 }
536 }
537 }
538 }
539}
540
542 std::span<const gfx::TileInfo> tiles) {
543 // Pattern: Doubled 2x2 with spacing (object 0x3C)
544 int size = obj.size_ & 0x0F;
545
546 for (int s = 0; s < size; s++) {
547 if (tiles.size() >= 8) {
548 // Draw doubled 2x2 pattern using 8x8 tiles from span
549 for (int y = 0; y < 2; ++y) {
550 for (int x = 0; x < 4; ++x) { // Draw a 4x2 area
551 WriteTile8(bg, obj.x_ + (s * 6) + x, obj.y_ + y, tiles[y * 4 + x]);
552 }
553 }
554 }
555 }
556}
557
559 std::span<const gfx::TileInfo> tiles) {
560 // Pattern: 2x2 decoration with large spacing (object 0x3E)
561 int size = obj.size_ & 0x0F;
562
563 for (int s = 0; s < size; s++) {
564 if (tiles.size() >= 4) {
565 // Draw 2x2 decoration with 12-tile spacing using 8x8 tiles from span
566 WriteTile8(bg, obj.x_ + (s * 14), obj.y_, tiles[0]); // Top-left
567 WriteTile8(bg, obj.x_ + (s * 14) + 1, obj.y_, tiles[1]); // Top-right
568 WriteTile8(bg, obj.x_ + (s * 14), obj.y_ + 1, tiles[2]); // Bottom-left
569 WriteTile8(bg, obj.x_ + (s * 14) + 1, obj.y_ + 1, tiles[3]); // Bottom-right
570 }
571 }
572}
573
574// ============================================================================
575// Downwards Draw Routines (Missing Implementation)
576// ============================================================================
577
579 std::span<const gfx::TileInfo> tiles) {
580 // Pattern: Draws 2x2 tiles downward (object 0x60)
581 // Size byte determines how many times to repeat (1-15 or 32)
582 int size = obj.size_;
583 if (size == 0) size = 32; // Special case for object 0x60
584
585 for (int s = 0; s < size; s++) {
586 if (tiles.size() >= 4) {
587 // Draw 2x2 pattern using 8x8 tiles from the span
588 WriteTile8(bg, obj.x_, obj.y_ + (s * 2), tiles[0]); // Top-left
589 WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2), tiles[1]); // Top-right
590 WriteTile8(bg, obj.x_, obj.y_ + (s * 2) + 1, tiles[2]); // Bottom-left
591 WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2) + 1, tiles[3]); // Bottom-right
592 }
593 }
594}
595
597 std::span<const gfx::TileInfo> tiles) {
598 // Pattern: Draws 4x2 tiles downward (objects 0x61-0x62)
599 int size = obj.size_;
600 if (size == 0) size = 26; // Special case
601
602 LOG_DEBUG("ObjectDrawer", "DrawDownwards4x2_1to15or26: obj=%04X tiles=%zu size=%d",
603 obj.id_, tiles.size(), size);
604
605 for (int s = 0; s < size; s++) {
606 if (tiles.size() >= 8) {
607 // Draw 4x2 pattern using 8 tiles from span
608 // Top row: tiles 0,1,2,3
609 WriteTile8(bg, obj.x_, obj.y_ + (s * 2), tiles[0]); // Top-left
610 WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2), tiles[1]); // Top-right
611 WriteTile8(bg, obj.x_ + 2, obj.y_ + (s * 2), tiles[2]); // Top-middle-left
612 WriteTile8(bg, obj.x_ + 3, obj.y_ + (s * 2), tiles[3]); // Top-middle-right
613
614 // Bottom row: tiles 4,5,6,7
615 WriteTile8(bg, obj.x_, obj.y_ + (s * 2) + 1, tiles[4]); // Bottom-left
616 WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2) + 1, tiles[5]); // Bottom-right
617 WriteTile8(bg, obj.x_ + 2, obj.y_ + (s * 2) + 1, tiles[6]); // Bottom-middle-left
618 WriteTile8(bg, obj.x_ + 3, obj.y_ + (s * 2) + 1, tiles[7]); // Bottom-middle-right
619 } else if (tiles.size() >= 4) {
620 // Fallback: use only first 4 tiles and repeat
621 WriteTile8(bg, obj.x_, obj.y_ + (s * 2), tiles[0]);
622 WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2), tiles[1]);
623 WriteTile8(bg, obj.x_ + 2, obj.y_ + (s * 2), tiles[0]);
624 WriteTile8(bg, obj.x_ + 3, obj.y_ + (s * 2), tiles[1]);
625 WriteTile8(bg, obj.x_, obj.y_ + (s * 2) + 1, tiles[2]);
626 WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2) + 1, tiles[3]);
627 WriteTile8(bg, obj.x_ + 2, obj.y_ + (s * 2) + 1, tiles[2]);
628 WriteTile8(bg, obj.x_ + 3, obj.y_ + (s * 2) + 1, tiles[3]);
629 }
630 }
631}
632
634 std::span<const gfx::TileInfo> tiles) {
635 // Pattern: Same as above but draws to both BG1 and BG2 (objects 0x63-0x64)
636 DrawDownwards4x2_1to15or26(obj, bg, tiles);
637 // Note: BothBG would require access to both buffers - simplified for now
638}
639
641 std::span<const gfx::TileInfo> tiles) {
642 // Pattern: Draws 4x2 decoration downward with spacing (objects 0x65-0x66)
643 int size = obj.size_ & 0x0F;
644
645 for (int s = 0; s < size; s++) {
646 if (tiles.size() >= 8) {
647 // Draw 4x2 pattern with spacing using 8x8 tiles from span
648 WriteTile8(bg, obj.x_, obj.y_ + (s * 6), tiles[0]);
649 WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 6), tiles[1]);
650 WriteTile8(bg, obj.x_ + 2, obj.y_ + (s * 6), tiles[2]);
651 WriteTile8(bg, obj.x_ + 3, obj.y_ + (s * 6), tiles[3]);
652 WriteTile8(bg, obj.x_, obj.y_ + (s * 6) + 1, tiles[4]);
653 WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 6) + 1, tiles[5]);
654 WriteTile8(bg, obj.x_ + 2, obj.y_ + (s * 6) + 1, tiles[6]);
655 WriteTile8(bg, obj.x_ + 3, obj.y_ + (s * 6) + 1, tiles[7]);
656 }
657 }
658}
659
661 std::span<const gfx::TileInfo> tiles) {
662 // Pattern: Draws 2x2 tiles downward (objects 0x67-0x68)
663 int size = obj.size_ & 0x0F;
664
665 for (int s = 0; s < size; s++) {
666 if (tiles.size() >= 4) {
667 // Draw 2x2 pattern using 8x8 tiles from span
668 WriteTile8(bg, obj.x_, obj.y_ + (s * 2), tiles[0]);
669 WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2), tiles[1]);
670 WriteTile8(bg, obj.x_, obj.y_ + (s * 2) + 1, tiles[2]);
671 WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2) + 1, tiles[3]);
672 }
673 }
674}
675
677 std::span<const gfx::TileInfo> tiles) {
678 // Pattern: 1x1 tiles with edge detection +3 offset downward (object 0x69)
679 int size = obj.size_ & 0x0F;
680
681 for (int s = 0; s < size; s++) {
682 if (tiles.size() >= 1) {
683 // Use first 8x8 tile from span
684 WriteTile8(bg, obj.x_ + 3, obj.y_ + s, tiles[0]);
685 }
686 }
687}
688
690 std::span<const gfx::TileInfo> tiles) {
691 // Pattern: 1x1 edge tiles downward (objects 0x6A-0x6B)
692 int size = obj.size_ & 0x0F;
693
694 for (int s = 0; s < size; s++) {
695 if (tiles.size() >= 1) {
696 // Use first 8x8 tile from span
697 WriteTile8(bg, obj.x_, obj.y_ + s, tiles[0]);
698 }
699 }
700}
701
703 std::span<const gfx::TileInfo> tiles) {
704 // Pattern: Left corner 2x1 tiles with +12 offset downward (object 0x6C)
705 int size = obj.size_ & 0x0F;
706
707 for (int s = 0; s < size; s++) {
708 if (tiles.size() >= 2) {
709 // Use first tile span for 2x1 pattern
710 WriteTile8(bg, obj.x_ + 12, obj.y_ + s, tiles[0]);
711 WriteTile8(bg, obj.x_ + 12 + 1, obj.y_ + s, tiles[1]);
712 }
713 }
714}
715
717 std::span<const gfx::TileInfo> tiles) {
718 // Pattern: Right corner 2x1 tiles with +12 offset downward (object 0x6D)
719 int size = obj.size_ & 0x0F;
720
721 for (int s = 0; s < size; s++) {
722 if (tiles.size() >= 2) {
723 // Use first tile span for 2x1 pattern
724 WriteTile8(bg, obj.x_ + 12, obj.y_ + s, tiles[0]);
725 WriteTile8(bg, obj.x_ + 12 + 1, obj.y_ + s, tiles[1]);
726 }
727 }
728}
729
730// ============================================================================
731// Utility Methods
732// ============================================================================
733
734void ObjectDrawer::WriteTile8(gfx::BackgroundBuffer& bg, int tile_x, int tile_y,
735 const gfx::TileInfo& tile_info) {
736 // Draw directly to bitmap instead of tile buffer to avoid being overwritten
737 auto& bitmap = bg.bitmap();
738 if (!bitmap.is_active() || bitmap.width() == 0) {
739 return; // Bitmap not ready
740 }
741
742 // The room-specific graphics buffer (current_gfx16_) contains the assembled
743 // tile graphics for the current room. Object tile IDs are relative to this buffer.
744 const uint8_t* gfx_data = room_gfx_buffer_;
745
746 if (!gfx_data) {
747 LOG_DEBUG("ObjectDrawer", "ERROR: No graphics data available");
748 return;
749 }
750
751 // Draw single 8x8 tile directly to bitmap
752 DrawTileToBitmap(bitmap, tile_info, tile_x * 8, tile_y * 8, gfx_data);
753}
754
755bool ObjectDrawer::IsValidTilePosition(int tile_x, int tile_y) const {
756 return tile_x >= 0 && tile_x < kMaxTilesX &&
757 tile_y >= 0 && tile_y < kMaxTilesY;
758}
759
761 int pixel_x, int pixel_y, const uint8_t* tiledata) {
762 // Draw an 8x8 tile directly to bitmap at pixel coordinates
763 if (!tiledata) return;
764
765 // DEBUG: Check if bitmap is valid
766 if (!bitmap.is_active() || bitmap.width() == 0 || bitmap.height() == 0) {
767 LOG_DEBUG("ObjectDrawer", "ERROR: Invalid bitmap - active=%d, size=%dx%d",
768 bitmap.is_active(), bitmap.width(), bitmap.height());
769 return;
770 }
771
772 // Calculate tile position in graphics sheet (128 pixels wide, 16 tiles per row)
773 int tile_sheet_x = (tile_info.id_ % 16) * 8; // 16 tiles per row
774 int tile_sheet_y = (tile_info.id_ / 16) * 8; // Each row is 16 tiles
775
776 // Palettes are 3bpp (8 colors). Convert palette index to base color offset.
777 uint8_t palette_offset = (tile_info.palette_ & 0x0F) * 8;
778
779 // DEBUG: Log tile info for first few tiles
780 static int debug_tile_count = 0;
781 if (debug_tile_count < 5) {
782 printf("[ObjectDrawer] DrawTile: id=0x%03X pos=(%d,%d) sheet=(%d,%d) pal=%d mirror=(h:%d,v:%d)\n",
783 tile_info.id_, pixel_x, pixel_y, tile_sheet_x, tile_sheet_y,
784 tile_info.palette_, tile_info.horizontal_mirror_, tile_info.vertical_mirror_);
785 debug_tile_count++;
786 }
787
788 // Draw 8x8 pixels
789 int pixels_written = 0;
790 int pixels_transparent = 0;
791 for (int py = 0; py < 8; py++) {
792 for (int px = 0; px < 8; px++) {
793 // Apply mirroring
794 int src_x = tile_info.horizontal_mirror_ ? (7 - px) : px;
795 int src_y = tile_info.vertical_mirror_ ? (7 - py) : py;
796
797 // Read pixel from graphics sheet
798 int src_index = (tile_sheet_y + src_y) * 128 + (tile_sheet_x + src_x);
799 uint8_t pixel_index = tiledata[src_index];
800 if (pixel_index == 0) {
801 pixels_transparent++;
802 continue;
803 }
804 uint8_t final_color = pixel_index + palette_offset;
805 int dest_x = pixel_x + px;
806 int dest_y = pixel_y + py;
807
808 if (dest_x >= 0 && dest_x < bitmap.width() && dest_y >= 0 && dest_y < bitmap.height()) {
809 int dest_index = dest_y * bitmap.width() + dest_x;
810 if (dest_index >= 0 && dest_index < static_cast<int>(bitmap.mutable_data().size())) {
811 bitmap.mutable_data()[dest_index] = final_color;
812 pixels_written++;
813 }
814 }
815 }
816 }
817
818 // Mark bitmap as modified if we wrote any pixels
819 if (pixels_written > 0) {
820 bitmap.set_modified(true);
821 }
822
823 // DEBUG: Log pixel writing stats for first few tiles
824 if (debug_tile_count < 5) {
825 printf("[ObjectDrawer] Tile 0x%03X: wrote %d pixels, %d transparent, src_index_range=[%d,%d]\n",
826 tile_info.id_, pixels_written, pixels_transparent,
827 (tile_sheet_y * 128 + tile_sheet_x),
828 (tile_sheet_y + 7) * 128 + (tile_sheet_x + 7));
829 }
830}
831
832} // namespace zelda3
833} // namespace yaze
The Rom class is used to load, save, and modify Rom data.
Definition rom.h:71
bool is_loaded() const
Definition rom.h:197
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:66
bool is_active() const
Definition bitmap.h:264
void set_modified(bool modified)
Definition bitmap.h:267
int height() const
Definition bitmap.h:254
int width() const
Definition bitmap.h:253
std::vector< uint8_t > & mutable_data()
Definition bitmap.h:258
SNES 16-bit tile metadata container.
Definition snes_tile.h:50
Draws dungeon objects to background buffers using game patterns.
int GetDrawRoutineId(int16_t object_id) const
Get draw routine ID for an object.
void WriteTile8(gfx::BackgroundBuffer &bg, int tile_x, int tile_y, const gfx::TileInfo &tile_info)
void DrawRightwardsPillar2x4spaced4_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
void DrawTileToBitmap(gfx::Bitmap &bitmap, const gfx::TileInfo &tile_info, int pixel_x, int pixel_y, const uint8_t *tiledata)
Draw a single tile directly to bitmap.
void InitializeDrawRoutines()
Initialize draw routine registry Must be called before drawing objects.
void DrawDiagonalAcute_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
void DrawRightwards2x4spaced4_1to16_BothBG(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
std::vector< DrawRoutine > draw_routines_
void DrawDownwardsRightCorners2x1_1to16_plus12(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
void DrawDownwardsLeftCorners2x1_1to16_plus12(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
void DrawRightwards2x4_1to15or26(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
void DrawRightwards4x4_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
void DrawDiagonalGrave_1to16_BothBG(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
void DrawRightwardsHasEdge1x1_1to16_plus2(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
void DrawRightwardsHasEdge1x1_1to16_plus3(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
absl::Status DrawObjectList(const std::vector< RoomObject > &objects, gfx::BackgroundBuffer &bg1, gfx::BackgroundBuffer &bg2, const gfx::PaletteGroup &palette_group)
Draw all objects in a room.
const uint8_t * room_gfx_buffer_
void DrawRightwards1x2_1to16_plus2(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
void DrawDownwardsDecor4x2spaced4_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
void DrawDownwardsEdge1x1_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
absl::Status DrawObject(const RoomObject &object, gfx::BackgroundBuffer &bg1, gfx::BackgroundBuffer &bg2, const gfx::PaletteGroup &palette_group)
Draw a room object to background buffers.
void DrawRightwards2x2_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
ObjectDrawer(Rom *rom, const uint8_t *room_gfx_buffer=nullptr)
void DrawDiagonalAcute_1to16_BothBG(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
void DrawRightwards1x1Solid_1to16_plus3(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
void DrawRightwards2x2_1to15or32(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
void DrawDownwardsHasEdge1x1_1to16_plus3(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
std::unordered_map< int16_t, int > object_to_routine_map_
void DrawDoorSwitcherer(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
static constexpr int kMaxTilesX
void DrawRightwardsStatue2x3spaced2_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
void DrawDownwards4x2_1to16_BothBG(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
void DrawRightwardsBottomCorners1x2_1to16_plus13(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
void DrawDiagonalGrave_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
void DrawRightwards2x4spaced4_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
void DrawRightwardsDecor4x4spaced2_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
void DrawRightwardsDecor2x2spaced12_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
void DrawDownwards2x2_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
static constexpr int kMaxTilesY
void DrawDownwards2x2_1to15or32(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
void DrawRightwardsDecor4x3spaced4_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
void CustomDraw(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
bool IsValidTilePosition(int tile_x, int tile_y) const
void DrawDownwards4x2_1to15or26(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
void DrawRightwardsTopCorners1x2_1to16_plus13(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
void DrawRightwardsDoubled2x2spaced2_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
const std::vector< gfx::TileInfo > & tiles() const
Definition room_object.h:87
void set_rom(Rom *rom)
Definition room_object.h:67
#define LOG_DEBUG(category, format,...)
Definition log.h:104
Main namespace for the application.
Represents a group of palettes.