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;
94 use_priority_compositing_ = true; // Default to accurate SNES behavior
95 }
96
97 // Priority compositing control
98 // When enabled (default): Uses SNES Mode 1 per-tile priority for Z-ordering
99 // When disabled: Simple back-to-front layer order (BG2 behind, BG1 in front)
100 void SetPriorityCompositing(bool enabled) { use_priority_compositing_ = enabled; }
102
103 // Layer visibility
104 void SetLayerVisible(LayerType layer, bool visible) {
105 layer_visible_[static_cast<int>(layer)] = visible;
106 }
107
108 bool IsLayerVisible(LayerType layer) const {
109 return layer_visible_[static_cast<int>(layer)];
110 }
111
112 // Layer blend mode
114 layer_blend_mode_[static_cast<int>(layer)] = mode;
115 // Update alpha based on blend mode
116 switch (mode) {
118 layer_alpha_[static_cast<int>(layer)] = 255;
119 break;
121 layer_alpha_[static_cast<int>(layer)] = 180;
122 break;
124 layer_alpha_[static_cast<int>(layer)] = 220;
125 break;
127 layer_alpha_[static_cast<int>(layer)] = 120;
128 break;
130 layer_alpha_[static_cast<int>(layer)] = 0;
131 break;
132 }
133 }
134
136 return layer_blend_mode_[static_cast<int>(layer)];
137 }
138
139 uint8_t GetLayerAlpha(LayerType layer) const {
140 return layer_alpha_[static_cast<int>(layer)];
141 }
142
143 // Per-object translucency
144 void SetObjectTranslucency(size_t object_index, bool translucent,
145 uint8_t alpha = 128) {
146 // Find existing entry or add new one
147 for (auto& entry : object_translucency_) {
148 if (entry.object_index == object_index) {
149 entry.translucent = translucent;
150 entry.alpha = alpha;
151 return;
152 }
153 }
154 object_translucency_.push_back({object_index, translucent, alpha});
155 }
156
157 bool IsObjectTranslucent(size_t object_index) const {
158 for (const auto& entry : object_translucency_) {
159 if (entry.object_index == object_index) {
160 return entry.translucent;
161 }
162 }
163 return false;
164 }
165
166 uint8_t GetObjectAlpha(size_t object_index) const {
167 for (const auto& entry : object_translucency_) {
168 if (entry.object_index == object_index && entry.translucent) {
169 return entry.alpha;
170 }
171 }
172 return 255;
173 }
174
176
177 // Color math participation flag (from LayerMergeType.Layer2OnTop)
178 // NOTE: This does NOT affect draw order - BG1 is always above BG2.
179 // This flag controls whether BG2 participates in sub-screen color math
180 // effects like transparency and additive blending.
181 void SetBG2ColorMathEnabled(bool enabled) { bg2_on_top_ = enabled; }
182 bool IsBG2ColorMathEnabled() const { return bg2_on_top_; }
183
184 // Legacy aliases for compatibility
185 void SetBG2OnTop(bool on_top) { bg2_on_top_ = on_top; }
186 bool IsBG2OnTop() const { return bg2_on_top_; }
187
188 // Apply layer settings to room from LayerMergeType
189 // NOTE: This affects BLEND MODES and COLOR MATH, not draw order.
190 // SNES Mode 1 always renders BG1 above BG2 - this is hardware behavior.
191 // Layer visibility checkboxes remain independent of merge type.
192 void ApplyLayerMerging(const LayerMergeType& merge_type) {
193 // Store the current merge type for queries
194 current_merge_type_id_ = merge_type.ID;
195 layers_merged_ = (merge_type.ID != 0); // ID 0 = "Off" = not merged
196
197 // Set BG2 color math participation (does NOT change draw order)
199
200 // Apply blend mode based on merge type
201 // NOTE: Layer2Visible from ROM is informational only - user can still
202 // enable/disable layers via checkboxes. We only set blend modes here.
203 // Layer2Translucent = true means BG2 should use translucent blend
204 if (merge_type.Layer2Translucent) {
207 } else {
210 }
211
212 // BG1 blend mode depends on merge type
213 // DarkRoom (ID 0x08) should darken BG1 to simulate unlit room
214 if (merge_type.ID == 0x08) {
215 // Dark room - BG1 is dimmed (reduced brightness)
218 } else {
221 }
222 }
223
224 // Apply room effect-specific visual hints on top of merge settings.
225 // This keeps the editor preview closer to in-game behavior for effect-driven
226 // rooms (e.g. Moving Water) without changing layer visibility policy.
228 switch (effect) {
230 // Water rooms: BG2 shows water with translucent overlay on BG1.
231 // SNES uses HDMA-driven color math to blend water layer.
236 }
241 }
242 break;
243
245 // Conveyor belt rooms: BG2 scrolls independently.
246 // No blend change needed - BG2 is opaque floor tiles.
247 break;
248
250 // Dark rooms where lighting a torch reveals BG2 floor.
251 // BG1 is the dark overlay, BG2 is the revealed floor.
252 // In-game, HDMA window controls which scanlines show BG2.
253 // For editor preview: darken BG1, keep BG2 visible.
256 break;
257
259 // Lightning/flash effect (Ganon fight). No persistent blend change.
260 break;
261
263 // Ganon's room: special rendering with translucent BG2 for
264 // the Triforce floor pattern showing through.
269 }
270 break;
271
272 default:
273 break;
274 }
275 }
276
284 // Store visibility before applying
285 bool bg1_layout_vis = IsLayerVisible(LayerType::BG1_Layout);
286 bool bg1_objects_vis = IsLayerVisible(LayerType::BG1_Objects);
287 bool bg2_layout_vis = IsLayerVisible(LayerType::BG2_Layout);
288 bool bg2_objects_vis = IsLayerVisible(LayerType::BG2_Objects);
289
290 // Apply merge settings
291 ApplyLayerMerging(merge_type);
292
293 // Restore visibility
294 SetLayerVisible(LayerType::BG1_Layout, bg1_layout_vis);
295 SetLayerVisible(LayerType::BG1_Objects, bg1_objects_vis);
296 SetLayerVisible(LayerType::BG2_Layout, bg2_layout_vis);
297 SetLayerVisible(LayerType::BG2_Objects, bg2_objects_vis);
298 }
299
304 bool AreLayersMerged() const { return layers_merged_; }
305
310 uint8_t GetMergeTypeId() const { return current_merge_type_id_; }
311
312 // Signature for caching composited output. If any layer/blend/translucency
313 // state changes, callers should treat previously cached composite bitmaps as
314 // stale.
315 uint64_t CompositeStateSignature() const {
316 uint64_t sig = 1469598103934665603ull;
317 const auto mix = [&sig](uint64_t value) {
318 sig ^= value;
319 sig *= 1099511628211ull;
320 };
321
322 for (int i = 0; i < 4; ++i) {
323 mix(layer_visible_[i] ? 1u : 0u);
324 mix(static_cast<uint64_t>(layer_blend_mode_[i]));
325 mix(layer_alpha_[i]);
326 }
327
328 mix(bg2_on_top_ ? 1u : 0u);
329 mix(layers_merged_ ? 1u : 0u);
331 mix(use_priority_compositing_ ? 1u : 0u);
332
333 mix(static_cast<uint64_t>(object_translucency_.size()));
334 for (const auto& entry : object_translucency_) {
335 mix(static_cast<uint64_t>(entry.object_index));
336 mix(entry.translucent ? 1u : 0u);
337 mix(entry.alpha);
338 }
339
340 return sig;
341 }
342
343 // ============================================================================
344 // Object Layer Assignment
345 // ============================================================================
346
362 int GetObjectLayerValue(int object_layer) const {
363 return object_layer;
364 }
365
366 // Legacy function - kept for API compatibility
367 // No longer affects visual order since SNES Mode 1 is fixed (BG1 > BG2)
368 int CalculateObjectPriority(int object_layer) const {
369 return object_layer * 10;
370 }
371
377 static bool IsObjectOnBG2(int object_layer) {
378 // Layer 1 objects render to BG2, layers 0 and 2 render to BG1
379 return object_layer == 1;
380 }
381
387 static LayerType GetObjectLayerType(int object_layer) {
388 return IsObjectOnBG2(object_layer) ? LayerType::BG2_Objects
390 }
391
405 std::array<LayerType, 4> GetDrawOrder() const {
406 // Standard SNES Mode 1 order: BG2 behind BG1
407 // bg2_on_top_ affects blend modes, not draw order
410 }
411
416 switch (layer) {
418 return room.bg1_buffer();
420 return room.object_bg1_buffer();
422 return room.bg2_buffer();
424 return room.object_bg2_buffer();
425 }
426 // Fallback (should never reach)
427 return room.bg1_buffer();
428 }
429
430 static const gfx::BackgroundBuffer& GetLayerBuffer(const Room& room,
431 LayerType layer) {
432 switch (layer) {
434 return room.bg1_buffer();
436 return room.object_bg1_buffer();
438 return room.bg2_buffer();
440 return room.object_bg2_buffer();
441 }
442 // Fallback (should never reach)
443 return room.bg1_buffer();
444 }
445
449 static const char* GetLayerName(LayerType layer) {
450 switch (layer) {
452 return "BG1 Layout";
454 return "BG1 Objects";
456 return "BG2 Layout";
458 return "BG2 Objects";
459 }
460 return "Unknown";
461 }
462
466 static const char* GetBlendModeName(LayerBlendMode mode) {
467 switch (mode) {
469 return "Normal";
471 return "Translucent";
473 return "Addition";
475 return "Dark";
477 return "Off";
478 }
479 return "Unknown";
480 }
481
491 void ApplySurfaceColorMod(SDL_Surface* surface) const {
492 if (!surface) return;
493
494 if (current_merge_type_id_ == 0x08) {
495 // DarkRoom: 50% brightness
496 SDL_SetSurfaceColorMod(surface, 128, 128, 128);
497 } else {
498 // Normal: Full brightness
499 SDL_SetSurfaceColorMod(surface, 255, 255, 255);
500 }
501 }
502
503 // ============================================================================
504 // Layer Compositing
505 // ============================================================================
506
539 void CompositeToOutput(Room& room, gfx::Bitmap& output) const;
540
541 private:
552 static bool IsTransparent(uint8_t pixel) {
553 return pixel == 255;
554 }
555
556 std::array<bool, 4> layer_visible_;
557 std::array<LayerBlendMode, 4> layer_blend_mode_;
558 std::array<uint8_t, 4> layer_alpha_;
559 std::vector<ObjectTranslucency> object_translucency_;
560
561 // Color math participation flag (from ROM's Layer2OnTop)
562 // NOTE: Does NOT affect draw order - BG1 is always above BG2 per SNES Mode 1.
563 // This controls whether BG2 participates in sub-screen color math effects.
564 bool bg2_on_top_ = false;
565
566 // Merge state tracking
567 bool layers_merged_ = false;
569
570 // When enabled, CompositeToOutput uses per-pixel priority buffers to emulate
571 // SNES Mode 1 ordering (BG2 pri=1 can appear above BG1 pri=0).
573};
574
575} // namespace zelda3
576} // namespace yaze
577
578#endif // YAZE_ZELDA3_DUNGEON_ROOM_LAYER_MANAGER_H
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:67
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:675
auto & bg1_buffer()
Definition room.h:669
auto & object_bg1_buffer()
Definition room.h:673
auto & bg2_buffer()
Definition room.h:670
LayerBlendMode
Layer blend modes for compositing.
@ Ganon_Room
Definition room.h:99
@ Moving_Floor
Definition room.h:94
@ Red_Flashes
Definition room.h:97
@ Moving_Water
Definition room.h:95
@ Torch_Show_Floor
Definition room.h:98
LayerType
Layer types for the 4-way visibility system.
Object metadata for tracking layer assignment.
Per-object translucency settings.