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;
92 layers_merged_ = false;
95 true; // Default to SNES-informed priority order
96 }
97
98 // Priority compositing control
99 // When enabled (default): Uses SNES Mode 1 per-tile priority for Z-ordering
100 // When disabled: Simple back-to-front layer order (BG2 behind, BG1 in front)
101 void SetPriorityCompositing(bool enabled) {
103 }
107
108 // Layer visibility
109 void SetLayerVisible(LayerType layer, bool visible) {
110 layer_visible_[static_cast<int>(layer)] = visible;
111 }
112
113 bool IsLayerVisible(LayerType layer) const {
114 return layer_visible_[static_cast<int>(layer)];
115 }
116
117 // Layer blend mode
119 layer_blend_mode_[static_cast<int>(layer)] = mode;
120 // Update alpha based on blend mode
121 switch (mode) {
123 layer_alpha_[static_cast<int>(layer)] = 255;
124 break;
126 layer_alpha_[static_cast<int>(layer)] = 180;
127 break;
129 layer_alpha_[static_cast<int>(layer)] = 220;
130 break;
132 layer_alpha_[static_cast<int>(layer)] = 120;
133 break;
135 layer_alpha_[static_cast<int>(layer)] = 0;
136 break;
137 }
138 }
139
141 return layer_blend_mode_[static_cast<int>(layer)];
142 }
143
144 uint8_t GetLayerAlpha(LayerType layer) const {
145 return layer_alpha_[static_cast<int>(layer)];
146 }
147
148 // Per-object translucency
149 void SetObjectTranslucency(size_t object_index, bool translucent,
150 uint8_t alpha = 128) {
151 // Find existing entry or add new one
152 for (auto& entry : object_translucency_) {
153 if (entry.object_index == object_index) {
154 entry.translucent = translucent;
155 entry.alpha = alpha;
156 return;
157 }
158 }
159 object_translucency_.push_back({object_index, translucent, alpha});
160 }
161
162 bool IsObjectTranslucent(size_t object_index) const {
163 for (const auto& entry : object_translucency_) {
164 if (entry.object_index == object_index) {
165 return entry.translucent;
166 }
167 }
168 return false;
169 }
170
171 uint8_t GetObjectAlpha(size_t object_index) const {
172 for (const auto& entry : object_translucency_) {
173 if (entry.object_index == object_index && entry.translucent) {
174 return entry.alpha;
175 }
176 }
177 return 255;
178 }
179
181
182 // Color math participation flag (from LayerMergeType.Layer2OnTop)
183 // NOTE: This does NOT affect draw order - BG1 is always above BG2.
184 // This flag controls whether BG2 participates in sub-screen color math
185 // effects like transparency and additive blending.
186 void SetBG2ColorMathEnabled(bool enabled) { bg2_on_top_ = enabled; }
187 bool IsBG2ColorMathEnabled() const { return bg2_on_top_; }
188
189 // Legacy aliases for compatibility
190 void SetBG2OnTop(bool on_top) { bg2_on_top_ = on_top; }
191 bool IsBG2OnTop() const { return bg2_on_top_; }
192
193 // Apply layer settings to room from LayerMergeType
194 // NOTE: This affects BLEND MODES and COLOR MATH, not draw order.
195 // SNES Mode 1 always renders BG1 above BG2 - this is hardware behavior.
196 // Layer visibility checkboxes remain independent of merge type.
197 void ApplyLayerMerging(const LayerMergeType& merge_type) {
198 // Store the current merge type for queries
199 current_merge_type_id_ = merge_type.ID;
200 layers_merged_ = (merge_type.ID != 0); // ID 0 = "Off" = not merged
201
202 // Set BG2 color math participation (does NOT change draw order)
204
205 // Apply blend mode based on merge type
206 // NOTE: Layer2Visible from ROM is informational only - user can still
207 // enable/disable layers via checkboxes. We only set blend modes here.
208 // Layer2Translucent = true means BG2 should use translucent blend
209 if (merge_type.Layer2Translucent) {
212 } else {
215 }
216
217 // BG1 blend mode depends on merge type
218 // DarkRoom (ID 0x08) should darken BG1 to simulate unlit room
219 if (merge_type.ID == 0x08) {
220 // Dark room - BG1 is dimmed (reduced brightness)
223 } else {
226 }
227 }
228
229 // Apply room effect-specific visual hints on top of merge settings.
230 // This keeps the editor preview closer to in-game behavior for effect-driven
231 // rooms (e.g. Moving Water) without changing layer visibility policy.
233 switch (effect) {
235 // Water rooms: BG2 shows water with translucent overlay on BG1.
236 // SNES uses HDMA-driven color math to blend water layer.
240 }
245 }
246 break;
247
249 // Conveyor belt rooms: BG2 scrolls independently.
250 // No blend change needed - BG2 is opaque floor tiles.
251 break;
252
254 // Dark rooms where lighting a torch reveals BG2 floor.
255 // BG1 is the dark overlay, BG2 is the revealed floor.
256 // In-game, HDMA window controls which scanlines show BG2.
257 // For editor preview: darken BG1, keep BG2 visible.
260 break;
261
263 // Lightning/flash effect (Ganon fight). No persistent blend change.
264 break;
265
267 // Ganon's room: special rendering with translucent BG2 for
268 // the Triforce floor pattern showing through.
272 }
273 break;
274
275 default:
276 break;
277 }
278 }
279
287 // Store visibility before applying
288 bool bg1_layout_vis = IsLayerVisible(LayerType::BG1_Layout);
289 bool bg1_objects_vis = IsLayerVisible(LayerType::BG1_Objects);
290 bool bg2_layout_vis = IsLayerVisible(LayerType::BG2_Layout);
291 bool bg2_objects_vis = IsLayerVisible(LayerType::BG2_Objects);
292
293 // Apply merge settings
294 ApplyLayerMerging(merge_type);
295
296 // Restore visibility
297 SetLayerVisible(LayerType::BG1_Layout, bg1_layout_vis);
298 SetLayerVisible(LayerType::BG1_Objects, bg1_objects_vis);
299 SetLayerVisible(LayerType::BG2_Layout, bg2_layout_vis);
300 SetLayerVisible(LayerType::BG2_Objects, bg2_objects_vis);
301 }
302
307 bool AreLayersMerged() const { return layers_merged_; }
308
313 uint8_t GetMergeTypeId() const { return current_merge_type_id_; }
314
315 // Signature for caching composited output. If any layer/blend/translucency
316 // state changes, callers should treat previously cached composite bitmaps as
317 // stale.
318 uint64_t CompositeStateSignature() const {
319 uint64_t sig = 1469598103934665603ull;
320 const auto mix = [&sig](uint64_t value) {
321 sig ^= value;
322 sig *= 1099511628211ull;
323 };
324
325 for (int i = 0; i < 4; ++i) {
326 mix(layer_visible_[i] ? 1u : 0u);
327 mix(static_cast<uint64_t>(layer_blend_mode_[i]));
328 mix(layer_alpha_[i]);
329 }
330
331 mix(bg2_on_top_ ? 1u : 0u);
332 mix(layers_merged_ ? 1u : 0u);
334 mix(use_priority_compositing_ ? 1u : 0u);
335
336 mix(static_cast<uint64_t>(object_translucency_.size()));
337 for (const auto& entry : object_translucency_) {
338 mix(static_cast<uint64_t>(entry.object_index));
339 mix(entry.translucent ? 1u : 0u);
340 mix(entry.alpha);
341 }
342
343 return sig;
344 }
345
346 // ============================================================================
347 // Object Layer Assignment
348 // ============================================================================
349
365 int GetObjectLayerValue(int object_layer) const { return object_layer; }
366
367 // Legacy function - kept for API compatibility
368 // No longer affects visual order since SNES Mode 1 is fixed (BG1 > BG2)
369 int CalculateObjectPriority(int object_layer) const {
370 return object_layer * 10;
371 }
372
378 static bool IsObjectOnBG2(int object_layer) {
379 // Layer 1 objects render to BG2, layers 0 and 2 render to BG1
380 return object_layer == 1;
381 }
382
388 static LayerType GetObjectLayerType(int object_layer) {
389 return IsObjectOnBG2(object_layer) ? LayerType::BG2_Objects
391 }
392
406 std::array<LayerType, 4> GetDrawOrder() const {
407 // Standard SNES Mode 1 order: BG2 behind BG1
408 // bg2_on_top_ affects blend modes, not draw order
411 }
412
417 switch (layer) {
419 return room.bg1_buffer();
421 return room.object_bg1_buffer();
423 return room.bg2_buffer();
425 return room.object_bg2_buffer();
426 }
427 // Fallback (should never reach)
428 return room.bg1_buffer();
429 }
430
431 static const gfx::BackgroundBuffer& GetLayerBuffer(const Room& room,
432 LayerType layer) {
433 switch (layer) {
435 return room.bg1_buffer();
437 return room.object_bg1_buffer();
439 return room.bg2_buffer();
441 return room.object_bg2_buffer();
442 }
443 // Fallback (should never reach)
444 return room.bg1_buffer();
445 }
446
450 static const char* GetLayerName(LayerType layer) {
451 switch (layer) {
453 return "BG1 Layout";
455 return "BG1 Objects";
457 return "BG2 Layout";
459 return "BG2 Objects";
460 }
461 return "Unknown";
462 }
463
467 static const char* GetBlendModeName(LayerBlendMode mode) {
468 switch (mode) {
470 return "Normal";
472 return "Translucent";
474 return "Addition";
476 return "Dark";
478 return "Off";
479 }
480 return "Unknown";
481 }
482
492 void ApplySurfaceColorMod(SDL_Surface* surface) const {
493 if (!surface)
494 return;
495
496 if (current_merge_type_id_ == 0x08) {
497 // DarkRoom: 50% brightness
498 SDL_SetSurfaceColorMod(surface, 128, 128, 128);
499 } else {
500 // Normal: Full brightness
501 SDL_SetSurfaceColorMod(surface, 255, 255, 255);
502 }
503 }
504
505 // ============================================================================
506 // Layer Compositing
507 // ============================================================================
508
543 void CompositeToOutput(Room& room, gfx::Bitmap& output) const;
544
545 private:
556 static bool IsTransparent(uint8_t pixel) { return pixel == 255; }
557
558 std::array<bool, 4> layer_visible_;
559 std::array<LayerBlendMode, 4> layer_blend_mode_;
560 std::array<uint8_t, 4> layer_alpha_;
561 std::vector<ObjectTranslucency> object_translucency_;
562
563 // Color math participation flag (from ROM's Layer2OnTop)
564 // NOTE: Does NOT affect draw order - BG1 is always above BG2 per SNES Mode 1.
565 // This controls whether BG2 participates in sub-screen color math effects.
566 bool bg2_on_top_ = false;
567
568 // Merge state tracking
569 bool layers_merged_ = false;
571
572 // When enabled, CompositeToOutput uses per-pixel priority buffers to emulate
573 // SNES Mode 1 ordering (BG2 pri=1 can appear above BG1 pri=0).
575};
576
577} // namespace zelda3
578} // namespace yaze
579
580#endif // YAZE_ZELDA3_DUNGEON_ROOM_LAYER_MANAGER_H
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:69
RoomLayerManager - Manages layer visibility and compositing.
uint8_t GetObjectAlpha(size_t object_index) 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 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)
std::vector< ObjectTranslucency > object_translucency_
auto & object_bg2_buffer()
Definition room.h:1045
auto & bg1_buffer()
Definition room.h:1039
auto & object_bg1_buffer()
Definition room.h:1043
auto & bg2_buffer()
Definition room.h:1040
LayerBlendMode
Layer blend modes for compositing.
@ Ganon_Room
Definition room.h:125
@ Moving_Floor
Definition room.h:120
@ Red_Flashes
Definition room.h:123
@ Moving_Water
Definition room.h:121
@ Torch_Show_Floor
Definition room.h:124
LayerType
Layer types for the 4-way visibility system.
Object metadata for tracking layer assignment.
Per-object translucency settings.