yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
room_layer_manager.h
Go to the documentation of this file.
1#ifndef YAZE_ZELDA3_DUNGEON_ROOM_LAYER_MANAGER_H
2#define YAZE_ZELDA3_DUNGEON_ROOM_LAYER_MANAGER_H
3
4#include <array>
5#include <cstdint>
6#include <vector>
7
10#include "zelda3/dungeon/room.h"
11
12namespace yaze {
13namespace zelda3 {
14
23enum class LayerType {
24 BG1_Layout, // Base BG1 tiles from room layout
25 BG1_Objects, // Objects drawn to BG1 (Layer 0, 2)
26 BG2_Layout, // Base BG2 tiles from room layout
27 BG2_Objects // Objects drawn to BG2 (Layer 1)
28};
29
33enum class LayerBlendMode {
34 Normal, // Standard alpha blending
35 Translucent, // 50% alpha
36 Addition, // Additive blending
37 Dark, // Darkened blend
38 Off // Layer hidden
39};
40
45 size_t object_index = 0;
46 bool translucent = false;
47 uint8_t alpha = 255; // 0-255 alpha value
48};
49
58 size_t object_index = 0;
59 int layer = 0; // Object's layer (0=BG1, 1=BG2, 2=BG1 priority)
60 int priority = 0; // Object layer value (not visual Z-order)
61 bool is_bg2_object = false; // True if object renders to BG2 buffer
62};
63
80 public:
82
83 // Reset to default state (all layers visible, normal blend)
84 void Reset() {
85 for (int i = 0; i < 4; ++i) {
86 layer_visible_[i] = true;
88 layer_alpha_[i] = 255;
89 }
91 bg2_on_top_ = false;
96 cgadsub_ = 0;
97 show_hidden_layers_ = false;
98 layers_merged_ = false;
101 true; // Default to SNES-informed priority order
102 }
103
104 // Priority compositing control
105 // When enabled (default): Uses SNES Mode 1 per-tile priority for Z-ordering
106 // When disabled: Simple back-to-front layer order (BG2 behind, BG1 in front)
107 void SetPriorityCompositing(bool enabled) {
109 }
113
114 // The game's own layer settings for the room (room_layer_registers.h).
115 // When the game shows neither tilemap bit 0 on main nor sub screen, the
116 // lower tilemap (yaze's BG2 buffers) is not on screen in game, and the
117 // composite hides it unless ShowHiddenLayers is on.
125 // Opaque upper-tilemap pixels always cover the lower tilemap: it is only on
126 // the sub screen and the upper tilemap does not blend with it.
127 bool UpperTilemapCoversLower(int layer2_mode) const {
129 return layer2_mode == 0x06;
130 }
132 }
133 // Both tilemaps are on the main screen; the lower one (hardware BG1) wins
134 // ties at equal tile priority.
139 // Editing aid: draw layers the game hides.
140 void SetShowHiddenLayers(bool show) { show_hidden_layers_ = show; }
141 bool ShowHiddenLayers() const { return show_hidden_layers_; }
142 // True when `layer` is hidden only because the game hides it.
143 bool IsHiddenByGame(LayerType layer) const {
145 (layer == LayerType::BG2_Layout || layer == LayerType::BG2_Objects);
146 }
147
148 // Layer visibility
149 void SetLayerVisible(LayerType layer, bool visible) {
150 layer_visible_[static_cast<int>(layer)] = visible;
151 }
152
153 bool IsLayerVisible(LayerType layer) const {
154 return layer_visible_[static_cast<int>(layer)];
155 }
156
157 // Layer blend mode
159 layer_blend_mode_[static_cast<int>(layer)] = mode;
160 // Update alpha based on blend mode
161 switch (mode) {
163 layer_alpha_[static_cast<int>(layer)] = 255;
164 break;
166 layer_alpha_[static_cast<int>(layer)] = 180;
167 break;
169 layer_alpha_[static_cast<int>(layer)] = 220;
170 break;
172 layer_alpha_[static_cast<int>(layer)] = 120;
173 break;
175 layer_alpha_[static_cast<int>(layer)] = 0;
176 break;
177 }
178 }
179
181 return layer_blend_mode_[static_cast<int>(layer)];
182 }
183
184 uint8_t GetLayerAlpha(LayerType layer) const {
185 return layer_alpha_[static_cast<int>(layer)];
186 }
187
188 // Per-object translucency
189 void SetObjectTranslucency(size_t object_index, bool translucent,
190 uint8_t alpha = 128) {
191 // Find existing entry or add new one
192 for (auto& entry : object_translucency_) {
193 if (entry.object_index == object_index) {
194 entry.translucent = translucent;
195 entry.alpha = alpha;
196 return;
197 }
198 }
199 object_translucency_.push_back({object_index, translucent, alpha});
200 }
201
202 bool IsObjectTranslucent(size_t object_index) const {
203 for (const auto& entry : object_translucency_) {
204 if (entry.object_index == object_index) {
205 return entry.translucent;
206 }
207 }
208 return false;
209 }
210
211 uint8_t GetObjectAlpha(size_t object_index) const {
212 for (const auto& entry : object_translucency_) {
213 if (entry.object_index == object_index && entry.translucent) {
214 return entry.alpha;
215 }
216 }
217 return 255;
218 }
219
221
222 // Color math participation flag (from LayerMergeType.Layer2OnTop)
223 // NOTE: This does NOT affect draw order - BG1 is always above BG2.
224 // This flag controls whether BG2 participates in sub-screen color math
225 // effects like transparency and additive blending.
226 void SetBG2ColorMathEnabled(bool enabled) { bg2_on_top_ = enabled; }
227 bool IsBG2ColorMathEnabled() const { return bg2_on_top_; }
228
229 // Legacy aliases for compatibility
230 void SetBG2OnTop(bool on_top) { bg2_on_top_ = on_top; }
231 bool IsBG2OnTop() const { return bg2_on_top_; }
232
233 // Apply layer settings to room from LayerMergeType
234 // NOTE: This affects BLEND MODES and COLOR MATH, not draw order.
235 // SNES Mode 1 always renders BG1 above BG2 - this is hardware behavior.
236 // Layer visibility checkboxes remain independent of merge type.
237 void ApplyLayerMerging(const LayerMergeType& merge_type) {
238 // Store the current merge type for queries
239 current_merge_type_id_ = merge_type.ID;
240 layers_merged_ = (merge_type.ID != 0); // ID 0 = "Off" = not merged
241
242 // Set BG2 color math participation (does NOT change draw order)
244
245 // Apply blend mode based on merge type
246 // NOTE: Layer2Visible from ROM is informational only - user can still
247 // enable/disable layers via checkboxes. We only set blend modes here.
248 // Layer2Translucent = true means BG2 should use translucent blend
249 if (merge_type.Layer2Translucent) {
252 } else {
255 }
256
257 // BG1 blend mode depends on merge type
258 // DarkRoom (ID 0x08) should darken BG1 to simulate unlit room
259 if (merge_type.ID == 0x08) {
260 // Dark room - BG1 is dimmed (reduced brightness)
263 } else {
266 }
267 }
268
269 // Apply room effect-specific visual hints on top of merge settings.
270 // This keeps the editor preview closer to in-game behavior for effect-driven
271 // rooms (e.g. Moving Water) without changing layer visibility policy.
273 switch (effect) {
275 // Water rooms: BG2 shows water with translucent overlay on BG1.
276 // SNES uses HDMA-driven color math to blend water layer.
280 }
285 }
286 break;
287
289 // Conveyor belt rooms: BG2 scrolls independently.
290 // No blend change needed - BG2 is opaque floor tiles.
291 break;
292
294 // Dark rooms where lighting a torch reveals BG2 floor.
295 // BG1 is the dark overlay, BG2 is the revealed floor.
296 // In-game, HDMA window controls which scanlines show BG2.
297 // For editor preview: darken BG1, keep BG2 visible.
300 break;
301
303 // Lightning/flash effect (Ganon fight). No persistent blend change.
304 break;
305
307 // Ganon's room: special rendering with translucent BG2 for
308 // the Triforce floor pattern showing through.
312 }
313 break;
314
315 default:
316 break;
317 }
318 }
319
327 // Store visibility before applying
328 bool bg1_layout_vis = IsLayerVisible(LayerType::BG1_Layout);
329 bool bg1_objects_vis = IsLayerVisible(LayerType::BG1_Objects);
330 bool bg2_layout_vis = IsLayerVisible(LayerType::BG2_Layout);
331 bool bg2_objects_vis = IsLayerVisible(LayerType::BG2_Objects);
332
333 // Apply merge settings
334 ApplyLayerMerging(merge_type);
335
336 // Restore visibility
337 SetLayerVisible(LayerType::BG1_Layout, bg1_layout_vis);
338 SetLayerVisible(LayerType::BG1_Objects, bg1_objects_vis);
339 SetLayerVisible(LayerType::BG2_Layout, bg2_layout_vis);
340 SetLayerVisible(LayerType::BG2_Objects, bg2_objects_vis);
341 }
342
347 bool AreLayersMerged() const { return layers_merged_; }
348
353 uint8_t GetMergeTypeId() const { return current_merge_type_id_; }
354
355 // Signature for caching composited output. If any layer/blend/translucency
356 // state changes, callers should treat previously cached composite bitmaps as
357 // stale.
358 uint64_t CompositeStateSignature() const {
359 uint64_t sig = 1469598103934665603ull;
360 const auto mix = [&sig](uint64_t value) {
361 sig ^= value;
362 sig *= 1099511628211ull;
363 };
364
365 for (int i = 0; i < 4; ++i) {
366 mix(layer_visible_[i] ? 1u : 0u);
367 mix(static_cast<uint64_t>(layer_blend_mode_[i]));
368 mix(layer_alpha_[i]);
369 }
370
371 mix(bg2_on_top_ ? 1u : 0u);
372 mix(game_hides_lower_tilemap_ ? 1u : 0u);
373 mix(game_registers_applied_ ? 1u : 0u);
374 mix(lower_tilemap_on_main_ ? 1u : 0u);
375 mix(upper_tilemap_blended_ ? 1u : 0u);
376 mix(cgadsub_);
377 mix(show_hidden_layers_ ? 1u : 0u);
378 mix(layers_merged_ ? 1u : 0u);
380 mix(use_priority_compositing_ ? 1u : 0u);
381
382 mix(static_cast<uint64_t>(object_translucency_.size()));
383 for (const auto& entry : object_translucency_) {
384 mix(static_cast<uint64_t>(entry.object_index));
385 mix(entry.translucent ? 1u : 0u);
386 mix(entry.alpha);
387 }
388
389 return sig;
390 }
391
392 // ============================================================================
393 // Object Layer Assignment
394 // ============================================================================
395
411 int GetObjectLayerValue(int object_layer) const { return object_layer; }
412
413 // Legacy function - kept for API compatibility
414 // No longer affects visual order since SNES Mode 1 is fixed (BG1 > BG2)
415 int CalculateObjectPriority(int object_layer) const {
416 return object_layer * 10;
417 }
418
424 static bool IsObjectOnBG2(int object_layer) {
425 // Layer 1 objects render to BG2, layers 0 and 2 render to BG1
426 return object_layer == 1;
427 }
428
434 static LayerType GetObjectLayerType(int object_layer) {
435 return IsObjectOnBG2(object_layer) ? LayerType::BG2_Objects
437 }
438
452 std::array<LayerType, 4> GetDrawOrder() const {
453 // Standard SNES Mode 1 order: BG2 behind BG1
454 // bg2_on_top_ affects blend modes, not draw order
457 }
458
463 switch (layer) {
465 return room.bg1_buffer();
467 return room.object_bg1_buffer();
469 return room.bg2_buffer();
471 return room.object_bg2_buffer();
472 }
473 // Fallback (should never reach)
474 return room.bg1_buffer();
475 }
476
477 static const gfx::BackgroundBuffer& GetLayerBuffer(const Room& room,
478 LayerType layer) {
479 switch (layer) {
481 return room.bg1_buffer();
483 return room.object_bg1_buffer();
485 return room.bg2_buffer();
487 return room.object_bg2_buffer();
488 }
489 // Fallback (should never reach)
490 return room.bg1_buffer();
491 }
492
496 static const char* GetLayerName(LayerType layer) {
497 switch (layer) {
499 return "BG1 Layout";
501 return "BG1 Objects";
503 return "BG2 Layout";
505 return "BG2 Objects";
506 }
507 return "Unknown";
508 }
509
513 static const char* GetBlendModeName(LayerBlendMode mode) {
514 switch (mode) {
516 return "Normal";
518 return "Translucent";
520 return "Addition";
522 return "Dark";
524 return "Off";
525 }
526 return "Unknown";
527 }
528
538 void ApplySurfaceColorMod(SDL_Surface* surface) const {
539 if (!surface)
540 return;
541
542 if (current_merge_type_id_ == 0x08) {
543 // DarkRoom: 50% brightness
544 SDL_SetSurfaceColorMod(surface, 128, 128, 128);
545 } else {
546 // Normal: Full brightness
547 SDL_SetSurfaceColorMod(surface, 255, 255, 255);
548 }
549 }
550
551 // ============================================================================
552 // Layer Compositing
553 // ============================================================================
554
589 void CompositeToOutput(Room& room, gfx::Bitmap& output) const;
590
591 private:
602 static bool IsTransparent(uint8_t pixel) { return pixel == 255; }
603
604 std::array<bool, 4> layer_visible_;
605 std::array<LayerBlendMode, 4> layer_blend_mode_;
606 std::array<uint8_t, 4> layer_alpha_;
607 std::vector<ObjectTranslucency> object_translucency_;
608
609 // Color math participation flag (from ROM's Layer2OnTop)
610 // NOTE: Does NOT affect draw order - BG1 is always above BG2 per SNES Mode 1.
611 // This controls whether BG2 participates in sub-screen color math effects.
612 bool bg2_on_top_ = false;
617 uint8_t cgadsub_ = 0;
619
620 // Merge state tracking
621 bool layers_merged_ = false;
623
624 // When enabled, CompositeToOutput uses per-pixel priority buffers to emulate
625 // SNES Mode 1 ordering (BG2 pri=1 can appear above BG1 pri=0).
627};
628
629} // namespace zelda3
630} // namespace yaze
631
632#endif // YAZE_ZELDA3_DUNGEON_ROOM_LAYER_MANAGER_H
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:70
RoomLayerManager - Manages layer visibility and compositing.
uint8_t GetObjectAlpha(size_t object_index) const
bool IsHiddenByGame(LayerType layer) const
static bool IsObjectOnBG2(int object_layer)
Check if an object on a given layer should render to BG2.
void SetPriorityCompositing(bool enabled)
static const char * GetBlendModeName(LayerBlendMode mode)
Get blend mode name.
std::array< LayerBlendMode, 4 > layer_blend_mode_
static gfx::BackgroundBuffer & GetLayerBuffer(Room &room, LayerType layer)
Get the bitmap buffer for a layer type.
static const char * GetLayerName(LayerType layer)
Get human-readable name for layer type.
void SetBG2ColorMathEnabled(bool enabled)
static const gfx::BackgroundBuffer & GetLayerBuffer(const Room &room, LayerType layer)
static bool IsTransparent(uint8_t pixel)
Check if a pixel index represents transparency.
int CalculateObjectPriority(int object_layer) const
void ApplyGameLayerRegisters(const RoomLayerRegisters &registers)
void SetLayerBlendMode(LayerType layer, LayerBlendMode mode)
void ApplyLayerMerging(const LayerMergeType &merge_type)
void SetLayerVisible(LayerType layer, bool visible)
void ApplySurfaceColorMod(SDL_Surface *surface) const
Apply surface color modulation for DarkRoom effect.
std::array< LayerType, 4 > GetDrawOrder() const
Get the draw order for layers.
bool IsLayerVisible(LayerType layer) const
void SetObjectTranslucency(size_t object_index, bool translucent, uint8_t alpha=128)
std::array< uint8_t, 4 > layer_alpha_
LayerBlendMode GetLayerBlendMode(LayerType layer) const
static LayerType GetObjectLayerType(int object_layer)
Get the appropriate background layer type for an object.
std::array< bool, 4 > layer_visible_
bool IsObjectTranslucent(size_t object_index) const
bool AreLayersMerged() const
Check if layers are currently merged.
uint8_t GetMergeTypeId() const
Get the current merge type ID.
void CompositeToOutput(Room &room, gfx::Bitmap &output) const
Composite all visible layers into a single output bitmap.
void ApplyLayerMergingPreserveVisibility(const LayerMergeType &merge_type)
Apply layer merge settings without changing visibility.
uint8_t GetLayerAlpha(LayerType layer) const
int GetObjectLayerValue(int object_layer) const
Get object layer value for buffer assignment.
void ApplyRoomEffect(EffectKey effect)
bool UpperTilemapCoversLower(int layer2_mode) const
std::vector< ObjectTranslucency > object_translucency_
auto & object_bg2_buffer()
Definition room.h:1151
auto & bg1_buffer()
Definition room.h:1145
auto & object_bg1_buffer()
Definition room.h:1149
auto & bg2_buffer()
Definition room.h:1146
LayerBlendMode
Layer blend modes for compositing.
@ Ganon_Room
Definition room.h:134
@ Moving_Floor
Definition room.h:129
@ Red_Flashes
Definition room.h:132
@ Moving_Water
Definition room.h:130
@ Torch_Show_Floor
Definition room.h:133
LayerType
Layer types for the 4-way visibility system.
Object metadata for tracking layer assignment.
Per-object translucency settings.