デコシノニッキ

ホロレンジャーの戦いの記録

HoloPlanisphereをリリースしました

www.microsoft.com

HoloPlanisphereをリリースしました! 200円! f:id:haikage1755:20180116182910p:plain:w350

こちらのアプリはHoloLensで星座を見れるアプリになります。
f:id:haikage1755:20180116171455p:plain:w350

星座の位置は、ヒッパルコス星表のデータ及び緯度と経度、時間から算出していますので実際と同じような見え方になります。
astronomy.webcrow.jp

勿論これらパラメータはアプリ内で調整可能なので3日後の星空や3時間前のロンドンの星空をシミュレートできます。(現在は緯度経度に合わせて時間を変更する機能はないので、現在の時刻と現地時間の時差を考慮する必要があります。)  f:id:haikage1755:20180116171505p:plain:w350

星の位置は赤道座標系から水平線の見える位置を4.5km先とした地平座標系に変換し、最終的にUnity用に球面座標に変換しています。星自体は単なるTextureとLightの組み合わせなので、親オブジェクトを1/10000サイズに縮小表示してあげればSphereとして星の配置の全体像を見ることができます。
f:id:haikage1755:20180116171512p:plain:w350

制作の流れ

事の発端
f:id:haikage1755:20180116173043p:plain:h250

作り始めて2日くらいでだいたいできる
f:id:haikage1755:20180116173338p:plain:w350

星がきれいそうな北の出張先でCTOにテスト運転してもらう(なにしてんの)

星座線をLineRendererで描画したらめっちゃ重かったので色々検討

FastLineRendererが爆速だった https://www.assetstore.unity3d.com/jp/#!/content/54118

キラキラさせてみる。各星にフレアを持たせて、色合いなんかは星の色からざっくり決めた。あまりにも重たいので全体表示のときしかLightはつけてないです。

もうちょっと細かい話

門外漢がノリで作ったものです。ここ間違ってるよ!とかあれば是非教えてもらいたいので全部オープンにしちゃいます。

星の位置の算出

ヒッパルコス星表の赤緯赤経を地平座標系の方位角と高さに変換します。それにあたり地方恒星時が必要になります。

    /// <summary>
    /// 地方恒星時を算出する
    /// </summary>
    /// <param name="dateTime">現在の時刻</param>
    public static TimeSpan CalculateLST(DateTime dateTime, float lng) {

        //世界標準時へ時刻変換
        var offset = DateTime.Now - DateTime.UtcNow;
        var utcTime = dateTime - offset;

        //ユリウス日
        var MJD = DateTimeToModifiedJulianDay(utcTime);
        //Debug.Log("MJD: " + MJD);

        //グリニッジ恒星時
        //var GST = 0.671262 + 1.0027379094 * MJD;
        //GST = 24 * (GST % 1);
        //var GST_h = (int)GST;
        //var GST_m = (int)((GST - GST_h) * 60);
        //var GST_s = (float)(((GST - GST_h) * 60 - GST_m) * 60);
        //Debug.LogFormat("GST: {0}:{1}:{2}", GST_h, GST_m, GST_s);

        //地方恒星時
        var LST = 0.671262 + 1.0027379094 * (MJD - 40000) + (lng / 360d);
        LST = 24 * (LST % 1);
        var LST_h = (int)LST;
        var LST_m = (int)((LST - LST_h) * 60);
        var LST_s = (int)(((LST - LST_h) * 60 - LST_m) * 60);
        Debug.LogFormat("{0}:{1}:{2}", LST_h, LST_m, LST_s);

        return new TimeSpan(LST_h, LST_m, LST_s);
    }

    /// <summary>
    /// 修正ユリアス日を算出する
    /// </summary>
    /// <param name="dateTime">変換したい日時</param>
    /// <returns></returns>
    public static double DateTimeToModifiedJulianDay(DateTime dateTime) {
        return ((dateTime.Ticks + (1721425.5 * 864000000000)) / 864000000000) - 2400000.5;
    }

地方恒星時が合っているかどうかはこちらのサイトから確認できます。http://park12.wakwak.com/~maki/lst.htm

