[플라네타리움 구현기] 5. 다듬기

  1. 1. 목차
  2. 2. 별 겉보기등급 반영
  3. 3. 부가기능
    1. 3.1. 실시간 업데이트
    2. 3.2. 창 크기 조절 대응
  4. 4. 반짝이는 별 만들기
  5. 5. 마치며

목차

핵심 기능은 다 구현이 되었기 때문에, 부가적인 요소를 생각해봅시다.

별 겉보기등급 반영

여러가지 함수를 테스트해봤지만 1차식을 사용했을 때 가장 그럴싸한 결과가 나오는 것을 확인할 수 있었습니다.

5등급정도의 별이 1px정도의 크기를 가지고, 0등급정도의 별이 7px정도의 크기를 가지게 만들어줍시다. 절대적인 공식은 없고, 보기에 적절하게 조절된 것 같을때가지 변수를 조절해주면 됩니다.

1
2
// brightness : mag -> size
const brightness = mag => Math.min(6.0, (-11/9) * mag + (21/3));;

별 밝기 조절

필요한 별이 눈에 더 띄도록 잘 조절되었습니다.

부가기능

이렇게 만들어진 플라네타리움을 가지고 놀다가 다음과 같은 기능이 있으면 좋을 것 같다는 생각이 듭니다.

  • 실시간 업데이트: 현재는 처음 접속한 시간의 밤하늘 그대로 고정되어있습니다.
  • 창 크기 조절에 따른 업데이트: 처음 페이지를 로드할때만 위치가 결정됩니다.

실시간 투영과 창 크기 조절에 따른 업데이트 로직은 plugin.js에 따로 분리해서 넣어줍니다.

실시간 투영과 창 크기 조절에 따른 업데이트 둘 다 업데이트 로직을 쓰기 때문에, 업데이트를 하는 로직을 따로 빼서 메서드를 만들어줍시다.

1
2
3
4
5
6
7
8
9
10
11
12
13
/*
* 0. Screen update logic
*/
const update = () => {
console.log(`[Plugin::update] Updating...`);
this.LST = getLocalSidereal(this.geo.lon);
console.log(`[Plugin::update] Reconverting: RaDec -> AzAlt`)
convertAllEquatorial();
project(`GroundMap`);
console.log(`[Plugin::update] Update done.`);

return;
};

해당 로직은 debug.js(지금은 main.js로 rename함)에 있는 로직의 일부를 재사용하는것으로 충분합니다. 창을 새로고짐할때 실행되는 main.js(전 debug.js)의 로직은 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
init()
.then(async (stars) => await (load(stars)))
.then(async () => this.geo = await getLocalGeographic())
.then(async () => this.LST = getLocalSidereal(this.geo.lon))
.then(async () => console.log(`[Core] Converting: RaDec -> AzAlt`))
.then(async () => convertAllEquatorial())
.then(async () => project(`GroundMap`))
.then(async () => console.log(`[Core] Initial rendering is done.`))
.then(async () => followSizeChange())
.then(async () => continuousUpdate())
.catch(err => console.log(err));

update()를 호출할 경우 F5를 눌러 창을 새로고침 할 필요 없이 다시 투영을 진행합니다.

실시간 업데이트

위에서 구현한 update() 메서드를 setInterval에 넣어서 실행해주면 됩니다.

1
2
3
4
5
6
7
8
/*
* 0.0. Continuous update
*/
const continuousUpdate = (minute = 1) => {
console.log(`[Plugin::continuousUpdate] Activated.`)
setInterval(update, 1000 * 60 * minute);
}

창 크기 조절 대응

window.addEventListener("resize", windowSizeMonitor)처럼 이벤트가 발생할때마다 다시 업데이트를 해주는 메서드를 리스너를 이용해서 묶어주면 됩니다.

하지만 화면 모서리를 드래그하면서 크기를 키울 때 매 순간 resize 이벤트가 발생하기 때문에, 성능 이슈를 피하기 위해 적절한 timeout을 두었습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*
* 0.1. Size change detection and update
*/
let timerFlag = false;

const windowSizeMonitor = () => {
if (timerFlag === false)
{
console.log(`[Plugin::followSizeChange] Size change detected.`)
console.log()
timerFlag = true;
setTimeout(() => {
innerWidth = window.innerWidth;
innerHeight = window.innerHeight;

update();

timerFlag = false;
console.log(`[Plugin::followSizeChange] Finished routine.`)
}, 50);
}
else
{
return;
}
}

const followSizeChange = () => {
console.log(`[Plugin::followSizeChange] Activated.`);
window.addEventListener("resize", windowSizeMonitor);
};

이제 (준)실시간으로 밤하늘을 투영할 수 있고, 창의 크기가 바뀌어도 대응할 수 있습니다.

반짝이는 별 만들기

별을 반짝이게 만드려면 픽셀의 크기를 줄이거나 해당 element의 opacity를 줄이는 방식으로 구현할 수 있습니다. 이번에는 opacity를 줄이는 방식을 css의 animation을 사용하여 구현했습니다.

1
2
3
4
5
@keyframes flicker {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}

반짝이는 animation의 이름을 flicker라고 일단 이름지었습니다. 해당 animation은 처음(0%)에 불투명(opacity: 1)하다가 중간쯤(50%)에 반투명(opacity: 0.5)하다가 다시 마지막(100%)에 불투명(opacity:1 )해집니다.

모든 별이 똑같은 주기로 반짝거리면 보기 이상합니다. 따라서, 별마다 반짝거리는 주기를 다르게 해주는 것이 좋습니다. stars.js에 가서 load()를 수정해줍시다. 더 우아한 방법이 있겠지만, 우선은 머리속에 떠오르는대로 구현합시다.

load()elem.setAttribute('aka', star.proper); 뒤에 다음 내용을 추가해줍시다.

1
elem.style.animation = `flicker ${Math.random() * 2 + 2}s infinite alternate`;

이제 모든 별이 앞에서 구현한 flicker animation을 가지는데 주기는 랜덤이고 계속 반복됩니다. 여러분이 생각하는 그 은은하게 반짝이는 효과가 맞습니다.

마치며

최종 결과

이제 필요한 모든 핵심 기능에 대한 구현을 끝냈습니다.

글에서 다루고자 했던 내용을 다 다루었기 때문에 이 글은 여기서 마치려고 합니다. 추가적으로 생각해보면 좋을 아이디어는 다음과 같습니다.

  • 성능 개선: HTML Canvas등을 이용하여 렌더링 과정을 더 단축할 수 있습니다.
  • 기능 추가: hover의 경우 별자리에 선을 이어주거나, 이름이 있는 별의 경우에는 이름을 띄워줄 수 있습니다.
  • 코드 개선: 작동하는 코드를 짜는것을 우선으로 했기 때문에, 코드의 가독성을 개선하고 재사용 가능하게 다시 코드를 짤 필요가 있습니다.

글은 이정도로 마치려고 합니다. 이 글에 새롭게 업데이트 된 내용이나 추가로 구현된 로직이 있을 경우 글을 이어나가겠습니다.