yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
room_layer_manager.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <vector>
5
6#include "SDL.h"
8#include "util/log.h"
9
10namespace yaze {
11namespace zelda3 {
12
13namespace {
14
15// Helper to copy SDL palette from source surface to destination bitmap
16// Uses vector extraction + SetPalette for reliable palette application
17void ApplySDLPaletteToBitmap(SDL_Surface* src_surface, gfx::Bitmap& dst_bitmap) {
18 if (!src_surface || !src_surface->format) return;
19
20 SDL_Palette* src_pal = src_surface->format->palette;
21 if (!src_pal || src_pal->ncolors == 0) return;
22
23 // Extract palette colors into a vector
24 std::vector<SDL_Color> colors(256);
25 int colors_to_copy = std::min(src_pal->ncolors, 256);
26 for (int i = 0; i < colors_to_copy; ++i) {
27 colors[i] = src_pal->colors[i];
28 }
29
30 // Fill remaining with transparent black (prevents undefined colors)
31 for (int i = colors_to_copy; i < 256; ++i) {
32 colors[i] = {0, 0, 0, 0};
33 }
34
35 // IMPORTANT: Do NOT force palette[0] to transparent!
36 // In the dungeon system, pixel value 0 is never drawn (skipped in IsTransparent),
37 // but palette[0] contains the FIRST actual color. Pixel value 1 maps to palette[0].
38 // Only index 255 needs to be transparent (fill color for empty areas).
39 colors[255] = {0, 0, 0, 0};
40
41 // Apply palette to destination bitmap using the reliable method
42 dst_bitmap.SetPalette(colors);
43}
44
45} // namespace
46
48 gfx::Bitmap& output) const {
49 constexpr int kWidth = 512;
50 constexpr int kHeight = 512;
51 constexpr int kPixelCount = kWidth * kHeight;
52
53 // Log layer visibility and blend modes (once per room)
54 static int last_room_id = -1;
55 if (room.id() != last_room_id) {
56 last_room_id = room.id();
57 LOG_DEBUG("LayerManager", "Room %03X: BG1_Layout(vis=%d,blend=%d) "
58 "BG1_Objects(vis=%d,blend=%d) BG2_Layout(vis=%d,blend=%d) "
59 "BG2_Objects(vis=%d,blend=%d) MergeType=%d",
60 room.id(),
70 }
71
72 // Ensure output bitmap is properly sized
73 if (output.width() != kWidth || output.height() != kHeight) {
74 output.Create(kWidth, kHeight, 8,
75 std::vector<uint8_t>(kPixelCount, 255));
76 } else {
77 // Clear to transparent (255)
78 output.Fill(255);
79 }
80
81 // Track if we've copied the palette yet
82 bool palette_copied = false;
83
84 // Get all 4 layer buffers
85 auto& bg1_layout = GetLayerBuffer(room, LayerType::BG1_Layout);
86 auto& bg1_objects = GetLayerBuffer(room, LayerType::BG1_Objects);
87 auto& bg2_layout = GetLayerBuffer(room, LayerType::BG2_Layout);
88 auto& bg2_objects = GetLayerBuffer(room, LayerType::BG2_Objects);
89
90 // Copy palette from first available visible layer
91 auto CopyPaletteIfNeeded = [&](const gfx::Bitmap& src_bitmap) {
92 if (!palette_copied && src_bitmap.surface()) {
93 ApplySDLPaletteToBitmap(src_bitmap.surface(), output);
94 palette_copied = true;
95 }
96 };
97
98 // Helper to composite a single layer with blend mode support
99 // Based on SNES ASM analysis: BG2 is drawn first, then BG1 overwrites on top
100 // This matches the SNES tilemap buffer architecture:
101 // $7E4000 = Lower layer (BG2) - drawn first
102 // $7E2000 = Upper layer (BG1) - drawn on top
103 //
104 // Blend Modes (from LayerMergeType):
105 // - Normal: Opaque pixels overwrite destination (standard)
106 // - Translucent: 50% alpha blend with destination
107 // - Addition: Additive color blending (SNES color math)
108 // - Dark: Darkened blend (reduced brightness)
109 // - Off: Layer is hidden
110 //
111 // IMPORTANT: Transparent pixels (255) in BG1 layers ALWAYS reveal BG2 beneath.
112 // This is how pits work: mask objects write 255 to BG1, allowing BG2 to show.
113 auto CompositeLayer = [&](gfx::BackgroundBuffer& buffer, LayerType layer_type) {
114 if (!IsLayerVisible(layer_type)) return;
115
116 const auto& src_bitmap = buffer.bitmap();
117 if (!src_bitmap.is_active() || src_bitmap.width() == 0) return;
118
119 LayerBlendMode blend_mode = GetLayerBlendMode(layer_type);
120 if (blend_mode == LayerBlendMode::Off) return;
121
122 CopyPaletteIfNeeded(src_bitmap);
123
124 const auto& src_data = src_bitmap.data();
125 auto& dst_data = output.mutable_data();
126
127 // Get layer alpha for translucent/dark blending
128 uint8_t layer_alpha = GetLayerAlpha(layer_type);
129
130 for (int idx = 0; idx < kPixelCount; ++idx) {
131 uint8_t src_pixel = src_data[idx];
132
133 // Skip transparent pixels (255 = fill color for undrawn areas)
134 // This is CRITICAL for pits: transparent BG1 pixels reveal BG2 beneath
135 if (IsTransparent(src_pixel)) continue;
136
137 // Apply blend mode
138 switch (blend_mode) {
140 // Standard opaque overwrite
141 dst_data[idx] = src_pixel;
142 break;
143
145 // 50% alpha blend: only overwrite if destination is transparent,
146 // otherwise blend colors using palette index averaging (simplified)
147 // For indexed color mode, we can't truly blend - use alpha threshold
148 if (IsTransparent(dst_data[idx]) || layer_alpha > 180) {
149 dst_data[idx] = src_pixel;
150 }
151 // If layer_alpha <= 180, destination shows through (simplified blend)
152 break;
153
155 // Additive blending: in indexed mode, just use source if visible
156 // True additive would need RGB values from palette
157 if (IsTransparent(dst_data[idx])) {
158 dst_data[idx] = src_pixel;
159 }
160 // Non-transparent dest + source: in indexed mode, just overwrite
161 // (True additive blend would require palette lookup and RGB math)
162 else {
163 dst_data[idx] = src_pixel;
164 }
165 break;
166
168 // Darkened blend: overwrite but surface will be color-modulated later
169 dst_data[idx] = src_pixel;
170 break;
171
173 // Layer hidden - should not reach here due to early return
174 break;
175 }
176 }
177 };
178
179 // Process all layers in back-to-front order (matching SNES hardware)
180 // BG2 is the lower/background layer, BG1 is the upper/foreground layer
181 // This is the standard SNES Mode 1 rendering order
182
183 // 1. BG2 Layout (floor tiles, background)
184 CompositeLayer(bg2_layout, LayerType::BG2_Layout);
185
186 // 2. BG2 Objects (objects drawn to background layer)
187 CompositeLayer(bg2_objects, LayerType::BG2_Objects);
188
189 // 3. BG1 Layout (main room structure) - overwrites BG2
190 CompositeLayer(bg1_layout, LayerType::BG1_Layout);
191
192 // 4. BG1 Objects (objects drawn to foreground layer) - overwrites all
193 CompositeLayer(bg1_objects, LayerType::BG1_Objects);
194
195 // If no palette was copied from layers, try to get it from bg1_buffer directly
196 if (!palette_copied) {
197 const auto& bg1_bitmap = room.bg1_buffer().bitmap();
198 if (bg1_bitmap.surface()) {
199 ApplySDLPaletteToBitmap(bg1_bitmap.surface(), output);
200 }
201 }
202
203 // Sync pixel data to SDL surface for texture creation
204 output.UpdateSurfacePixels();
205
206 // Set up transparency and effects for the composite output
207 if (output.surface()) {
208 // IMPORTANT: Use the same transparency setup as room.cc's set_dungeon_palette
209 // Color key on index 255 (unused in 90-color dungeon palette)
210 SDL_SetColorKey(output.surface(), SDL_TRUE, 255);
211 SDL_SetSurfaceBlendMode(output.surface(), SDL_BLENDMODE_BLEND);
212
213 // Apply DarkRoom effect if merge type is 0x08
214 // This simulates the SNES master brightness reduction for unlit rooms
215 if (current_merge_type_id_ == 0x08) {
216 // Apply color modulation to darken the output (50% brightness)
217 SDL_SetSurfaceColorMod(output.surface(), 128, 128, 128);
218 } else {
219 // Reset to full brightness for non-dark rooms
220 SDL_SetSurfaceColorMod(output.surface(), 255, 255, 255);
221 }
222 }
223
224 // Mark output as modified for texture update
225 output.set_modified(true);
226}
227
228} // namespace zelda3
229} // namespace yaze
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:67
void Create(int width, int height, int depth, std::span< uint8_t > data)
Create a bitmap with the given dimensions and data.
Definition bitmap.cc:201
void UpdateSurfacePixels()
Update SDL surface with current pixel data from data_ vector Call this after modifying pixel data via...
Definition bitmap.cc:369
void set_modified(bool modified)
Definition bitmap.h:388
int height() const
Definition bitmap.h:374
void Fill(uint8_t value)
Fill the bitmap with a specific value.
Definition bitmap.h:143
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap using SNES palette format.
Definition bitmap.cc:384
int width() const
Definition bitmap.h:373
std::vector< uint8_t > & mutable_data()
Definition bitmap.h:378
SDL_Surface * surface() const
Definition bitmap.h:379
static gfx::BackgroundBuffer & GetLayerBuffer(Room &room, LayerType layer)
Get the bitmap buffer for a layer type.
static bool IsTransparent(uint8_t pixel)
Check if a pixel index represents transparency.
bool IsLayerVisible(LayerType layer) const
LayerBlendMode GetLayerBlendMode(LayerType layer) const
void CompositeToOutput(Room &room, gfx::Bitmap &output) const
Composite all visible layers into a single output bitmap.
uint8_t GetLayerAlpha(LayerType layer) const
auto & bg1_buffer()
Definition room.h:542
int id() const
Definition room.h:488
#define LOG_DEBUG(category, format,...)
Definition log.h:103
void ApplySDLPaletteToBitmap(SDL_Surface *src_surface, gfx::Bitmap &dst_bitmap)
LayerBlendMode
Layer blend modes for compositing.
LayerType
Layer types for the 4-way visibility system.