算出した地方恒星時と現在の緯度経度から、星の赤緯赤経を地平座標系の高さと方位角に変換します。
ちょっと式が違う部分はありますが、こちらと答え合わせをしながら確認しました。
ameblo.jp

    /// <summary>
    /// 星の現在位置(方位角、高さ)を計算する
    /// </summary>
    /// <param name="LST">準ユリウス</param>
    /// <param name="declination">赤緯</param>
    /// <param name="rightAscension">赤経</param>
    /// <param name="latitude">緯度</param>
    /// <param name="longitude">経度</param>
    public static float[] CulculateStarPosition(TimeSpan LST, double latitude, double longitude, int[] declination, TimeSpan rightAscension) {
        
        //時角
        var H = LST - rightAscension;
        if (H.Ticks < 0) { H += new TimeSpan(24, 0, 0); }
        var H_degree = 15f * (H.Hours + H.Minutes / 60f + H.Seconds / 3600f);

        //赤緯
        var PN = (declination[0] < 0) ? -1 : 1;
        var delta = declination[0] + PN * declination[1] / 60f + PN * declination[2] / 3600f;

        var Rad = Math.PI / 180f;
        var sinDelta = Math.Sin(delta * Rad);
        var cosDelta = Math.Cos(delta * Rad);
        var sinPhi = Math.Sin(latitude * Rad);
        var cosPhi = Math.Cos(latitude * Rad);
        var sinH = Math.Sin(H_degree * Rad);
        var cosH = Math.Cos(H_degree * Rad);

        //Debug.LogFormat(
        //    "sinδ={0}," +
        //    "cosδ={1}," +
        //    "sinφ={2}" +
        //    "cosφ={3}" +
        //    "sinH={4}" +
        //    "cosH={5}",
        //    sinDelta,
        //    cosDelta,
        //    sinPhi,
        //    cosPhi,
        //    sinH,
        //    cosH
        //);

        //高さ
        var h = Math.Asin(sinPhi * sinDelta + cosPhi * cosDelta * cosH);
        //方位角
        var A = Math.Atan2((-cosDelta * sinH), (cosPhi * sinDelta - sinPhi * cosDelta * cosH));

        h /= Rad; A /= Rad;

        Debug.LogFormat(
            "H = {0} δ={1} ,h={2} A = {3}",
            H_degree, delta, h, A);

        return new float[] { (float)h, (float)A };
    }

最後に、星の位置を球面座標系に変換してその位置に星を配置してあげます。

    /// <summary>
    /// 方位角と仰角を球面座標に変換する
    /// </summary>
    /// <param name="height">高さ</param>
    /// <param name="azimuth">方位角</param>
    /// <param name="holizontalDistance">地平線までの距離</param>>
    public static Vector3 DegToUnityPosition(float height, float azimuth,  float horizontalDistance) {
        return horizontalDistance * new Vector3(
             Mathf.Cos(height * Mathf.Deg2Rad) * Mathf.Sin(azimuth * Mathf.Deg2Rad),
             Mathf.Sin(height * Mathf.Deg2Rad),
             Mathf.Cos(height * Mathf.Deg2Rad) * Mathf.Cos(azimuth * Mathf.Deg2Rad)
            );
    }

AstroCommonsさんでは、星座の位置も赤緯赤経で提供してくださっているので、表示している星座の文字も同様の方法で計算して配置しています。

星の温度

BV色指標からざっくりとした値を決めています。

    /// <summary>
    /// B-V 色指標から温度を概算
    /// </summary>
    /// <param name="BV"></param>
    /// <returns></returns>
    public static float GetStarTempareture(float BV) {
        if (BV < -0.31) {
            return 34000;
        }
        else if (-0.31 < BV && BV < -0.24) {
            return 34000f - Mathf.Lerp(-0.31f, -0.24f, BV) * (34000f - 23000f);
        }
        else if (-0.24 < BV && BV < -0.20) {
            return 23000f - Mathf.Lerp(-0.24f, -0.20f, BV) * (23000f - 18500f);
        }
        else if (-0.20 < BV && BV < -0.12) {
            return 18500f - Mathf.Lerp(-0.20f, -0.12f, BV) * (18500f - 13000f);
        }
        else if (-0.12 < BV && BV < 0) {
            return 13000f - Mathf.Lerp(-0.12f, 0f, BV) * (13000f - 9500f);
        }
        else if (0 < BV && BV < 0.15) {
            return 9500f - Mathf.Lerp(0f, -0.15f, BV) * (9500f - 8500f);
        }
        else if (0.15 < BV && BV < 0.29) {
            return 8500f - Mathf.Lerp(0.15f, -0.29f, BV) * (8500f - 7300f);
        }
        else if (0.29 < BV && BV < 0.42) {
            return 7300f - Mathf.Lerp(0.29f, 0.42f, BV) * (7300f - 6600f);
        }
        else if (0.42 < BV && BV < 0.58) {
            return 6600f - Mathf.Lerp(0.42f, 0.58f, BV) * (6600f - 5900f);
        }
        else if (0.58 < BV && BV < 0.69) {
            return 5900f - Mathf.Lerp(0.58f, 0.69f, BV) * (5900f - 5600f);
        }
        else if (0.69 < BV && BV < 0.85) {
            return 5600f - Mathf.Lerp(0.69f, 0.85f, BV) * (5600f - 5100f);
        }
        else if (0.85 < BV && BV < 1.16) {
            return 5100f - Mathf.Lerp(0.85f, 1.16f, BV) * (5100f - 4200f);
        }
        else if (1.16 < BV && BV < 1.42) {
            return 4200f - Mathf.Lerp(1.16f, 1.42f, BV) * (4200f - 3700f);
        }
        else if (1.42 < BV && BV < 1.61) {
            return 3700f - Mathf.Lerp(1.42f, 1.61f, BV) * (3700f - 3000f);
        }
        else {
            return 3000f;
        }
    }

