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 } else if (direction == DoorDirection::East) {
166 // USDASM RoomDraw_OneSidedShutters_East ($01:AC13) advances the
167 // tilemap destination by one word before writing its three columns.
168 ++tile_x;
169 }
170 return {tile_x, tile_y};
171}
172
174 uint8_t position, DoorDirection direction) {
175 auto [tile_x, tile_y] = PositionToTileCoords(position, direction);
176 return {tile_x * kTileSize, tile_y * kTileSize};
177}
178
180 uint8_t position, DoorDirection direction) {
181 auto [tile_x, tile_y] = PositionToRenderTileCoords(position, direction);
182 return {tile_x * kTileSize, tile_y * kTileSize};
183}
184
186 switch (direction) {
188 return 0;
190 return kRoomHeightTiles - 3; // 3 tiles from bottom for door height
192 return 0;
194 return kRoomWidthTiles - 3; // 3 tiles from right for door width
195 }
196 return 0;
197}
198
200 DoorDirection direction) {
201 // ALTTP's door position tables expose 12 usable entries per direction:
202 // 0-5 for one section and 6-11 for the paired wall/seam section.
203 if (position >= DoorTilemapOffsets(direction).size()) {
204 return false;
205 }
206
207 auto dims = GetDoorDimensions(direction);
208 auto [tile_x, tile_y] = PositionToRenderTileCoords(position, direction);
209
210 return tile_x >= 0 && tile_y >= 0 &&
211 tile_x + dims.width_tiles <= kRoomWidthTiles &&
212 tile_y + dims.height_tiles <= kRoomHeightTiles;
213}
214
215bool DoorPositionManager::DetectWallFromPosition(int canvas_x, int canvas_y,
216 DoorDirection& out_direction) {
217 // Convert to tile coordinates
218 int tile_x = canvas_x / kTileSize;
219 int tile_y = canvas_y / kTileSize;
220
221 // Check each wall edge with threshold
222 int threshold = kWallDetectionThreshold;
223
224 // North wall (top edge)
225 if (tile_y < threshold) {
226 out_direction = DoorDirection::North;
227 return true;
228 }
229
230 // South wall (bottom edge)
231 if (tile_y >= kRoomHeightTiles - threshold) {
232 out_direction = DoorDirection::South;
233 return true;
234 }
235
236 // West wall (left edge)
237 if (tile_x < threshold) {
238 out_direction = DoorDirection::West;
239 return true;
240 }
241
242 // East wall (right edge)
243 if (tile_x >= kRoomWidthTiles - threshold) {
244 out_direction = DoorDirection::East;
245 return true;
246 }
247
248 return false;
249}
250
251bool DoorPositionManager::DetectWallSection(int canvas_x, int canvas_y,
252 DoorDirection& out_direction,
253 bool& out_is_inner) {
254 // Convert to tile coordinates
255 int tile_x = canvas_x / kTileSize;
256 int tile_y = canvas_y / kTileSize;
257
258 // Room is 64x64 tiles, divided into 4 quadrants at tile 32
259 constexpr int kMiddleSeam = 32;
260 constexpr int kSeamThreshold = 6; // Detection range around seam
261 int threshold = kWallDetectionThreshold;
262
263 // Check outer walls first (edges of room)
264 // North wall (top edge)
265 if (tile_y < threshold) {
266 out_direction = DoorDirection::North;
267 out_is_inner = false;
268 return true;
269 }
270
271 // South wall (bottom edge)
272 if (tile_y >= kRoomHeightTiles - threshold) {
273 out_direction = DoorDirection::South;
274 out_is_inner = false; // South outer wall = positions 6-11
275 return true;
276 }
277
278 // West wall (left edge)
279 if (tile_x < threshold) {
280 out_direction = DoorDirection::West;
281 out_is_inner = false;
282 return true;
283 }
284
285 // East wall (right edge)
286 if (tile_x >= kRoomWidthTiles - threshold) {
287 out_direction = DoorDirection::East;
288 out_is_inner = false; // East outer wall = positions 6-11
289 return true;
290 }
291
292 // Check inner seams (middle of room between quadrants)
293 // Horizontal seam at Y=32 (between top and bottom quadrants)
294 if (std::abs(tile_y - kMiddleSeam) < kSeamThreshold) {
295 // Determine if North or South based on which side of seam
296 if (tile_y < kMiddleSeam) {
297 out_direction = DoorDirection::North;
298 } else {
299 out_direction = DoorDirection::South;
300 }
301 out_is_inner = true;
302 return true;
303 }
304
305 // Vertical seam at X=32 (between left and right quadrants)
306 if (std::abs(tile_x - kMiddleSeam) < kSeamThreshold) {
307 // Determine if West or East based on which side of seam
308 if (tile_x < kMiddleSeam) {
309 out_direction = DoorDirection::West;
310 } else {
311 out_direction = DoorDirection::East;
312 }
313 out_is_inner = true;
314 return true;
315 }
316
317 return false;
318}
319
321 bool is_inner) {
322 // Position ranges per direction:
323 // - North: Outer (0-5), Inner (6-11)
324 // - South: Inner (0-5), Outer (6-11) <- inverted!
325 // - West: Outer (0-5), Inner (6-11)
326 // - East: Inner (0-5), Outer (6-11) <- inverted!
327 switch (direction) {
330 return is_inner ? 6 : 0;
331
334 // South/East have inverted mapping
335 return is_inner ? 0 : 6;
336 }
337 return 0;
338}
339
340std::pair<uint8_t, uint8_t> DoorPositionManager::EncodeDoorBytes(
341 uint8_t position, DoorType type, DoorDirection direction) {
342 // Byte 1: position in bits 4-7, direction in bits 0-1
343 // This matches FromRomBytes decoding: position = (b1 >> 4) & 0x0F,
344 // direction = b1 & 0x03
345 uint8_t byte1 =
346 ((position & 0x0F) << 4) | (static_cast<uint8_t>(direction) & 0x03);
347
348 // Byte 2: door type (full byte, values 0x00, 0x02, 0x04, etc.)
349 uint8_t byte2 = static_cast<uint8_t>(type);
350
351 return {byte1, byte2};
352}
353
354std::tuple<int, int, int, int> DoorPositionManager::GetDoorBounds(
355 uint8_t position, DoorDirection direction) {
356 auto [pixel_x, pixel_y] = PositionToRenderPixelCoords(position, direction);
357 auto dims = GetDoorDimensions(direction);
358
359 return {pixel_x, pixel_y, dims.width_pixels(), dims.height_pixels()};
360}
361
362std::tuple<int, int, int, int> DoorPositionManager::GetDoorEditorBounds(
363 uint8_t position, DoorDirection direction, DoorType type) {
364 auto [tile_x, tile_y] = PositionToTileCoords(position, direction);
365 const auto footprint = GetEditorDoorFootprint(direction, type);
366 tile_x += footprint.offset_x_tiles;
367 tile_y += footprint.offset_y_tiles;
368
369 return {tile_x * kTileSize, tile_y * kTileSize,
370 footprint.width_tiles * kTileSize,
371 footprint.height_tiles * kTileSize};
372}
373
374} // namespace zelda3
375} // 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:253
DoorType
Door types from ALTTP.
Definition door_types.h:33
constexpr int kDoorPosSouthAddr
constexpr DoorFootprint GetEditorDoorFootprint(DoorDirection dir, DoorType type)
Get the visible editor footprint for a door.
Definition door_types.h:275
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)