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 return {tile_x * kTileSize, tile_y * kTileSize};
162}
163
165 switch (direction) {
167 return 0;
169 return kRoomHeightTiles - 3; // 3 tiles from bottom for door height
171 return 0;
173 return kRoomWidthTiles - 3; // 3 tiles from right for door width
174 }
175 return 0;
176}
177
179 DoorDirection direction) {
180 // Position must fit in 5 bits
181 if (position > 0x1F) {
182 return false;
183 }
184
185 // Check that resulting tile position is within room bounds
186 // and leaves room for door dimensions
187 int tile = (position & 0x1F) * 2;
188 auto dims = GetDoorDimensions(direction);
189
190 // For horizontal doors (N/S), check X doesn't overflow
191 // For vertical doors (E/W), check Y doesn't overflow
192 if (direction == DoorDirection::North || direction == DoorDirection::South) {
193 return tile + dims.width_tiles <= kRoomWidthTiles;
194 } else {
195 return tile + dims.height_tiles <= kRoomHeightTiles;
196 }
197}
198
199bool DoorPositionManager::DetectWallFromPosition(int canvas_x, int canvas_y,
200 DoorDirection& out_direction) {
201 // Convert to tile coordinates
202 int tile_x = canvas_x / kTileSize;
203 int tile_y = canvas_y / kTileSize;
204
205 // Check each wall edge with threshold
206 int threshold = kWallDetectionThreshold;
207
208 // North wall (top edge)
209 if (tile_y < threshold) {
210 out_direction = DoorDirection::North;
211 return true;
212 }
213
214 // South wall (bottom edge)
215 if (tile_y >= kRoomHeightTiles - threshold) {
216 out_direction = DoorDirection::South;
217 return true;
218 }
219
220 // West wall (left edge)
221 if (tile_x < threshold) {
222 out_direction = DoorDirection::West;
223 return true;
224 }
225
226 // East wall (right edge)
227 if (tile_x >= kRoomWidthTiles - threshold) {
228 out_direction = DoorDirection::East;
229 return true;
230 }
231
232 return false;
233}
234
235bool DoorPositionManager::DetectWallSection(int canvas_x, int canvas_y,
236 DoorDirection& out_direction,
237 bool& out_is_inner) {
238 // Convert to tile coordinates
239 int tile_x = canvas_x / kTileSize;
240 int tile_y = canvas_y / kTileSize;
241
242 // Room is 64x64 tiles, divided into 4 quadrants at tile 32
243 constexpr int kMiddleSeam = 32;
244 constexpr int kSeamThreshold = 6; // Detection range around seam
245 int threshold = kWallDetectionThreshold;
246
247 // Check outer walls first (edges of room)
248 // North wall (top edge)
249 if (tile_y < threshold) {
250 out_direction = DoorDirection::North;
251 out_is_inner = false;
252 return true;
253 }
254
255 // South wall (bottom edge)
256 if (tile_y >= kRoomHeightTiles - threshold) {
257 out_direction = DoorDirection::South;
258 out_is_inner = false; // South outer wall = positions 6-11
259 return true;
260 }
261
262 // West wall (left edge)
263 if (tile_x < threshold) {
264 out_direction = DoorDirection::West;
265 out_is_inner = false;
266 return true;
267 }
268
269 // East wall (right edge)
270 if (tile_x >= kRoomWidthTiles - threshold) {
271 out_direction = DoorDirection::East;
272 out_is_inner = false; // East outer wall = positions 6-11
273 return true;
274 }
275
276 // Check inner seams (middle of room between quadrants)
277 // Horizontal seam at Y=32 (between top and bottom quadrants)
278 if (std::abs(tile_y - kMiddleSeam) < kSeamThreshold) {
279 // Determine if North or South based on which side of seam
280 if (tile_y < kMiddleSeam) {
281 out_direction = DoorDirection::North;
282 } else {
283 out_direction = DoorDirection::South;
284 }
285 out_is_inner = true;
286 return true;
287 }
288
289 // Vertical seam at X=32 (between left and right quadrants)
290 if (std::abs(tile_x - kMiddleSeam) < kSeamThreshold) {
291 // Determine if West or East based on which side of seam
292 if (tile_x < kMiddleSeam) {
293 out_direction = DoorDirection::West;
294 } else {
295 out_direction = DoorDirection::East;
296 }
297 out_is_inner = true;
298 return true;
299 }
300
301 return false;
302}
303
305 bool is_inner) {
306 // Position ranges per direction:
307 // - North: Outer (0-5), Inner (6-11)
308 // - South: Inner (0-5), Outer (6-11) <- inverted!
309 // - West: Outer (0-5), Inner (6-11)
310 // - East: Inner (0-5), Outer (6-11) <- inverted!
311 switch (direction) {
314 return is_inner ? 6 : 0;
315
318 // South/East have inverted mapping
319 return is_inner ? 0 : 6;
320 }
321 return 0;
322}
323
324std::pair<uint8_t, uint8_t> DoorPositionManager::EncodeDoorBytes(
325 uint8_t position, DoorType type, DoorDirection direction) {
326 // Byte 1: position in bits 4-7, direction in bits 0-1
327 // This matches FromRomBytes decoding: position = (b1 >> 4) & 0x0F,
328 // direction = b1 & 0x03
329 uint8_t byte1 =
330 ((position & 0x0F) << 4) | (static_cast<uint8_t>(direction) & 0x03);
331
332 // Byte 2: door type (full byte, values 0x00, 0x02, 0x04, etc.)
333 uint8_t byte2 = static_cast<uint8_t>(type);
334
335 return {byte1, byte2};
336}
337
338std::tuple<int, int, int, int> DoorPositionManager::GetDoorBounds(
339 uint8_t position, DoorDirection direction) {
340 auto [pixel_x, pixel_y] = PositionToPixelCoords(position, direction);
341 auto dims = GetDoorDimensions(direction);
342
343 return {pixel_x, pixel_y, dims.width_pixels(), dims.height_pixels()};
344}
345
346} // namespace zelda3
347} // 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 > 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 > 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 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:192
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)