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