포인트, 트라이포인트 및 좌표 시스템
이 문서는 CBN의 좌표 시스템과 진행 중인 tripoint → 타입 지정 좌표 마이그레이션에서 기여자가 좌표 관련 코드를 수정할 때 사용해야 하는 패턴을 설명합니다. 목표는 게임 로직 코드에서 원시 tripoint/point를 제거하고, 기존 변환 함수를 통합하며, 컴파일 시 좌표 의도를 명확하게 하는 것입니다.
축
게임은 3차원이며 축은 다음과 같이 정렬됩니다:
- x축은 디스플레이에서 왼쪽에서 오른쪽으로 이동합니다(비등각 뷰에서).
- y축은 디스플레이의 위에서 아래로 이동합니다.
- z축은 수직이며, 음수 z는 지하를, 양수 z는 하늘을 가리킵니다.
좌표 시스템
CBN은 다양한 목적을 위해 여러 좌표 시스템을 사용합니다. 이들은 규모와 원점에 따라 다릅니다.
가장 정밀한 좌표는 맵 스퀘어(ms) 좌표입니다. 이것은 게임을 플레이할 때 일반적으로 보는 타일을 나타냅니다.
맵 스퀘어 좌표의 일반적인 원점은 세 가지입니다:
- 절대 좌표(때로는 글로벌이라고도 함)는 고정된 원점을 기준으로 하는 전체 게임의 글로벌 시스템입니다.
- 버블 좌표는 현재 "현실 버블" 또는 아바타를 대략 중심으로 하는
map의 모서리를 기준으로 합니다. 로컬 맵 스퀘어 좌표에서x및y값은 모두[0, MAPSIZE_X)범위에 속합니다. - 차량 좌표는 현재 차량의 피벗을 기준으로 하며 차량과 함께 회전합니다. 이 원점은 특별하므로
mount_to_*및*_to_mount함수를 사용해야 올바르게 작동합니다. 다른 좌표 공간과 달리 차량은 회전을 고려해야 합니다.
차량 스케일(veh)도 있지만 마운트 함수에서만 사용되며 현재는 맵 스퀘어 좌표와 같습니다. 차량 좌표에서 실수하기 어렵게 만드는 역할만 합니다.
다음 규모는 서브맵(sm) 좌표입니다. 하나의 서브맵은 12x12(SEEXxSEEY) 맵 스퀘어입니다. 서브맵은 맵의 청크가 현실 버블에 들어가거나 나갈 때 로드되거나 저장되는 규모입니다.
다음은 오버맵 터레인(omt) 좌표입니다. 하나의 오버맵 터레인은 2x2 서브맵입니다. 오버맵 터레인은 게임 내 맵 뷰의 단일 타일에 해당하며 맵젠의 청크 규모입니다.
그 다음은 메모리 맵 영역(mmr) 좌표입니다. 현재 서브맵 저장 및 로드에만 사용되므로 마주칠 가능성은 낮습니다.
세그먼트(seg) 좌표는 메모리 맵 영역과 비슷하게 사용되지만 더 큰 규모입니다.
가장 큰 것은 오버맵(om) 좌표입니다. 하나의 오버맵은 180x180(OMAPXxOMAPY) 오버맵 터레인입니다. 대규모 맵젠(예: 도시 레이아웃)은 한 번에 하나의 오버맵에서 발생합니다.
절대, 버블, 차량 좌표 외에도 때때로 더 큰 규모에 상대적인 좌표를 사용해야 합니다. 예를 들어 단일 오버맵에 대한 맵젠을 수행할 때 해당 오버맵 내의 좌표로 작업하려고 합니다. 이것은 포함하는 오버맵의 모서리를 기준으로 하는 오버맵 터레인 규모 포인트이므로 일반적으로 [0,180) 범위의 x 및 y 값을 취합니다.
수직 좌표
x 및 y 좌표는 이러한 다양한 규모에서 작동하지만 z 좌표는 모든 컨텍스트에서 일관됩니다. 이들은 [-OVERMAP_DEPTH,OVERMAP_HEIGHT] 범위에 있습니다.
차량 좌표
각 차량에는 고유한 원점이 있으며, 이는 차량의 특정 부분(예: 운전석)에 위치합니다. 차량이 손상되고 해당 위치의 모든 차량 부품이 파괴되면 원점이 이동할 수 있습니다.
차량은 원점을 기준으로 두 가지 좌표 시스템을 사용합니다:
- 차량 / 마운트 좌표는 차량이 이동할 때 변경되지 않는 차량 부품의 위치를 제공합니다. 차량이 정동쪽을 향할 때 차량 원점을 기준으로 한 해당 부품의 맵 스퀘어입니다.
- 맵 스퀘어는 원점을 기준으로 하지만 차량의 현재 방향을 고려한 맵 스퀘어입니다.
차량 방향은 회전(1/4 회전) 및 전단의 조합을 통해 1/4 회전 사이를 보간하여 구현됩니다. 차량 마운트와 맵 스퀘어 좌표 간 변환 로직은 복잡하며 vehicle::abs_to_mount() 및 vehicle::mount_to_bubble() 함수 계열에서 처리됩니다.
현재 차량 마운트 좌표에는 z 레벨 구성 요소가 없지만 차량 맵 스퀘어 좌표에는 있습니다. z 좌표는 차량 원점을 기준으로 합니다. 이는 타입 지정 tripoint로 마이그레이션하면서 변경될 가능성이 높습니다.
포인트 타입
이러한 좌표 시스템으로 작업하기 위해 다양한 타입이 있습니다. 이들은 coordinates.h에 정의되어 있습니다. 예를 들어 절대 맵 스퀘어 좌표에 대한 point_abs_ms가 있습니다. 타입 이름의 세 부분은 차원 _ 원점 _ _규모_입니다.
- 차원은 2차원의 경우
point, 3차원의 경우tripoint입니다. - 원점은 값이 무엇을 기준으로 하는지 지정하며 다음과 같을 수 있습니다:
rel은 임의의 포인트를 기준으로 한다는 의미입니다. 공통 원점을 가진 두 포인트를 빼는 결과입니다. 예를 들어 아바타와 그들이 쏘는 몬스터 사이의 오프셋을 나타내는 데 사용됩니다.abs는 글로벌 절대 좌표를 의미합니다.bub은 현실 버블의 모서리를 기준으로 한다는 의미입니다.mnt는 차량의 기준점을 기준으로 하며 회전된 좌표라는 의미입니다.sm은 서브맵의 모서리를 기준으로 한다는 의미입니다.omt는 오버맵 터레인의 모서리를 기준으로 한다는 의미입니다.om은 오버맵의 모서리를 기준으로 한다는 의미입니다.
- 규모는 위에서 논의한 규모를 의미합니다.
ms는 맵 스퀘어입니다.veh는 차량 마운트 좌표(mnt원점에만 관련)입니다.sm은 서브맵입니다.omt는 오버맵 터레인입니다.seg는 세그먼트입니다.om은 오버맵입니다.
원시 포인트 타입
타입에 인코딩된 원점과 규모를 가진 이러한 타입 외에도 point 및 tripoint라는 간단한 원시 포인트 타입이 있습니다. 이들은 특정 게임 규모가 의도되지 않은 경우에 사용할 수 있습니다.
작성 시점에서 우리는 여전히 이러한 원시 포인트 타입을 어디서나 사용하는 것에서 코드베이스를 전환하는 과정에 있으므로, 더 타입 안전한 포인트가 적절해 보이는 곳에서 이들을 사용하는 레거시 코드를 볼 가능성이 높습니다.
코드는 가능한 경우 좌표 시스템을 포함하는 타입을 우선 사용해야 합니다.
기본 원칙은 다음과 같습니다. tripoint나 point가 세계 안의 위치(또는 그로부터 파생된 기준 좌표계)를 나타낸다면 타입 지정 좌표를 사용해야 합니다.
포인트 타입 간 변환
규모 변경
원점을 변경하지 않고 포인트의 규모를 변경하려면 project_to를 사용합니다. 예를 들어:
point_abs_ms pos_ms = get_avatar()->abs_pos().xy();
point_abs_omt pos_omt = project_to<coords::omt>( pos_ms );
assert( pos_omt == get_avatar()->abs_omt_pos().xy() );
동일한 함수 project_to를 확대 또는 축소에 사용할 수 있습니다. 더 거친 좌표 시스템으로 변환할 때 정밀도가 손실됩니다. 나머지가 중요하다면 대신 project_remain을 사용해야 합니다.
project_remain은 더 거친 좌표 시스템으로 변환하고 해당 거친 포인트를 기준으로 나머지를 캡처할 수 있습니다. 결과의 두 부분을 캡처하기 위해 std::tie와 함께 사용하도록 의도된 헬퍼 구조체를 반환합니다. 예를 들어 아바타가 어느 오버맵에 있는지, 그 오버맵 내에서 어느 오버맵 터레인에 있는지 알고 싶다고 가정합니다.
point_abs_omt abs_pos = get_avatar()->abs_omt_pos().xy();
point_abs_om overmap;
point_om_omt omt_within_overmap;
std::tie( overmap, omt_within_overmap ) = project_remain<coords::om>( abs_pos );
2차원 point 타입에는 적합하지만 tripoint는 어떻게 처리할까요? z 좌표는 수평 차원과 함께 스케일링되지 않으므로 z 값은 project_to 및 project_remain에 의해 변경되지 않는다는 것을 기억하세요. 그러나 project_remain의 경우 결과의 두 부분에서 z 좌표를 복제하고 싶지 않으므로 정확히 하나를 tripoint로 선택해야 합니다. 위의 예에서 z 좌표는 오버맵 규모에서 큰 의미가 없으므로 omt_within_overmap에 z 좌표를 원할 것입니다. 다음과 같이 수행할 수 있습니다:
tripoint_abs_omt abs_pos = get_avatar()->abs_omt_pos();
point_abs_om overmap;
tripoint_om_omt omt_within_overmap;
std::tie( overmap, omt_within_overmap ) = project_remain<coords::om>( abs_pos );
포인트 재스케일링에 사용 가능한 마지막 작업은 project_combine입니다. 이것은 project_remain의 반대 작업을 수행합니다. 두 번째의 원점이 첫 번째의 규모와 일치하는 두 포인트가 주어지면 단일 값으로 결합할 수 있습니다. 위 논의에서 예상할 수 있듯이 이 중 하나는 tripoint일 수 있지만 둘 다는 아닙니다.
tripoint_abs_omt abs_pos = get_avatar()->abs_omt_pos();
point_abs_om overmap;
tripoint_om_omt omt_within_overmap;
std::tie( overmap, omt_within_overmap ) = project_remain<coords::om>( abs_pos );
tripoint_abs_omt abs_pos_again = project_combine( overmap, omt_within_overmap );
assert( abs_pos == abs_pos_again );
원점 변경
project_remain과 project_combine은 재스케일링과 직접 관련된 일부 원점 변경을 지원합니다. 버블 또는 차량 좌표로 변환하려면 특정 map 또는 vehicle 객체가 필요합니다.
버블 변환 - abs_to_bub / bub_to_abs 사용
버블 좌표를 직접 계산하지 마세요. 맵의 변환 함수를 사용하세요:
// 잘못된 방법 - 수동 계산은 실수하기 쉽습니다
tripoint local = p - tripoint( abs_sub.x() * SEEX, abs_sub.y() * SEEY, 0 );
// 올바른 방법
tripoint_bub_ms bub = here.abs_to_bub( abs_ms_pos );
tripoint_abs_ms abs = here.bub_to_abs( bub_ms_pos );
호출부에서 get_map().이 필요 없는 자유 함수 오버로드도 일반적인 타입 지정 변형에 제공됩니다:
abs_to_bub( tripoint_abs_ms ) → tripoint_bub_ms
bub_to_abs( tripoint_bub_ms ) → tripoint_abs_ms
abs_to_bub( tripoint_abs_sm ) → tripoint_bub_sm
bub_to_abs( tripoint_bub_sm ) → tripoint_abs_sm
// …및 2D point_ 변형
이 함수는 맵 객체를 가져와야 하므로 성능이 중요한 위치에서는 사용하지 마세요. 조명, 그림자 및 기타 캐시 계산이 이에 해당합니다. 가능하다면 대규모 반복 전에 맵을 지역 변수로 보관하세요.
버블 공간의 z는 항상 절대 z 레벨입니다.
"버블 공간"은 tripoint_sm_ms가 해당 서브맵의 왼쪽 위에서부터의 맵 스퀘어를 뜻하는 것처럼 현실 버블 왼쪽 위 모서리에서 측정한 좌표입니다. x와 y는 버블 기준이지만 버블은 수직으로 이동하지 않으므로 z는 절대값입니다(vertical_shift는 abs_sub.z()만 갱신하고 격자를 재배치하지 않습니다). 따라서:
bub.z() == abs.z() // 항상 참
이는 XY만 변환하는 abs_to_bub와 bub_to_abs가 보장합니다. z에 abs_sub.z()를 직접 더하거나 빼지 마세요. 항상 아무 효과가 없고 버그를 만듭니다.
포인트 연산
표준 산술 연산을 오버로드된 연산자로 제공하지만 버그를 방지하기 위해 제한합니다. 예를 들어 대부분의 포인트 타입은 상수로 곱할 수 없지만 rel 원점을 가진 것은 가능합니다("같은 방향으로 절반만큼 멀리"라고 말하는 것이 합리적입니다).
마찬가지로 일반적으로 두 포인트를 더할 수 없지만 그 중 하나가 rel 원점을 가지거나 원시 포인트 타입인 경우 가능합니다.
거리를 계산하기 위해 요구 사항에 따라 다양한 함수를 사용할 수 있습니다: square_dist, trig_dist, rl_dist, manhattan_dist. 기타 관련 유틸리티 함수에는 direction_from 및 line_to가 있습니다.
같은 타입의 근처 포인트를 반복하려면 closest_points_first를 사용할 수 있습니다.
지원 중단됨 → 권장 참조
| 지원 중단됨 | 권장 |
|---|---|
here.bub_to_abs( tripoint ) |
here.bub_to_abs( tripoint_bub_ms( p ) ) |
here.abs_to_bub( tripoint ) |
here.abs_to_bub( tripoint_abs_ms( p ) ) |
ms_to_sm_copy( p ) |
project_to<coords::sm>( p ) |
sm_to_ms_copy( p ) |
project_to<coords::ms>( p ) |
sm_to_omt_copy + sm_to_omt_remain |
project_remain<coords::omt>( p ) |
omt_to_ms_copy( omt ) + offset |
project_combine( omt, offset ) |
사용 가능한 템플릿 헬퍼
다음은 모두 구현되어 사용할 수 있습니다:
| 헬퍼 | 위치 | 목적 |
|---|---|---|
project_to<S>(p) |
coordinates.h |
원점을 보존하는 스케일 변환 |
project_remain<S>(p) |
coordinates.h |
몫과 나머지 분해 |
project_combine(coarse, fine) |
coordinates.h |
몫과 나머지 재결합 |
abs_to_bub(p) / bub_to_abs(p) |
map/map.h (자유 함수) |
버블 ↔ 절대 |
p.reinterpret_as<T>() |
coordinates.h |
마이그레이션 단계에서만 사용하는 명시적 타입 변환 |
IsCoordPoint<T> 개념 |
coordinates.h |
타입 지정 좌표로 템플릿 제한 |
rl_dist(a, b) 타입 지정 오버로드 |
line.h |
같은 타입의 coord_point 쌍 허용 |
ch.bub_pos() / ch.abs_pos() |
creature.h |
타입 지정 생물 위치 접근자 |
reinterpret_as<T>()는 안전하지 않은 원점 변환을 명시적으로 만들고 검색 가능하게 하는 마이그레이션 도구입니다. 이 호출이 남아 있는 코드는 아직 완전히 마이그레이션되지 않은 것입니다.
절대 공간과 버블 공간
변환이 간단하다면 게임 로직에는 절대 공간을 우선 사용하세요. 다음에는 버블 공간이 적절합니다:
- 렌더링 및 표시 코드
- 현실 버블과 본질적으로 연결된 함수(예:
map::shift) - 변환이 복잡하고 변경 범위를 벗어나는 코드
기존 코드는 편리한 지역 프레임으로 현실 버블에 의존했습니다. 간단한 교체로 고칠 수 있다면 수정하고, 더 큰 수술이 필요하면 넘어가세요.