Polestar
Polestar is a free, open-source leveling guide addon for WoW: Forever: an in-game arrow and leveling routes that make sense for every race 1 to 60, plus class quests.
翻訳待ち
No updates detected yet. GitHub releases will appear here.
통영~거제 한려수도를 항해하는 웹 기반 3D 항해 시뮬레이터 (Three.js, OpenLayers)
翻訳待ち
Loading the full README. The saved excerpt is shown below.
통영과 거제 사이의 한려수도에서 실제 지형과 해도 위로 선박을 조종하는 웹 기반의 3D 항해 시뮬레이터입니다. 지형과 파도, 바람, 태양의 위치를 실제 자료와 물리 수식으로 계산합니다. 바로 실행하기: https://namhae-sail.vercel.app/ (데스크톱의 키보드가 필요합니다) Node.js 24와 pnpm 12에서 개발하고 확인했습니다. 지형의 표고 자료는 실행 중에 외부 서버에서 받아 오므로 인터넷 연결이 필요합니다. 연결이 없으면 3D 화면에 지형 없이 바다만 표시됩니다. 해도는 저장소에 포함된 자료로 그리므로 연결이 없어도 표시됩니다. 오른쪽 위의 버튼으로 시각과 풍속, 풍향을 조절할 수 있고, 왼쪽 위의 설정 메뉴에서 조작키의 전체 목록을 확인할 수 있습니다. 구역의 여러 곳을 배를 몰고 가지 않고 둘러볼 수 있는 개발용 기능입니다. pnpm dev로 실행한 로컬의 개발 서버에서만 동작하며, 배포용 빌드에는 포함되지 않습니다.Read the full README ↗
통영과 거제 사이의 한려수도에서 실제 지형과 해도 위로 선박을 조종하는 웹 기반의 3D 항해 시뮬레이터입니다. 지형과 파도, 바람, 태양의 위치를 실제 자료와 물리 수식으로 계산하고, 거친 바다에서는 파도를 받는 방향과 속력에 따라 선체가 기울거나 뒤집힙니다.
바로 실행하기: https://namhae-sail.vercel.app/ (데스크톱의 키보드가 필요합니다)
| 기능 | 설명 |
|---|---|
| 실제 지형 | 공개 표고 자료로 배 주변 14km의 지형을 실시간으로 불러와서 표시합니다. 운영 구역은 통영~거제의 약 55×48km이며, 통영항 앞에서 출발합니다. 왼쪽 위의 "시작 위치" 메뉴에서 다섯 곳의 시작 위치(통영항, 한산도, 견내량, 사량도, 욕지도) 중의 하나를 골라서 바로 이동할 수 있습니다. |
| 등대와 부표 | OpenStreetMap에 등록된 운영 구역의 등대, 등표, 부표 104개를 실제 위치에 3D로 표시합니다. 부표는 파도를 따라 움직이고, 등화는 자료에 기록된 색상과 주기로 깜빡입니다. 마우스를 올리면 이름과 등화, 배에서의 방위와 거리를 표시합니다. |
| 방파제와 부두 | OpenStreetMap에 등록된 방파제와 부두 407개를 윤곽대로 3D로 세웁니다. 해도와 같은 자료를 사용하므로 두 화면이 일치합니다. |
| 다리 | 바다를 건너는 다리 39개(거가대교, 신거제대교, 통영대교 등)의 상판과 교각을 3D로 세웁니다. 다리 밑의 높이가 선박보다 낮으면 지나갈 수 없습니다. |
| 충돌 | 선체가 화면에 보이는 해안선이나 방파제, 부두, 교각, 등대, 등표에 닿으면 멈추고 경고를 표시합니다. 부표는 밀려나서 흔들립니다. 충돌한 속력에 비례해서 물보라가 튀고 화면이 흔들립니다. |
| 바람과 파도 | 파도의 높이는 풍속과, 바람이 육지에 막히지 않고 불어온 거리(취주 거리)로 정합니다. 같은 바람이어도 섬의 뒤쪽은 열린 바다보다 잔잔합니다. 먼바다에서 밀려오는 너울을 따로 더하며, 큰 파도가 몇 개씩 무리를 지어 지나갑니다. 풍향은 접속할 때마다 무작위로 정해집니다. |
| 파도의 세기 | 잔잔함부터 폭풍까지 다섯 단계 가운데에서 고릅니다. 풍속 2 |
| 부력과 횡동요 | 선체 아래의 수면을 읽어서 상하 움직임과 앞뒤 기울기를 계산합니다. 좌우 기울기는 고유 주기를 가진 진동으로 계산하므로, 파도를 옆에서 받으면 크게 흔들리고 선수로 받으면 거의 흔들리지 않습니다. 옆바람과 선회도 선체를 기울입니다. |
| 전복 | 선체를 일으켜 세우는 힘은 기울기가 커지면 줄어들다가 약 65°에서 사라집니다. 가파른 마루가 선체의 옆면을 때리면 그 너머로 넘어가서 뒤집힐 수 있고, 빠르게 달릴수록 충격이 커집니다. 뒤집힌 배는 엔진이 멈추며, R 키로 마지막에 배를 놓았던 곳에서 다시 시작합니다. |
| 부서지는 마루 | 선체를 넘어뜨릴 만큼 가파른 마루를 수면에 흰 물거품으로 표시합니다. 물리 계산과 같은 기준을 사용하므로, 화면에서 희게 부서지는 곳이 피해야 할 곳입니다. 풍속 13m/s 부근부터는 바람에 날리는 물보라가 수면에 줄을 긋습니다. |
| 실제 태양 | 날짜와 시각, 위치로부터 태양의 고도와 방위를 계산하고, 고도에 따라 햇빛의 색상과 세기를 변경합니다. |
| 항적 | 배가 지나간 자리에 선미의 물거품과 선수에서 갈라지는 V자 물결이 남습니다. |
| 해도와 계기판 | OpenLayers로 그린 해도에 해안선과 지명, 시가지, 방파제와 부두, 다리, 해상 표지, 배의 위치와 항적을 표시하고, 속력과 선수 방위, 대지 침로, 엔진 회전수, 스로틀, 타각, 겉보기 바람을 계기로 표시합니다. 경사계는 선체의 좌우 기울기를, 파도 방향 계기는 풍랑과 너울이 선수를 기준으로 어느 쪽에서 오는지를 보여 주며, 파도를 옆으로 받는 위험한 자세를 붉은색으로 경고합니다. |
| 레이더 | 회전하는 안테나의 방향으로 광선을 쏘아서 주변의 해안선과 방파제, 다리, 등대와 부표를 탐지합니다. 섬의 뒤쪽은 전파가 닿지 않아서 표시되지 않고, 주사선이 지나간 자리만 갱신되며 잔상이 서서히 흐려집니다. 레이더 위에서 휠을 돌리면 탐지 거리가 0.25해리에서 4해리까지 바뀝니다. |
| 해상도 조절 | 프레임이 느린 상태가 이어지면 장면을 그리는 해상도를 한 단계씩 낮추고, 여유가 생기면 다시 올립니다. 창의 크기와 GPU의 성능이 달라도 끊기지 않게 합니다. |
| 선박의 교체 | 기본 선박은 15m급 요트이며, 180m급 여객선의 사양도 함께 정의되어 있습니다. main.ts의 상수 하나를 바꾸면 모델과 조종 특성, 카메라의 거리, 항적의 크기가 함께 전환됩니다. |
Node.js 24와 pnpm 12에서 개발하고 확인했습니다.
pnpm install
pnpm dev # http://localhost:5190
pnpm test # 단위 테스트
pnpm typecheck # 타입 검사
pnpm build # 배포용 빌드
지형의 표고 자료는 실행 중에 외부 서버에서 받아 오므로 인터넷 연결이 필요합니다. 연결이 없으면 3D 화면에 지형 없이 바다만 표시됩니다. 해도는 저장소에 포함된 자료로 그리므로 연결이 없어도 표시됩니다.
| 입력 | 동작 |
|---|---|
W / S | 스로틀 증가 / 감소. 레버처럼 놓은 위치에 유지되며, 0% 아래로 내리면 후진합니다. |
A / D | 좌현 / 우현 타. 키를 놓으면 중앙으로 복귀합니다. |
Space | 스로틀 중립 |
C | 시점 초기화 |
R | 전복된 뒤에 다시 시작 |
| 드래그 / 휠 | 시점 회전 / 카메라 거리 |
오른쪽 위의 패널에서 파도의 세기를 고를 수 있고, 왼쪽 위의 메뉴는 역할별로 나뉘어 있습니다. "시작 위치"에서 다른 곳으로 바로 이동하고, "도움말"에서 조작키의 전체 목록을 확인합니다.
거친 바다에서는 파도 방향 계기의 화살표가 붉은 구간에 들어가지 않도록 선수를 돌리고, 흰 물거품이 이는 마루 앞에서는 속력을 줄입니다. 기울어지는 쪽의 반대로 타를 쓰면 선체가 일어섭니다.
pnpm dev로 실행한 로컬의 개발 서버에서만 동작하며, 배포용 빌드에는 포함되지 않습니다.
관찰 모드는 구역의 여러 곳을 배를 몰고 가지 않고 둘러보는 기능입니다.
| 입력 | 동작 |
|---|---|
F | 관찰 모드를 켜고 끕니다. 켜져 있는 동안에는 배의 시간이 멈춥니다. |
W A S D | 카메라가 바라보는 지점을 옮깁니다. Shift를 함께 누르면 빠르게 움직입니다. |
| 드래그 / 휠 | 시점 회전 / 카메라 거리 (최대 9km) |
T | 배를 지금 보고 있는 위치로 옮깁니다. 육지나 구조물 위로는 옮기지 않습니다. |
"개발 도구" 메뉴에서 시, 읍면, 섬의 이름을 골라서 그 장소로 바로 이동할 수 있습니다. 주소에 ?lat=34.76&lon=128.477&observe와 같이 적으면 그 좌표를 관찰 모드로 바라보면서 시작합니다.
날씨의 직접 조절: 오른쪽 위의 패널에 시각, 풍속, 풍향, 너울의 높이와 방향을 하나씩 바꾸는 막대가 추가됩니다. 풍속이나 너울을 직접 바꾸면, 파도의 세기를 다시 고를 때까지 날씨가 저절로 변하지 않습니다.
장면의 조사: 브라우저의 콘솔에서 window.__namhae로 렌더러와 장면, 선박의 객체를 꺼내 볼 수 있습니다. 성능을 측정하는 스크립트가 이것으로 장면의 요소를 하나씩 끄면서 비용을 잽니다.
| 기술 | 선택한 이유 |
|---|---|
| TypeScript | 좌표계와 단위가 여러 모듈에 걸쳐서 사용되므로, 타입으로 인터페이스를 고정했습니다. |
| Three.js (WebGL2) | 씬 그래프와 glTF 불러오기, PBR 머티리얼은 라이브러리에 맡기고, 수면과 같이 직접 제어해야 하는 부분만 GLSL 셰이더로 작성했습니다. |
| Vite | 셰이더와 물리 수치를 변경할 때마다 즉시 화면에서 확인하기 위해서 사용했습니다. |
| OpenLayers | 해도를 그립니다. 외부의 지도 타일 서버를 조회하지 않고, 미리 가공한 해안선과 해상 표지를 벡터 레이어로 표시합니다. |
| Vitest | 좌표 변환과 태양의 위치처럼 실제 값과 대조할 수 있는 계산을 검증합니다. |
src/
main.ts 모듈을 만들어서 서로 연결하고, 한 프레임의 처리 순서를 정합니다
core/ 렌더러, 해상도 조절, 고정 간격의 루프, 키보드 입력, 관찰 모드
geo/ 경위도와 월드 좌표의 변환, 운영 구역, 단위의 변환과 표기
environment/ 바람과 너울, 파도의 세기, 태양의 위치와 햇빛의 색상
ocean/ 파도의 스펙트럼, 푸리에 변환으로 만드는 수면, 수면 높이 계산, 수면의 셰이더, 항적
ship/ 선박의 사양, 운동 모델, 횡동요, 선체가 파도와 바람에서 받는 힘, 추적 카메라
world/ 지형 타일, 육지 마스크, 취주 거리, 방파제와 부두, 다리, 등대와 부표, 공간 색인
sim/ 선박과 장애물의 충돌 판정, 전복과 부서지는 파도의 연출
ui/ 지도, 레이더, 계기판과 원형 계기, 나침반, 환경 패널, 역할별 메뉴, 시작 화면
dev/ 개발 서버에서만 사용하는 시작 조건
scripts/ 요트 모델을 생성하는 스크립트, 해안선과 지명, 해상 표지 자료를 내려받는 스크립트
lib/ 스크립트들이 함께 사용하는 Overpass 요청, 도형 계산, 운영 구역
의존의 방향은 한쪽으로만 흐릅니다. geo와 core는 다른 폴더를 참조하지 않고, world와 ship은 서로를 참조하지 않으며, 둘을 함께 다루는 충돌 판정은 sim에, 전체를 연결하는 일은 main.ts에 둡니다.
한 프레임은 다음의 순서로 처리됩니다.
입력 ─▶ 물리 (1/60초 고정 간격)
├ 운동 모델: 추력, 저항, 타, 풍압
└ 충돌 판정: 구역의 경계, 육지, 교각, 다리 밑의 높이, 방파제와 부두, 해상 표지
─▶ 렌더링 (화면의 주사율)
├ 프레임 시간 → 그리는 해상도 조절
├ 날씨: 고른 단계 안에서 풍속과 너울을 변화, 취주 거리 측정
├ 바람과 너울 → 파도의 목록 갱신
├ 파도와 바람 → 선체의 상하 움직임과 기울기, 횡동요, 전복 판정
├ 지형 타일의 불러오기와 해제
├ 항적 지도 그리기
├ 태양 → 조명, 하늘, 연무
└ 장면 그리기, 표시 장치 갱신
좌표 규약은 프로젝트 전체에서 동일합니다. 길이의 단위는 미터이고, 월드의 +X는 동쪽, +Y는 위쪽, -Z는 북쪽이며, 방위는 북쪽을 0으로 하는 시계 방향의 각도입니다.
문제: 지도 타일은 웹 메르카토르 도법을 사용하는데, 이 도법은 위도가 높아질수록 거리를 늘려서 표현합니다. 물리 계산에는 실제 미터 단위가 필요합니다.
결정: 메르카토르 좌표에 기준 위도의 cos 값을 곱해서 월드 좌표로 사용했습니다. 이 방식에서는 타일이 월드에서도 축에 정렬된 정사각형으로 유지되므로, 타일을 배치하는 계산이 단순해집니다.
This is the beginning of the README. Read the rest at the original. Read the full README ↗