yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
door_position.cc
Go to the documentation of this file.
1#include "door_position.h"
2
3#include <algorithm>
4#include <cmath>
5#include <tuple>
6
7namespace yaze {
8namespace zelda3 {
9
10namespace {
11
12constexpr int TilemapOffsetToTileX(uint16_t offset) {
13 return static_cast<int>((offset % 0x80) / 2);
14}
15
16constexpr int TilemapOffsetToTileY(uint16_t offset) {
17 return static_cast<int>(offset / 0x80);
18}
19
20constexpr std::array<uint16_t, 12> kNorthDoorTilemapOffsets = {
21 0x021C, 0x023C, 0x025C, 0x039C, 0x03BC, 0x03DC,
22 0x121C, 0x123C, 0x125C, 0x139C, 0x13BC, 0x13DC};
23
24constexpr std::array<uint16_t, 12> kSouthDoorTilemapOffsets = {
25 0x0D1C, 0x0D3C, 0x0D5C, 0x0B9C, 0x0BBC, 0x0BDC,
26 0x1D1C, 0x1D3C, 0x1D5C, 0x1B9C, 0x1BBC, 0x1BDC};
27
28constexpr std::array<uint16_t, 12> kWestDoorTilemapOffsets = {
29 0x0784, 0x0F84, 0x1784, 0x078A, 0x0F8A, 0x178A,
30 0x07C4, 0x0FC4, 0x17C4, 0x07CA, 0x0FCA, 0x17CA};
31
32constexpr std::array<uint16_t, 12> kEastDoorTilemapOffsets = {
33 0x07B4, 0x0FB4, 0x17B4, 0x07AE, 0x0FAE, 0x17AE,
34 0x07F4, 0x0FF4, 0x17F4, 0x07EE, 0x0FEE, 0x17EE};
35
36const std::array<uint16_t, 12>& DoorTilemapOffsets(DoorDirection direction) {
37 switch (direction) {
46 }
47
49}
50
51} // namespace
52
53// ROM addresses for door position tables (PC addresses, bank $00)
54// Each direction has TWO consecutive tables for a total of 12 positions:
55// - Positions 0-5: Wall table (outer edges of room)
56// - Positions 6-11: Middle table (internal seams between quadrants)
57//
58// Table layout in ROM (consecutive in memory):
59// NorthWall ($997E): $021C,$023C,$025C,$039C,$03BC,$03DC (6 entries)
60// NorthMiddle ($998A): $121C,$123C,$125C,$139C,$13BC,$13DC (6 entries)
61// SouthMiddle ($9996): $0D1C,$0D3C,$0D5C,$0B9C,$0BBC,$0BDC,$1D1C,$1D3C,$1D5C (9 entries)
62// LowerLayerEntrance ($99A8): $1B9C,$1BBC,$1BDC (3 entries)
63// WestWall ($99AE): $0784,$0F84,$1784,$078A,$0F8A,$178A (6 entries)
64// WestMiddle ($99BA): $07C4,$0FC4,$17C4,$07CA,$0FCA,$17CA (6 entries)
65// EastMiddle ($99C6): $07B4,$0FB4,$17B4,$07AE,$0FAE,$17AE (6 entries)
66// EastWall ($99D2): $07F4,$0FF4,$17F4,$07EE,$0FEE,$17EE (6 entries)
67//
68// VRAM offset to editor-room tile conversion:
69// room_x = (offset % 0x80) / 2
70// room_y = (offset / 0x80)
71// The editor's 64x64 room canvas already operates in room-space, so the
72// vertical margin from the VRAM tables must not be subtracted a second time.
73//
74// Room layout: 64x64 tiles divided into 4 quadrants (32x32 each)
75// X positions for N/S doors: 14, 30, 46 (left/center/right)
76// Y positions for E/W doors: 15, 31, 47 (distributed across 64 tiles)
77[[maybe_unused]] constexpr int kDoorPosNorthAddr = 0x197E;
78[[maybe_unused]] constexpr int kDoorPosSouthAddr = 0x198A;
79[[maybe_unused]] constexpr int kDoorPosWestAddr = 0x1996;
80[[maybe_unused]] constexpr int kDoorPosEastAddr = 0x19A2;
81
83 DoorDirection direction) {
84 // Return valid placement rows/columns based on the actual ROM tilemap tables.
85 // Outer and inner sections share the same three positions along their moving axis.
86 switch (direction) {
88 return {14, 30, 46};
90 return {14, 30, 46};
92 return {15, 31, 47};
94 return {15, 31, 47};
95 }
96 return {};
97}
98
99uint8_t DoorPositionManager::SnapToNearestPosition(int canvas_x, int canvas_y,
100 DoorDirection direction) {
101 // First detect which section (outer wall vs inner seam) we're on
102 DoorDirection detected_dir;
103 bool is_inner = false;
104 if (!DetectWallSection(canvas_x, canvas_y, detected_dir, is_inner)) {
105 // Fallback: use outer wall positions
106 is_inner = false;
107 }
108
109 // Get the starting position index for this section
110 uint8_t start_pos = GetSectionStartPosition(direction, is_inner);
111
112 // Convert canvas pixels to tile coordinates
113 int tile_x = canvas_x / kTileSize;
114 int tile_y = canvas_y / kTileSize;
115
116 // Determine which coordinate to snap based on direction
117 // For North/South walls, we snap the X position (horizontal placement)
118 // For East/West walls, we snap the Y position (vertical placement)
119 int coord =
120 (direction == DoorDirection::North || direction == DoorDirection::South)
121 ? tile_x
122 : tile_y;
123
124 // Get valid snap positions for this direction
125 auto valid_positions = GetSnapPositions(direction);
126 if (valid_positions.empty()) {
127 return start_pos; // Fallback
128 }
129
130 // Find the nearest valid X/Y position (index 0, 1, or 2)
131 int nearest_idx = 0;
132 int min_dist = std::abs(coord - valid_positions[0]);
133 for (size_t i = 1; i < valid_positions.size(); ++i) {
134 int dist = std::abs(coord - valid_positions[i]);
135 if (dist < min_dist) {
136 min_dist = dist;
137 nearest_idx = static_cast<int>(i);
138 }
139 }
140
141 // Return position index offset by section start
142 // Positions 0,1,2 and 3,4,5 have same X coords but different Y for layer variation
143 // For simplicity, we return the base position (0,1,2 offset by start_pos)
144 return static_cast<uint8_t>(start_pos + nearest_idx);
145}
146
148 uint8_t position, DoorDirection direction) {
149 int pos_idx = position & 0x0F;
150 if (pos_idx > 11) {
151 pos_idx = 11;
152 }
153
154 const uint16_t offset = DoorTilemapOffsets(direction)[pos_idx];
155 return {TilemapOffsetToTileX(offset), TilemapOffsetToTileY(offset)};
156}
157
159 uint8_t position, DoorDirection direction) {
160 auto [tile_x, tile_y] = PositionToTileCoords(position, direction);
161 if (direction == DoorDirection::South) {
162 // USDASM RoomDraw_OneSidedShutters_South ($01:AABB) writes through
163 // $CB/$D7/$DA, i.e. rows y+1..y+3 from DoorTilemapPositions_South*.
164 ++tile_y;
165 }
166 return {tile_x, tile_y};
167}
168
170 uint8_t position, DoorDirection direction) {
171 auto [tile_x, tile_y] = PositionToTileCoords(position, direction);
172 return {tile_x * kTileSize, tile_y * kTileSize};
173}
174
176 uint8_t position, DoorDirection direction) {
177 auto [tile_x, tile_y] = PositionToRenderTileCoords(position, direction);
178 return {tile_x * kTileSize, tile_y * kTileSize};
179}
180
182 switch (direction) {
184 return 0;
186 return kRoomHeightTiles - 3; // 3 tiles from bottom for door height
188 return 0;
190 return kRoomWidthTiles - 3; // 3 tiles from right for door width
191 }
192 return 0;
193}
194
196 DoorDirection direction) {
197 // ALTTP's door position tables expose 12 usable entries per direction:
198 // 0-5 for one section and 6-11 for the paired wall/seam section.
199 if (position >= DoorTilemapOffsets(direction).size()) {
200 return false;
201 }
202
203 auto dims = GetDoorDimensions(direction);
204 auto [tile_x, tile_y] = PositionToRenderTileCoords(position, direction);
205
206 return tile_x >= 0 && tile_y >= 0 &&
207 tile_x + dims.width_tiles <= kRoomWidthTiles &&
208 tile_y + dims.height_tiles <= kRoomHeightTiles;
209}
210
211bool DoorPositionManager::DetectWallFromPosition(int canvas_x, int canvas_y,
212 DoorDirection& out_direction) {
213 // Convert to tile coordinates
214 int tile_x = canvas_x / kTileSize;
215 int tile_y = canvas_y / kTileSize;
216
217 // Check each wall edge with threshold
218 int threshold = kWallDetectionThreshold;
219
220 // North wall (top edge)
221 if (tile_y < threshold) {
222 out_direction = DoorDirection::North;
223 return true;
224 }
225
226 // South wall (bottom edge)
227 if (tile_y >= kRoomHeightTiles - threshold) {
228 out_direction = DoorDirection::South;
229 return true;
230 }
231
232 // West wall (left edge)
233 if (tile_x < threshold) {
234 out_direction = DoorDirection::West;
235 return true;
236 }
237
238 // East wall (right edge)
239 if (tile_x >= kRoomWidthTiles - threshold) {
240 out_direction = DoorDirection::East;
241 return true;
242 }
243
244 return false;
245}
246
247bool DoorPositionManager::DetectWallSection(int canvas_x, int canvas_y,
248 DoorDirection& out_direction,
249 bool& out_is_inner) {
250 // Convert to tile coordinates
251 int tile_x = canvas_x / kTileSize;
252 int tile_y = canvas_y / kTileSize;
253
254 // Room is 64x64 tiles, divided into 4 quadrants at tile 32
255 constexpr int kMiddleSeam = 32;
256 constexpr int kSeamThreshold = 6; // Detection range around seam
257 int threshold = kWallDetectionThreshold;
258
259 // Check outer walls first (edges of room)
260 // North wall (top edge)
261 if (tile_y < threshold) {
262 out_direction = DoorDirection::North;
263 out_is_inner = false;
264 return true;
265 }
266
267 // South wall (bottom edge)
268 if (tile_y >= kRoomHeightTiles - threshold) {
269 out_direction = DoorDirection::South;
270 out_is_inner = false; // South outer wall = positions 6-11
271 return true;
272 }
273
274 // West wall (left edge)
275 if (tile_x < threshold) {
276 out_direction = DoorDirection::West;
277 out_is_inner = false;
278 return true;
279 }
280
281 // East wall (right edge)
282 if (tile_x >= kRoomWidthTiles - threshold) {
283 out_direction = DoorDirection::East;
284 out_is_inner = false; // East outer wall = positions 6-11
285 return true;
286 }
287
288 // Check inner seams (middle of room between quadrants)
289 // Horizontal seam at Y=32 (between top and bottom quadrants)
290 if (std::abs(tile_y - kMiddleSeam) < kSeamThreshold) {
291 // Determine if North or South based on which side of seam
292 if (tile_y < kMiddleSeam) {
293 out_direction = DoorDirection::North;
294 } else {
295 out_direction = DoorDirection::South;
296 }
297 out_is_inner = true;
298 return true;
299 }
300
301 // Vertical seam at X=32 (between left and right quadrants)
302 if (std::abs(tile_x - kMiddleSeam) < kSeamThreshold) {
303 // Determine if West or East based on which side of seam
304 if (tile_x < kMiddleSeam) {
305 out_direction = DoorDirection::West;
306 } else {
307 out_direction = DoorDirection::East;
308 }
309 out_is_inner = true;
310 return true;
311 }
312
313 return false;
314}
315
317 bool is_inner) {
318 // Position ranges per direction:
319 // - North: Outer (0-5), Inner (6-11)
320 // - South: Inner (0-5), Outer (6-11) <- inverted!
321 // - West: Outer (0-5), Inner (6-11)
322 // - East: Inner (0-5), Outer (6-11) <- inverted!
323 switch (direction) {
326 return is_inner ? 6 : 0;
327
330 // South/East have inverted mapping
331 return is_inner ? 0 : 6;
332 }
333 return 0;
334}
335
336std::pair<uint8_t, uint8_t> DoorPositionManager::EncodeDoorBytes(
337 uint8_t position, DoorType type, DoorDirection direction) {
338 // Byte 1: position in bits 4-7, direction in bits 0-1
339 // This matches FromRomBytes decoding: position = (b1 >> 4) & 0x0F,
340 // direction = b1 & 0x03
341 uint8_t byte1 =
342 ((position & 0x0F) << 4) | (static_cast<uint8_t>(direction) & 0x03);
343
344 // Byte 2: door type (full byte, values 0x00, 0x02, 0x04, etc.)
345 uint8_t byte2 = static_cast<uint8_t>(type);
346
347 return {byte1, byte2};
348}
349
350std::tuple<int, int, int, int> DoorPositionManager::GetDoorBounds(
351 uint8_t position, DoorDirection direction) {
352 auto [pixel_x, pixel_y] = PositionToRenderPixelCoords(position, direction);
353 auto dims = GetDoorDimensions(direction);
354
355 return {pixel_x, pixel_y, dims.width_pixels(), dims.height_pixels()};
356}
357
358std::tuple<int, int, int, int> DoorPositionManager::GetDoorEditorBounds(
359 uint8_t position, DoorDirection direction, DoorType type) {
360 auto [pixel_x, pixel_y] = PositionToRenderPixelCoords(position, direction);
361 auto dims = GetEditorDoorDimensions(direction, type);
362
363 return {pixel_x, pixel_y, dims.width_pixels(), dims.height_pixels()};
364}
365
366} // namespace zelda3
367} // namespace yaze
static uint8_t GetSectionStartPosition(DoorDirection direction, bool is_inner)
Get the starting position index for outer/inner section.
static constexpr int kRoomHeightTiles
static std::pair< uint8_t, uint8_t > EncodeDoorBytes(uint8_t position, DoorType type, DoorDirection direction)
Encode door data for ROM storage.
static std::pair< int, int > PositionToPixelCoords(uint8_t position, DoorDirection direction)
Convert encoded position to pixel coordinates.
static std::tuple< int, int, int, int > GetDoorEditorBounds(uint8_t position, DoorDirection direction, DoorType type)
Get the editor interaction bounds for a typed door.
static std::tuple< int, int, int, int > GetDoorBounds(uint8_t position, DoorDirection direction)
Get the bounding rectangle for a door.
static bool DetectWallFromPosition(int canvas_x, int canvas_y, DoorDirection &out_direction)
Detect which wall the cursor is near.
static bool IsValidPosition(uint8_t position, DoorDirection direction)
Check if a position is valid for door placement.
static constexpr int kWallDetectionThreshold
static std::pair< int, int > PositionToRenderTileCoords(uint8_t position, DoorDirection direction)
Convert encoded position to the top-left tile of the visible door.
static std::pair< int, int > PositionToTileCoords(uint8_t position, DoorDirection direction)
Convert encoded position to tile coordinates.
static bool DetectWallSection(int canvas_x, int canvas_y, DoorDirection &out_direction, bool &out_is_inner)
Detect wall with inner/outer section information.
static std::pair< int, int > PositionToRenderPixelCoords(uint8_t position, DoorDirection direction)
Convert encoded position to visible render pixel coordinates.
static uint8_t SnapToNearestPosition(int canvas_x, int canvas_y, DoorDirection direction)
Convert canvas coordinates to nearest valid door position.
static int GetWallEdge(DoorDirection direction)
Get the wall edge coordinate for a direction.
static constexpr int kRoomWidthTiles
static std::vector< int > GetSnapPositions(DoorDirection direction)
Get all valid snap positions for a given direction.
const std::array< uint16_t, 12 > & DoorTilemapOffsets(DoorDirection direction)
constexpr int TilemapOffsetToTileY(uint16_t offset)
constexpr std::array< uint16_t, 12 > kEastDoorTilemapOffsets
constexpr std::array< uint16_t, 12 > kSouthDoorTilemapOffsets
constexpr std::array< uint16_t, 12 > kWestDoorTilemapOffsets
constexpr std::array< uint16_t, 12 > kNorthDoorTilemapOffsets
constexpr int TilemapOffsetToTileX(uint16_t offset)
constexpr DoorDimensions GetDoorDimensions(DoorDirection dir)
Get door dimensions based on direction.
Definition door_types.h:235
DoorType
Door types from ALTTP.
Definition door_types.h:33
constexpr int kDoorPosSouthAddr
constexpr int kDoorPosEastAddr
constexpr int kDoorPosNorthAddr
constexpr int kDoorPosWestAddr
DoorDirection
Door direction on room walls.
Definition door_types.h:18
@ South
Bottom wall (horizontal door, 4x3 tiles)
@ North
Top wall (horizontal door, 4x3 tiles)
@ East
Right wall (vertical door, 3x4 tiles)
@ West
Left wall (vertical door, 3x4 tiles)
constexpr DoorDimensions GetEditorDoorDimensions(DoorDirection dir, DoorType type)
Get editor interaction dimensions for a door.
Definition door_types.h:257