yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
graphics_edit_context.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <array>
5#include <limits>
6#include <map>
7#include <optional>
8#include <set>
9#include <tuple>
10#include <utility>
11
12#include "absl/cleanup/cleanup.h"
13#include "absl/strings/str_format.h"
19#include "zelda3/dungeon/room.h"
20
21namespace yaze::editor {
22
23namespace {
24
26 return std::make_tuple(object.id_, object.x_, object.y_, object.size_,
27 object.GetLayerValue(), object.all_bgs_, object.lit_,
28 object.options(), object.block_behavior_layer(),
29 object.torch_reserved_bit());
30}
31
32absl::StatusOr<std::vector<zelda3::ObjectDrawer::TileTrace>> TraceObjectPattern(
33 const zelda3::Room& room, const zelda3::RoomObject& object,
34 std::string& limitation) {
35 const auto& objects = room.GetTileObjects();
36 const auto identity = ObjectIdentity(object);
37 std::optional<size_t> selected;
38 for (size_t i = 0; i < objects.size(); ++i) {
39 if (ObjectIdentity(objects[i]) != identity) {
40 continue;
41 }
42 if (selected) {
43 return absl::FailedPreconditionError(
44 "Object pattern geometry requires an unambiguous placement");
45 }
46 selected = i;
47 }
48
49 zelda3::ObjectDrawer drawer(room.rom(), room.id(),
50 room.get_gfx_buffer().data());
51 drawer.SetRoomTag2(static_cast<int>(room.tag2()));
52 drawer.SetRoomFloorGraphics(room.floor1(), room.floor2());
54 std::vector<zelda3::ObjectDrawer::TileTrace> trace;
55 gfx::PaletteGroup palette;
56 gfx::BackgroundBuffer bg1(512, 512), bg2(512, 512);
57 if (!selected) {
58 limitation =
59 "Unplaced catalog pattern uses standalone geometry; room "
60 "layout and preceding objects are not part of that pattern.";
61 auto rendered = object;
62 rendered.SetRom(room.rom());
64 std::min<uint8_t>(object.GetLayerValue(), 2));
65 drawer.SetTraceCollector(&trace, /*trace_only=*/true);
66 const auto status =
67 drawer.DrawObject(rendered, bg1, bg2, palette, room.GetDungeonState());
68 if (!status.ok()) {
69 return status;
70 }
71 return trace;
72 }
73 if (!zelda3::UsesRoomObjectStream(objects[*selected])) {
74 return absl::UnimplementedError(
75 "Special-table object pattern geometry uses a separate room pass");
76 }
77
78 // Match BuildObjectTraceReport: layout words plus the primary -> BG2 -> BG1
79 // stream prefix determine conditional caps and edges. Rendering the prefix
80 // records object coverage, even when a prior glyph is visually transparent.
81 gfx::BackgroundBuffer layout_bg1(512, 512), layout_bg2(512, 512);
82 const absl::Cleanup retire = [&] {
83 for (auto* buffer : {&bg1, &bg2, &layout_bg1, &layout_bg2}) {
84 gfx::Arena::Get().RetireBitmap(buffer->bitmap());
85 }
86 };
87 for (auto* buffer : {&bg1, &bg2}) {
88 buffer->EnsureBitmapInitialized();
89 buffer->bitmap().Fill(255);
90 buffer->ClearBuffer();
91 }
92 layout_bg1.buffer() = room.bg1_buffer().buffer();
93 layout_bg2.buffer() = room.bg2_buffer().buffer();
94 for (uint8_t pass = 0; pass < 3; ++pass) {
95 for (size_t i = 0; i < objects.size(); ++i) {
96 const auto& stored = objects[i];
97 const auto list = std::min<uint8_t>(stored.GetLayerValue(), 2);
98 if (!zelda3::UsesRoomObjectStream(stored) || list != pass) {
99 continue;
100 }
101 auto rendered = stored;
102 rendered.SetRom(room.rom());
103 rendered.layer_ = zelda3::MapRoomObjectListIndexToDrawLayer(list);
104 if (i == *selected) {
105 drawer.SetTraceCollector(&trace, /*trace_only=*/false);
106 }
107 const auto status =
108 drawer.DrawObject(rendered, bg1, bg2, palette, room.GetDungeonState(),
109 &layout_bg1, &layout_bg2);
110 if (!status.ok()) {
111 return status;
112 }
113 if (i == *selected) {
114 drawer.ClearTraceCollector();
115 return trace;
116 }
117 }
118 }
119 return absl::FailedPreconditionError("Selected object was not replayed");
120}
121
122} // namespace
123
125 constexpr const char* kLabels[] = {"Unflipped", "Horizontal", "Vertical",
126 "Horizontal + vertical"};
127 std::string label;
128 for (int orientation = 0; orientation < 4; ++orientation) {
129 if ((source_orientation_mask & (1 << orientation)) == 0) {
130 continue;
131 }
132 if (!label.empty()) {
133 label += ", ";
134 }
135 label += kLabels[orientation];
136 }
137 return label;
138}
139
141 const zelda3::GameData* owner) {
142 if (!target.writable_palette)
143 return absl::UnimplementedError(target.palette_address_limitation);
144 const auto& binding = *target.writable_palette;
145 if (!owner || binding.owner != owner)
146 return absl::FailedPreconditionError("Palette address session mismatch");
147 if (binding.colors[0] || target.palette.size() != 8 || binding.row < 2 ||
148 binding.row >= 8 ||
149 (binding.half_offset != 0 && binding.half_offset != 8)) {
150 return absl::FailedPreconditionError("Invalid raw palette address mapping");
151 }
152 for (int raw = 1; raw < 8; ++raw) {
153 if (!binding.colors[raw])
154 return absl::FailedPreconditionError("Nonzero palette index is unmapped");
155 auto color = zelda3::ReadPaletteColor(*owner, *binding.colors[raw]);
156 if (!color.ok())
157 return color.status();
158 const auto expected = color->rom_color();
159 const auto displayed = target.palette[raw].rom_color();
160 if (expected.red != displayed.red || expected.green != displayed.green ||
161 expected.blue != displayed.blue) {
162 return absl::FailedPreconditionError(
163 "Displayed palette differs from its writable source; refresh the "
164 "source");
165 }
166 }
167 return absl::OkStatus();
168}
169
171 GraphicsEditTarget& target, const zelda3::GameData* owner,
172 const zelda3::PaletteAddressMap& addresses, int row, int half_offset) {
173 target.writable_palette.reset();
175 row < 2 ? "HUD palette addressing is unavailable for this operation."
176 : "Animated, overridden or unresolved palette source has no "
177 "writable mapping.";
178 if (!owner || row < 0 || row >= 8 || (half_offset != 0 && half_offset != 8))
179 return;
181 binding.owner = owner;
182 binding.row = static_cast<uint8_t>(row);
183 binding.half_offset = static_cast<uint8_t>(half_offset);
184 for (int raw = 1; raw < 8; ++raw) {
185 binding.colors[raw] = addresses[row * 16 + half_offset + raw];
186 if (!binding.colors[raw])
187 return;
188 }
189 target.writable_palette = std::move(binding);
190 const auto status = ValidateGraphicsPaletteAddressing(target, owner);
191 if (!status.ok()) {
192 target.writable_palette.reset();
193 target.palette_address_limitation = std::string(status.message());
194 return;
195 }
196 target.palette_address_limitation.clear();
197}
198
200 GraphicsEditContext refreshed) {
201 if (context.owner != refreshed.owner ||
202 context.targets.size() != refreshed.targets.size()) {
203 return absl::FailedPreconditionError(
204 "Graphics source changed; reopen Edit Graphics from the source.");
205 }
206 for (size_t i = 0; i < context.targets.size(); ++i) {
207 const auto& old = context.targets[i];
208 const auto& next = refreshed.targets[i];
209 if (old.sheet_id != next.sheet_id || old.tile_index != next.tile_index ||
210 old.label != next.label ||
211 old.source_orientation_mask != next.source_orientation_mask ||
212 next.palette.size() != 8) {
213 return absl::FailedPreconditionError(
214 "Graphics source changed; reopen Edit Graphics from the source.");
215 }
216 if (next.writable_palette) {
217 const auto status =
219 if (!status.ok())
220 return status;
221 }
222 }
223 for (size_t i = 0; i < context.targets.size(); ++i) {
224 context.targets[i].writable_palette =
225 std::move(refreshed.targets[i].writable_palette);
226 context.targets[i].palette_address_limitation =
227 std::move(refreshed.targets[i].palette_address_limitation);
228 context.targets[i].palette = std::move(refreshed.targets[i].palette);
229 }
230 context.resolved = std::move(refreshed.resolved);
231 return absl::OkStatus();
232}
233
234absl::StatusOr<GraphicsEditContext> MakeDungeonGraphicsEditContext(
235 const zelda3::Room& room, const zelda3::RoomObject& object,
236 const zelda3::GameData* owner) {
237 if (!owner || room.game_data() != owner) {
238 return absl::FailedPreconditionError("Dungeon graphics session mismatch");
239 }
240 auto tiles = object.GetTiles();
241 if (!tiles.ok()) {
242 return tiles.status();
243 }
244 const auto patterns = zelda3::CustomObjectManager::Get().GetTilePatterns();
245 if (!patterns.ok())
246 return patterns.status();
247 std::vector<gfx::TileInfo> source_tiles;
248 if (*patterns && !zelda3::HasActiveCustomObjectOverride(object)) {
249 auto parsed = zelda3::ObjectParser(room.rom(), patterns->get())
250 .ParseObject(object.id_);
251 if (!parsed.ok())
252 return parsed.status();
253 source_tiles = std::move(*parsed);
254 tiles = std::span<const gfx::TileInfo>(source_tiles);
255 }
256 GraphicsEditContext context;
257 context.owner = owner;
258 context.label =
259 absl::StrFormat("Room %03X / Object %03X / Main %02X", room.id(),
260 object.id_, room.resolved_main_blockset());
264 auto traced = TraceObjectPattern(room, object, context.limitation);
265 if (!traced.ok()) {
266 context.limitation = std::string(traced.status().message());
267 }
268 // Actual writes can select room-floor or state-dependent payloads that are
269 // absent from the object's generic recipe. Attribute targets from the trace
270 // so imported art never falls back to a different physical source glyph.
271 std::vector<gfx::TileInfo> drawn_tiles;
272 if (traced.ok() && !traced->empty()) {
273 for (const auto& cell : *traced) {
274 gfx::TileInfo tile;
275 tile.id_ = cell.tile_id;
276 tile.palette_ = (cell.flags >> 3) & 7;
277 tile.horizontal_mirror_ = (cell.flags & 1) != 0;
278 tile.vertical_mirror_ = (cell.flags & 2) != 0;
279 tile.over_ = (cell.flags & 4) != 0;
280 drawn_tiles.push_back(tile);
281 }
282 tiles = std::span<const gfx::TileInfo>(drawn_tiles);
283 }
284 const auto& colors = room.rendered_palette();
285 const auto blocks = room.blocks();
286 std::map<std::pair<uint16_t, uint8_t>, size_t> target_indices;
287 for (const auto& tile : *tiles) {
288 if (tile.id_ >= 512 || tile.palette_ >= 8 ||
289 (tile.id_ >= 0x1B0 && tile.id_ < 0x1D0)) {
290 if (!context.limitation.empty()) {
291 context.limitation += " ";
292 }
293 context.limitation +=
294 "Animated or unmapped tiles are omitted from pixel editing.";
295 continue;
296 }
297 const uint8_t orientation =
298 (tile.horizontal_mirror_ ? 1 : 0) | (tile.vertical_mirror_ ? 2 : 0);
299 const uint8_t orientation_bit = 1 << orientation;
300 const auto [entry, inserted] = target_indices.emplace(
301 std::make_pair(tile.id_, tile.palette_), context.targets.size());
302 if (!inserted) {
303 context.targets[entry->second].source_orientation_mask |= orientation_bit;
304 continue;
305 }
306 const int slot = tile.id_ / 64;
307 const int offset = tile.palette_ * 16;
308 if (colors.size() < static_cast<size_t>(offset + 16)) {
309 return absl::FailedPreconditionError("Room palette is not rendered");
310 }
311 const int half = room.BackgroundPaletteOffset(slot);
312 std::vector<gfx::SnesColor> palette;
313 for (int index = 0; index < 8; ++index) {
314 const auto& color = colors[offset + (index == 0 ? 0 : half + index)];
315 palette.emplace_back(color.r, color.g, color.b);
316 }
317 context.targets.push_back({
318 .sheet_id = blocks[slot],
319 .tile_index = static_cast<uint16_t>(tile.id_ % 64),
320 .label = absl::StrFormat("Tile %03X / slot %d / sheet %02X / row %d %s",
321 tile.id_, slot, blocks[slot], tile.palette_,
322 half == 8 ? "right" : "left"),
323 .palette = gfx::SnesPalette(palette),
324 .source_orientation_mask = orientation_bit,
325 });
326 ResolveGraphicsPaletteAddressing(context.targets.back(), owner,
328 tile.palette_, half);
329 }
330 if (context.targets.empty()) {
331 return absl::UnimplementedError(
332 "This object's tiles do not yet have an editable graphics source");
333 }
334 if (traced.ok() && !traced->empty()) {
335 int min_x = std::numeric_limits<int>::max();
336 int min_y = min_x;
337 int max_x = std::numeric_limits<int>::min();
338 int max_y = max_x;
339 for (const auto& cell : *traced) {
340 min_x = std::min(min_x, static_cast<int>(cell.x_tile));
341 min_y = std::min(min_y, static_cast<int>(cell.y_tile));
342 max_x = std::max(max_x, static_cast<int>(cell.x_tile));
343 max_y = std::max(max_y, static_cast<int>(cell.y_tile));
344 }
345 context.pattern_width = (max_x - min_x + 1) * 8;
346 context.pattern_height = (max_y - min_y + 1) * 8;
347 std::set<std::tuple<int, int, size_t, bool, bool>> seen;
348 for (const auto& cell : *traced) {
349 const uint8_t palette = (cell.flags >> 3) & 7;
350 const auto target = target_indices.find({cell.tile_id, palette});
351 if (target == target_indices.end())
352 continue;
353 const int x = cell.x_tile - min_x;
354 const int y = cell.y_tile - min_y;
355 const bool h = (cell.flags & 1) != 0;
356 const bool v = (cell.flags & 2) != 0;
357 if (seen.emplace(x, y, target->second, h, v).second) {
358 context.pattern_cells.push_back({x, y, target->second, h, v});
359 }
360 }
361 }
362 return context;
363}
364
365} // namespace yaze::editor
void RetireBitmap(Bitmap &bitmap)
Explicitly retire resources owned by a bitmap that is being erased.
Definition arena.cc:63
static Arena & Get()
Definition arena.cc:24
std::vector< uint16_t > & buffer()
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
SNES 16-bit tile metadata container.
Definition snes_tile.h:52
static CustomObjectManager & Get()
absl::StatusOr< std::shared_ptr< const DungeonTilePatterns > > GetTilePatterns()
Draws dungeon objects to background buffers using game patterns.
void SetRoomFloorGraphics(uint8_t floor1, uint8_t floor2)
void SetBG1RevealMaskSource(gfx::BG1RevealMaskSource source)
absl::Status DrawObject(const RoomObject &object, gfx::BackgroundBuffer &bg1, gfx::BackgroundBuffer &bg2, const gfx::PaletteGroup &palette_group, const DungeonState *state=nullptr, gfx::BackgroundBuffer *layout_bg1=nullptr, gfx::BackgroundBuffer *layout_bg2=nullptr)
Draw a room object to background buffers.
void SetTraceCollector(std::vector< TileTrace > *collector, bool trace_only=false)
Direct ROM parser for dungeon objects.
absl::StatusOr< std::vector< gfx::TileInfo > > ParseObject(int16_t object_id)
Parse object data directly from ROM.
void SetRom(Rom *rom)
Definition room_object.h:78
const std::array< uint8_t, 0x10000 > & get_gfx_buffer() const
Definition room.h:1120
const std::vector< SDL_Color > & rendered_palette() const
Definition room.h:1138
auto game_data()
Definition room.h:1086
TagKey tag2() const
Definition room.h:988
uint8_t floor2() const
Definition room.h:1042
auto rom() const
Definition room.h:1082
auto & bg1_buffer()
Definition room.h:1145
const std::vector< RoomObject > & GetTileObjects() const
Definition room.h:445
DungeonState * GetDungeonState()
Definition room.h:1165
auto blocks() const
Definition room.h:1080
int BackgroundPaletteOffset(int slot) const
Definition room.cc:1085
const PaletteAddressMap & rendered_palette_addresses() const
Definition room.h:1135
uint8_t floor1() const
Definition room.h:1041
auto & bg2_buffer()
Definition room.h:1146
uint8_t resolved_main_blockset() const
Definition room.h:1017
int id() const
Definition room.h:1012
absl::StatusOr< std::vector< zelda3::ObjectDrawer::TileTrace > > TraceObjectPattern(const zelda3::Room &room, const zelda3::RoomObject &object, std::string &limitation)
Editors are the view controllers for the application.
absl::Status UpdateGraphicsContextPalettes(GraphicsEditContext &context, GraphicsEditContext refreshed)
void ResolveGraphicsPaletteAddressing(GraphicsEditTarget &target, const zelda3::GameData *owner, const zelda3::PaletteAddressMap &addresses, int row, int half_offset)
absl::StatusOr< GraphicsEditContext > MakeDungeonGraphicsEditContext(const zelda3::Room &room, const zelda3::RoomObject &object, const zelda3::GameData *owner)
absl::Status ValidateGraphicsPaletteAddressing(const GraphicsEditTarget &target, const zelda3::GameData *owner)
ResolvedGraphicsContext DescribeDungeonGraphicsContext(const Room &room, const GraphicsContextIdentity &identity)
RoomObject::LayerType MapRoomObjectListIndexToDrawLayer(uint8_t list_index)
ObjectLayerSemantics GetEffectiveObjectLayerSemantics(const RoomObject &object)
const char * ObjectRenderRoutingDisplayLabel(const ObjectLayerSemantics &semantics)
bool UsesRoomObjectStream(const RoomObject &object)
bool HasActiveCustomObjectOverride(const RoomObject &object)
absl::StatusOr< gfx::SnesColor > ReadPaletteColor(const GameData &owner, const PaletteColorAddress &address)
std::array< std::optional< PaletteColorAddress >, 128 > PaletteAddressMap
zelda3::ResolvedGraphicsContext resolved
std::vector< GraphicsEditTarget > targets
std::vector< GraphicsSourceCell > pattern_cells
std::optional< GraphicsPaletteAddressing > writable_palette
std::array< std::optional< zelda3::PaletteColorAddress >, 8 > colors
Represents a group of palettes.