星の色

泥臭いです。星の色は温度からそう簡単にRGBに変換できないみたいなのでごり押しです。
Groovy Sky - 空のQA - はてなBOX 星編

    /// <summary>
    /// 星の色を取得する
    /// </summary>
    /// <param name="temperature">星の温度</param>
    /// <returns></returns>
    public static Color GetStarColor(float temperature) {
        if (temperature < 3500f) {
            return Color.Lerp(Color.red, new Color(1f, 165f / 255f, 0), (temperature - 3000) / (3500 - 3000));
        }
        else if (3500f < temperature && temperature < 6000f) {
            return Color.Lerp(new Color(1f, 165f / 255f, 0), Color.yellow, (temperature - 3500) / (6000 - 3500));
        }
        else if (6000f < temperature && temperature < 7000f) {
            return Color.Lerp(Color.yellow, Color.white, (temperature - 6000) / (7000 - 6000));
        }
        else if (7000f < temperature && temperature < 10000f) {
            return Color.Lerp(Color.white, Color.cyan, (temperature - 7000) / (10000 - 7000));
        }
        else {
            return Color.Lerp(Color.cyan, new Color(30 / 255f, 80f / 255f, 1f), (temperature - 10000) / (25000 - 10000));
        }

星の見かけ上の大きさ

実際にはそのままの大きさで表示するとほぼ点で見えにくいのでスケーリングはしています。
星の見かけ上の大きさを求めるには3ステップです。絶対等級を算出、星の直径を算出、見かけの大きさの算出です。 星の半径を求める

    /// <summary>
    /// 絶対等級を算出する
    /// </summary>
    /// <param name="m">視等級</param>
    /// <param name="p">年周視差</param>
    /// <returns></returns>
    public static double GetStarMagnitude(double m, double p) {
        return m - 5 * Math.Log(1 / p) + 5;
    }

    /// <summary>
    /// 星の直径を算出する
    /// </summary>
    /// <param name="starTemp">星の温度</param>
    /// <param name="pm">絶対等級</param>
    /// <returns></returns>
    public static double GetStarDiameter(float starTemp, float pm) {
        return Math.Pow(5800 / starTemp, 2) * Math.Pow(0.4 * pm, 1 / 2) * 2;
    }

    /// <summary>
    /// 星の見かけの大きさを算出する
    /// </summary>
    /// <param name="diameter">星の直径</param>
    /// <param name="distance">星の距離</param>
    /// <returns></returns>
    public static float GetApparentStarSize(float diameter, float distance) {
        return 2 * Mathf.Atan2(diameter, 2 * distance);
    }

星座線を描画する

こちらもAstroCommonsさんが星座線のデータを提供しています。HIPと呼ばれる星を管理する番号があり、線ごとにその始点と終点となる星のHIPを持っています。アプリないではこのHIPをキーとしたGameObjectのDictionaryをつくってあるのでそれらの位置座標をもってきて描画するだけです。FastLineRendererはLineRendererのように親オブジェクトに追従するといったことはできないので、親オブジェクトが動くたびに再描画する必要があるので注意です。とはいえ、LineRendererよりは軽快に動く印象です。

   void DrawFastStarLines() {
        //ラインのリセット
        lineRenderer.Reset();
        //星座線の始点と終点を決める
        var points = new List<Vector3>();
        for (int i = 0; i < lineDatas.Count; i++) {
            points.Add(stars[lineDatas[i].HIP_0].transform.position);
            points.Add(stars[lineDatas[i].HIP_1].transform.position);
        }
        
        FastLineRendererProperties props = new FastLineRendererProperties();
        
        //線を追加する
        lineRenderer.AddLines(props, points, (FastLineRendererProperties _props) => {
           //親オブジェクトのサイズに合わせて線の太さを変更する
           props.Radius = transform.localScale.x;
        }, true, true);

        //描画
        lineRenderer.Apply();
    }

以上アプリの中身でした。他にもメシエ天体がみたいとか、天の川がほしいとか、ガイドがほしいとか、星座の絵がほしいとか色々要望はいただいているんですけど、開発もタダではないのでアプリ買ってくれるととっても嬉しいな!(200円)

おまけ
いらすとやでやってみようとした結果

アプリをやってもらった反応
体験者1「すごい!きれい!星お好きなんですか?」
わたし「いや、ぜんぜん」
体験者1「あっ、そうなんですか…」

体験者2「あーーこれ球面にだしているんですね。いいっすね。」
わたし「いえ、全部計算してます」
体験者2「は?」

体験者3「こんなもの作るなんてロマンチストですね!」
わたし「FastLineRendererめっちゃ軽快に動くの!そっち感動して!」

[デコシノニッキ]は、Amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、Amazonアソシエイト・プログラムの参加者です。」