[Astro] #210 スマホと Cardboard で見上げるプラネタリウム(注視パネル・星の名前・場所と時刻の切り替え)
はじめに
スマホと Cardboard 型ゴーグル(PC でも動きます)で見る、PLANETARIUM の実装記録です。
- 星空の真ん中に立って、見回します(スマホの傾きセンサー。PC はドラッグ)
- 足元より少し下を見ると、時間を進める・戻すボタンが出ます。ボタンは見つめると押せます
- 星を見つめると、名前・星座・等級・色・方角・高度が下に出ます
- 場所(東京・シドニー・トロムソ)、±1 か月、表示の切り替え、太陽の通り道を、同じパネルで操作します
空の描画は、先に作った Sky Today の部品をそのまま読みます。Sky Today 側のファイルは変えていません。操作の枠は PLAZA と共通です(../common/cardboard.js、../lib/three-vrm.js)。
スクリーンショット
星を見つめると、名前が出る
足元の、時間と表示のパネル
太陽の通り道
動画
1. 構成
public/tool/mobile/planetarium/
index.html cardboard.css + planetarium.css + planetarium.js
planetarium.js 本体(先頭の CONFIG に調整値)
planetarium.css START 画面のタイトルの色だけ
public/tool/sky-today/ (読むだけ)
sky-scene.js sky-calc.js sky-dome.js(loadSkyData) astronomy.mjs
stars.json constellations.json messier.js
../../sky-today/…は、静的なimportで読みます(publicの JS を動的なimport()で読むと、Astro の dev サーバーで失敗するため)- three は
../lib/three-vrm.js(r183)のものを、createSkyScene(THREE, …)に渡します
sky = createSkyScene(THREE, {
calc: createCalc(Astronomy), data, S, pixelRatio: cb.renderer.getPixelRatio(), decor: true,
labelScale: CONFIG.labelScale, bodyScale: CONFIG.bodyScale,
onMode: () => { if (S.bg) scene.background = S.bg; },
});
scene.add(sky.root);
Sは Sky Today の状態オブジェクトです(latlonshowConsshowNamesshowMWshowMeteorssunTracksunAltなど)。sky.tickが読み書きします- 座標は x 東、y 上、z 南(北が −z)です。
sky.root.rotation.yを回すと、「正面」の方位が変わります(北 0、東 π/2、南 π、西 −π/2) - 空の構築は約 460 ms(デスクトップ)です。「読み込み中」を一度描かせてから始めます
2. 時間
const sim = { t: Date.now(), rateIdx: RATE_LIVE, live: true, headingIdx: 2, placeIdx: 0 };
// CONFIG.rates = [-3600, -600, -60, 1, 60, 600, 3600](実時間 1 秒あたりの、空の秒数)
liveのときはDate.now()に追従します。それ以外は、毎フレームdt * rateぶん進めます- 時刻の表示は
Intl.DateTimeFormat(timeZoneを場所ごとに指定)です - 速く送っている間(|rate| ≥ 600)は、太陽・月・惑星の再計算(1 回約 3 ms)を 1 秒に 10 回までにします。
sky.tickが毎フレーム再計算するので、呼ぶ前後でS.lastBodies(最後に計算した時刻)を操作して間引きます
const keep = S.lastBodies, now = performance.now(), throttled = fast && now - bodiesAt < 100;
if (throttled) S.lastBodies = t; // 「いま計算済み」に見せて、再計算を飛ばす
sky.tick(new Date(t), dt);
if (throttled) S.lastBodies = keep; else if (S.lastBodies !== keep) bodiesAt = now;
星の回転は行列なので、間引いても毎フレーム滑らかです。
3. 視線についてくるパネル
タップのメニューは、ゴーグルをつけると押せません。createGazeButton の丸いボタンを 3 段に並べます。
| 段(俯角) | ボタン |
|---|---|
| 1(−20°) | << STOP >> -1か月 +1か月 場所 |
| 2(−33°) | -1d -1h NOW +1h +1d |
| 3(−46°) | 星座 名前 銀河 流星 日の道 正面 |
<<>>は、速さを 1 段ずつ変えます。STOPは等速に戻します(時刻はそのまま)。NOWはいまの時刻に戻して LIVE にします-1d-1h+1h+1dと ±1 か月は、見つめ続けると 0.35 秒ごとに繰り返します(stepRepeat)- 3 段目は、見つめるたびに ON / OFF です。ラベルにいまの状態を出します
向きについてくる
- 水平から −12°(
panelFollowPitch)より上を見ている間、パネルの向き(yaw)は視線についてきます - 見下ろすと止まるので、ボタンを順に見つめられます
- ボタンは、カメラから 2 m の球面に、段の俯角と列の角度で置きます(
layoutPanel)
縦長の画面での縮小
PC の縦長の窓では、片目の横の視野が狭く、ボタンが画面からはみ出します。最長の行が横の視野の 8 割に収まる大きさまで、ボタンと間隔を縮めます。横長のスマホでは縮みません。
const hHalf = degrees(Math.atan(Math.tan(radians(camera.fov) / 2) * cb.aspectEye)); // 片目の横の半視野
const maxN = Math.max(...panel.rows.map((r) => r.length));
const k = Math.min(1, (hHalf * CONFIG.panelFit) / ((maxN - 1) / 2 * CONFIG.panelStepDeg + 6.5));
ラベルを小さくする
ボタンの文字の大きさは、行数と最長の行の長さから決まります。前後に空行を足すと、文字が小さくなり、丸の中に収まります。
const small = (a, b) => `\n${a}\n${b}\n`;
4. 星を見つめると名前が出る
レイキャストは使わず、視線の方向と、各天体の方向の角度で選びます。
- 視線の方向は、
sky.rootの回転(正面の方位)を戻した、地平座標(x 東、y 上、z 南)にします - 候補は、太陽・月・惑星、星雲・星団(夜で、天の川が ON のとき)、星(夜で、
pickMaxMag以下)です - 視線の中心から
pickDeg(2°)以内で、角度が最も小さいものを選びます。名前のある星、惑星は、少し選ばれやすくしてあります - 地平線の下の天体は、候補にしません
const altAz = (v) => ({
alt: Math.asin(Math.max(-1, Math.min(1, v.y))) * RAD,
az: ((Math.atan2(v.x, -v.z) * RAD) + 360) % 360, // 北 0、東 90
});
- 判定は 10 回/秒です。同じ天体を見つめ始めると、輪が出ます(0.25 秒後)。
pickHold(0.8 秒)で、下に情報が出ます。視線を外してもpickKeep(1.5 秒)は残ります - 輪が満ちる様子は、
cb.gaze.extra(0〜1)に入れると、照準の周りの輪に出ます - 時間を速く送っている間(等速より速いとき)と、足元(−10° より下)を見ている間は、判定しません
名前のない星
星のデータ(stars.json)に名前があるのは 29 個だけです。名前がない星は、星座の名前から「ぎょしゃ座の星」と出します。
- 星座線(
constellations.json)の頂点の座標は、星の座標と同じです ra.toFixed(2) + ',' + dec.toFixed(2)をキーに、星座線の頂点から、星 → 星座のMapを作ります- 3 等より明るい星は 164 個中 156 個が一致します
表示
シリウス
おおいぬ座 -1.4等 白
南東 高度 30°
- 色は、B−V 値(
colorName(bv))、方角は方位から(dirName(az))出します(どちらもsky-scene.jsの関数) - 月は、月齢と月相の名前も出します。世界に置いた HUD(
createWorldHud)に、画面の縦横比に合わせた大きさで出します
5. 場所
places: [
{ name: '東京', lat: 35.6812, lon: 139.7671, elev: 0, tz: 'Asia/Tokyo', tzLabel: 'JST', heading: 2 },
{ name: 'シドニー', lat: -33.8688, lon: 151.2093, elev: 0, tz: 'Australia/Sydney', heading: 0 },
{ name: 'トロムソ', lat: 69.6492, lon: 18.9553, elev: 0, tz: 'Europe/Oslo', heading: 2 },
],
- 「場所」ボタンを見つめるたびに、順に切り替わります
S.lat/S.lonは、毎回のsky.tickで読まれます。値を差し替えて、すぐtickを呼ぶだけです(空の作り直しは不要)- 時刻の表示は、場所の
tzのIntl.DateTimeFormatに替えます。tzLabelがない場所は、timeZoneName: 'shortOffset'のGMT+11をUTC+11にして出します - 「正面」は、その場所の
headingにします(シドニーは、北が空の高い側なので、北向きから始めます) - 見つめていた星の表示は、消します
function setPlace(i) {
sim.placeIdx = (i % n + n) % n;
const p = CONFIG.places[sim.placeIdx];
S.lat = p.lat; S.lon = p.lon; S.elev = p.elev;
fmtParts = mkFmt(p.tz);
pick.key = ''; pick.shown = null; pick.t = 0; pick.acc = 0;
setHeading(p.heading);
if (sky) { S.lastBodies = 0; sky.tick(new Date(sim.t), 0); bodiesAt = 0; }
}
6. ±1 か月
同じ日・同じ時刻のまま、月だけを動かします。月末は、移動先の月の最終日に丸めます(1/31 の次は 2/28)。
const bumpMonth = (n) => {
const d = new Date(sim.t), day = d.getUTCDate();
d.setUTCDate(1); d.setUTCMonth(d.getUTCMonth() + n);
d.setUTCDate(Math.min(day, new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth() + 1, 0)).getUTCDate()));
sim.live = false; sim.t = d.getTime();
};
- 先に日を 1 にしてから月を動かさないと、
setUTCMonthが 3/3 のように溢れます - 月は UTC で動かします。夏時間のある場所では、現地の時刻が 1 時間ずれることがあります
7. 太陽の通り道
createSkyScene の sky.setSunPath() は、S.sunTrack(高度・方位の表)の、地平線より上の部分を線にします。表を作って入れ、呼びます。
S.sunTrack = calc.track('Sun', calc.observer(S.lat, S.lon, S.elev), new Date(t - dayInfo.min * 60000), 10);
sky.setSunPath();
calc.track(body, obs, start, stepMin)は、startから 24 時間分を、stepMin分刻みで返します- 起点は、場所の現地の 0 時です。時刻の表示で求めた「0 時からの分」を引きます
- 日付か場所が変わったときだけ作り直します(キーは
場所|年月日)。OFF のときは、S.sunTrack = nullで線を消します - 季節で線の高さと長さが変わります。トロムソで極夜の日は、地平線より上の点がなく、線は出ません
8. つまずいた点
- 3 段目が地面に埋まる: 最初は −52° に置きました。カメラの高さ 1.6 m、距離 2 m だと、ボタンの高さが地面(y = 0)に近く、半分が埋まりました。−46° に直しました。段を足すときは、地面に埋まらないか見ます
- 縦長の窓で、ボタンが大きすぎる: 原因は 2 つありました。横の視野が狭いこと(3 章の縮小)と、ボタンの文字が大きいこと(
small()で空行を足して直しました) - 1/31 の次が 3/3 になる: 6 章のとおり、日を 1 にしてから月を動かします
- 日の道が出ない: 最初は OFF にしています(星座を見る邪魔になるため)。
CONFIG.sunPathで、最初から出せます
9. 制約
- 場所は 3 か所の固定です。位置情報は取りません(許可の画面が出るため)
- 実際の方位(コンパス)には合わせていません。「正面」の方位は、ボタンで決めます
- 時間を速く送っている間は、星の名前を出しません
- 星のデータは 5044 個です。名前のある星は 29 個で、あとは星座名で出します
クレジット
- 星のデータ: d3-celestial(BSD-3)
- 天体の計算: Astronomy Engine(MIT)
- 星空の描画: Sky Today(lain-lab.com)
アプリの CREDIT ボタンに、同じ内容を出します。
関連
- PLAZA(見つめると近づく VRM の広場)
- Sky Today(今日の空)
[Web Tool] Sky Today — 日の出・日の入り・月・星を、全天の 3D と VR で見る ブラウザ完結型 アプリ // PROTOCOL.LAIN
lain-lab.com の「Sky Today」(ブラウザ完結)の使い方ガイド。場所と日付の入れ方、数字の見方、空の図、1 年の日の出・日の入りの図、全天 3D の操作とキーボードショートカット、天の川・星雲・星団・流れ星、日付と 1 年のアニメ、Quest の VR・MR、時間帯の違い、対応していないもの、うまくいかないときの対処、よくある質問を解説。計算も位置情報も、外へは送信されません。
lain-lab.com
[Mobile Tool] PLAZA — 見つめると近づく VR の広場ゲーム(スマホVR・Cardboard 対応) // PROTOCOL.LAIN
スマホの傾きで見回し、顔を見つめると VRM が寄ってくる、注視ゲーム PLAZA の案内。Cardboard 型ゴーグルで遊ぶ手順、ハートの仕組み、ステージごとの人数と制限時間、足元のボタン(RETURN / SKIP / CREDIT / CONTINUE)、PC での遊び方、クレジットをまとめています。
lain-lab.com