遷移する可能性のあるアニメーションのステートは遷移中である可能性を考慮しつつ保存しなければならない(当たり前だ)

アニメーションのポーズ機能を作りたくて、

stateInfo = animator.GetCurrentAnimatorStateInfo(targetLayer);

みたいな処理を書き、

animator.Play(stateInfo.fullPathHash, targetLayer, stateInfo.normalizedTime);

みたいにして使っていた。しかし、デバッグ中に、アニメーションが正常に再生されない問題が発生した。

原因を探ると、どうもTriggerで wait -> move みたいに遷移を作っていたのがうまく動いていないみたいだった。

今回はwaitからmoveへの繊維は2フレーム程度で終わるようにしていたのだが、利用者がボタンを連打したりすることで、waitからmoveへの遷移中にポーズ処理が発行される場合があることがわかった。

対策は以下

if(animator.IsInTransition(targetLayer))
    {
        stateInfo[targetLayer] = animator.GetNextAnimatorStateInfo(targetLayer);
    }
    else
    {
        stateInfo[targetLayer] = animator.GetCurrentAnimatorStateInfo(targetLayer);
    }

アニメーションが遷移中は遷移先のステートを保存するようにした。今の所これで同様の不具合は出ていないので大丈夫だと思う。

2019年やったこと・考えていたこととか

こんばんは。xRArchiが末端、建築にあまり明るくない勢、麻木浅葱です。

今年は割と仕事が忙しく、自身の実績として書けるようなことが殆どないので、仕事でやったことで一応名前が出ている奴をボカしつつ、内容について触れていきたいと思います。

某子ども向け科学雑誌の付録AR

こちらは昨年末から本年明けにかけてやった仕事で、某無人探査機が探査対象の小惑星に対し実行する作業の一部始終をARとして見せるというような、シンプルかつ教育的な内容です。

某アーティストMV用AR

こちらも昨年末あたりから本年春にかけてやっていた仕事です。主には調整が長引いた形です。

こちらはARKitの平面検出を利用し、街中にアーティスト本人が手書き(Tilt Brush)した歌詞を配置。それを音楽に合わせて追いかけるというような内容になっています。

とにかく現地での調整がひたすらに長く、つらかったです。次似たような案件があれば、最初に予定される進行ルートとその周辺をスキャニング・ないしはマッピングし、カーブやスロープ・階段・高低差などを3D化するでしょう。

このあたりはもしかしたらArchiにかかわってくる部分かもしれませんね。

また、屋外ならではの問題ですが、日が昇ってくるとアスファルトや舗装道・タイル張りの道なんかはトラッキングが外れやすくなり、長距離の移動や撮影といった用途では厳しくなってきます。こういうこともあってテストや撮影が早朝・夕方にしか行えなかったのも大変でした。

仕事でxR周りに携わっていて、今年の所感

今年は大きな変動があったというよりは、よりxR・特にARが盤石に求められるようになってきたなという印象です。 VRがいらなくなったとかというよりは、Vuforia, ARKit, ARCore, そして8th Wallに代表されるWebARの台頭によりARが再び脚光を浴びているように思います(その前はスノーとかの顔ARとかソニースマホとかにあったやつ)。

特にスマホARの熱はぐんぐんと増してきているように思います。 何せ、今先進国で一般に普及しているようなスマホなら半分以上はARKitやARCoreなんかは動くでしょうし、マーカーARに限ればほとんどのスマホで問題なく動くでしょう。結果として液晶を通さねば見れないという点で大きな制約性のあるものではありますが、AR眼鏡の開発・普及も伴って今後よりARは重要な産業になってくるのではと思っております。

とはいえ、まだまだスマホARは万能ではないです。 先ほど書いたように、液晶を通さないと見れない=VRほどの没入感・現実感がないというのもそうですし、端末によってできる表現の差があったり、トラッキング精度にまだ不安があったり…。

あと、これはARの問題ではないのですが、『ARをやりたい』が先行してしまって『ARで何をするか』が決まらないまま走り出してしまってグダグダ…という話も耳にするので、気を付けたいところですね。

来年に向けて、そのほか雑感

これ書くにあたって 今年何もやってねえな!!!!!!!!!!!! ってめっちゃ思ったので今年残した1ケ月と来年は、もうちょっと何か作りたいですね。

とか毎年言ってる気もしますが…。みんなどうやって時間作っとるんや…。

さて、ほかのxRArchiメンバーに比べて大分お粗末な出来になってしまいましたが今の私にはこれが限界です。 来年こそはみんなをあっと言わせる記事が書けるようになりたいですね。 ではさよ~なら~。良いお年を。

【Unity】Vuforiaを使うとCanvasの 'Screen Space - Camera' が使えなくなるので対処法を考えた

今作っているアプリで、画面の左下に、Cameraにレンダリングされる UI(アプリロゴ)を表示する必要があった(Render textureとして取得したかった)。

当然、普通ならCanvasのRender ModeをScreen Space - Cameraに設定しておけば良い。 しかし、何度やってもうまく描画されず、なんでやねんと調べてみると、どうやら Vuforia Behaviour を適用している状態のカメラをRender Cameraに設定すると、WidthとかRightとか、そういった値が壊れてしまうことが分かった。

当初はARCameraの階下に別のCameraを配置して、レンダリングはそっちにやらせれば…などと思っていたのだが、処理が重くなりそうなのと、肝心なAR周りの描画がちょっと不安定になったりしたのでやめた。

完成図

手法

結果としては、今回は、カメラの near clipping plane にあたる位置を取得し、それをもとにQuadを配置する手法をとった。

当初はポストエフェクト的にロゴを表示する方法なども考えたが、ポストエフェクトではCameraがレンダリングした画像としては取得できないことに気づいてこちらもやめた。

実装

まず作ったのは、near clip planeの取得処理。

これはそのものを取得することは出来ないので、 Camera.ScreenToWorldPoint を使って取得する。 具体的には以下

AsagiHandyScriptSingle/GetScreenWorldPoint.cs at master · M-T-Asagi/AsagiHandyScriptSingle · GitHub

using UnityEngine;

namespace AsagiHandyScripts
{
    public static class GetScreenWorldPoint
    {
        public struct ScreenWorldPoints
        {
            public Vector3 topLeft;
            public Vector3 topRight;
            public Vector3 bottomLeft;
            public Vector3 bottomRight;

            public ScreenWorldPoints(Vector3 tl, Vector3 tr, Vector3 bl, Vector3 br)
            {
                topLeft = tl;
                topRight = tr;
                bottomLeft = bl;
                bottomRight = br;
            }
        }

        public static ScreenWorldPoints GetPoints(Camera eye)
        {
            Vector3 UPSIDE_DOWN;
            UPSIDE_DOWN.x = 1;
            UPSIDE_DOWN.y = -1;
            UPSIDE_DOWN.z = 1;

            ScreenWorldPoints points;
            points.bottomLeft = eye.ScreenToWorldPoint(new Vector3(0, 0, eye.nearClipPlane));
            points.topLeft = eye.ScreenToWorldPoint(new Vector3(0, Screen.height, eye.nearClipPlane));
            points.topRight = eye.ScreenToWorldPoint(new Vector3(Screen.width, Screen.height, eye.nearClipPlane));
            points.bottomRight = eye.ScreenToWorldPoint(new Vector3(Screen.width, 0, eye.nearClipPlane));
            return points;
        }
    }
}

のように書いた。 画面のそれぞれ端点をNearClipの距離に合わせて取得する。

次に書いたのはQuadの配置とスケーリング処理で、

以下のように書いた

private void Update()
{
    GetScreenWorldPoint.ScreenWorldPoints points = GetScreenWorldPoint.GetPoints(camera);

    Vector3 newScale;
    newScale.x = Vector3.Distance(points.topLeft, points.topRight);
    newScale.y = newScale.x;
    newScale.z = 1;
    quad.localScale = newScale;

    Vector3 newPos = camera.transform.position;
    newPos += camera.transform.forward * (camera.nearClipPlane - 1f);
    newPos += -(points.topLeft - points.bottomLeft) * 0.5f + camera.transform.up * newScale.y * 0.5f;
    quad.position = newPos;
}

Scale部分の処理

Vector3 newScale;
newScale.x = Vector3.Distance(points.topLeft, points.topRight);
newScale.y = newScale.x;
newScale.z = 1;
quad.localScale = newScale;

今回、の目的は「ロゴを左下に表示したい」と「画面の大きさに合わせてスケールさせたい」の二点だったので、上記のような処理になった。

表示する画像はただのロゴ画像ではなく、背景透過された正方形の画像の左下にロゴが配置されている、といったもので、たとえば画面が正方形であればこの処理だけで画面の左下にロゴが配置できるものになった。

もし、そういった画像が用意できない、あるいはスクリプトだけで処理を完結させたい場合は、これを

newScale.x = Vector3.Distance(points.topLeft, points.topRight) * 0.25f;
newScale.y = (newScale.x / originX) * originY;

のように書く必要がある。

Position部分の処理

こちらは、

Vector3 newPos = camera.transform.position; でQuadの位置をカメラにそろえ

newPos += camera.transform.forward * (baseSettings.Eye.nearClipPlane - 1f); でnearClipPlaneの位置にQuadをそろえ

newPos += -(points.topLeft - points.bottomLeft) * 0.5f + camera.transform.up * newScale.y * 0.5f; でQuadを画面下部に固定している。

気を付けたいのはnear clip planeの位置に合わせている部分で、最後に -1f などしているように、少しばかりカメラ側に寄せないとVuforiaのAR(カメラ映像)とぶつかる。

おわり

UnityでMissingになっているScriptを特定する

作業をしようと思ったら Missing の文字が。多分リファクタリングの過程で削除したスクリプトの残滓だと思うので放置しても良いが、重要な何かだったら困るので確認をする。

guidを特定する

対称のGameObjectが配置されているシーンファイルをVisualStudioなどで開き、ファイル内検索などを使って対称のGameObjectの項へ移動する。

GameObjectの名前はシーンファイルに記述されているので、GameObjectの名前で検索すれば開けるはず。

Generalという名前のオブジェクトはGeneralで検索すると出る
こんな感じ

GameObjectの情報が見れたら、その下は次のGameObjectが出てくるまで対象のGameObjectに関する情報になっているので、 MonoBehaviour: という記述を追っていく。

GameObjectにスクリプトをいくつかアタッチしている場合は、同数のMonoBehaviourに関する項目があるので、それぞれの

m_Script: {fileID: ******, guid: ***************, type: *}

という記述を見る。

これで、アタッチされているすべてのスクリプトのguidが追える

ファイルの存在確認

この時点で、GameObjectに3つのスクリプトをアタッチしていたのであれば、3つのguidが取得できていることになる。

これを、プロジェクトに追加されているすべてのスクリプトのmetaファイルに記述されている、guidと比較していく。

方法はなんでもよくて、VSの機能を使うなり、エクスプローラーの機能を使うなり、Linux, UNIX系なら find ./Assets/Scripts -type f -name "*.meta" | xargs grep "guid" などすれば全スクリプトのguid一覧が見れるので検索するなりすればよい。

ファイルがある場合

上記までの手順でguidの一致が確認できた場合は、アタッチされているはずのスクリプトが何らかの理由でMissingになってしまっているということなので、原因を探して解消すると良い。

ファイルがない場合

ファイルがない場合考えられる大きな可能性は2つで、

1つはファイルを削除してしまっている場合。

もう1つはファイルをリネームするなどしてguidが変わっている場合。

どちらも、Gitを使っていれば git log --diff-filter=D --summarygit log --diff-filter=R --summary などで終えるので、直近の変更を確認してみると良い。

変更をさかのぼるなどしてguidの一致を確認できるはずだ。

.NET Core 3.0が正式リリースされたのでBlazor Server触ってみた②

せっかく触ってみたので、ページの追加くらいしてみようと思っていじってみた。

最初はプロジェクトの Pages ディレクトリ下に適当な Test のようなディレクトリを作成し、VSの右クリックメニューから 追加 > Razorページ などしてページを作成したのだが、何か違う気がしたのでやり直した。

結局チュートリアルを参考にMovieモデルを追加する形に落ち着いた。

チュートリアル: 

ASP.NET Core での Razor ページ アプリへのモデルの追加 | Microsoft Docs

おおよそはチュートリアル通りにやればよいのだが、プロジェクトの作成に際して何か設定を間違えたのか、作成したMovies以下のページにスタイルが適用されていない。

レンダリングされたページのHTMLを見てると、とか<HEAD>とかといった必須そうな項目が何1つない。 どうやらページのベースになるレイアウトっぽいものが読み込まれていないようだ。

色々見てみる

とにかく、BlazorとかRazorとか触るのが初めてなので構造がわからない。 もしかしたらそのうち開設されるかもとチュートリアルを一通り流して見るが、特に記述なし。

.cs とか .cshtml とか .razor とか書かれているファイルを片っ端から覗いていく。

関係のありそうなファイル

一通り眺めて関係ありそうだなと思ったのが _Host.cshtmlStartup.cs の2つ。

_Host.cshtml は

@page "/"
@namespace BlazorTest001.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>BlazorTest001</title>
    <base href="~/" />
    <link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
    <link href="css/site.css" rel="stylesheet" />
</head>
<body>
    <app>
        @(await Html.RenderComponentAsync<App>(RenderMode.ServerPrerendered))
    </app>

    <script src="_framework/blazor.server.js"></script>
</body>
</html>

といった様子で、まさに求めていたベースっぽいものが書かれている。ほかにこれっぽい記述のあるファイルはなかったので、デフォルトで出てくるようなHello world!ページは恐らくこれを読み込んでいるのだろう。

Startup.csチュートリアルなんかでも軽く触れられていたが、実行時の初期化処理を担っているようで、 endpoints.MapFallbackToPage("/_Host"); といった記述があったので、関係ありそうだと踏んだ。

テンプレートの適用されているファイル

実験的に Pages > Movies 以下に、`index.razor をコピーしておいてみる。

indextest.razor のように改名し、内部の @page を修正して実行すると、こちらはきれいに表示される。 どうやら、Moviesページがうまく出ないのは .cshtml.razor とは違う流れでレンダリングされているのが原因らしい。

_ViewStart.cshtml

ほとほと困り果てていろいろ触っていると、スキャフォールディングファイルの作成をするダイアログの中に レイアウトページを使用する > Razor _viewstart ファイルで設定されている場合は空のままにしてください といった記述を見つけた。

察するに新規にビューを作成する際はテンプレートとなるレイアウトを選ぶか、 viewstart なるファイルの設定に従うかを選べるようだ。 しかし、プロジェクト内を検索してみても、 viewstart ファイルは見つからない。どうやら存在しないようだ。

そこで見つけたのがこちらのページ asp.net mvc - _ViewStart.cshtml not found to render embedded cshtml - Stack Overflow

ないなら作ればいいじゃないということのようで、Pages下に作成し、以下のように記述した

@{ 
    Layout = "~/Shared/_Layout.cshtml";
}

同様に _Layout.cshtml ファイルも Shared ディレクトリ内に作成し、 _Host.cshtml の内容をコピーした。

表示できた

改めてページを表示してみると、見事レンダリングは成功していた。

レンダリング成功
画像は残る問題解決後のもの
スタイルシートの設定が違うのか、若干見本と違う気がするが、些細な差だろう。

新たな問題

今度は、 Movies/Create にアクセスすると InvalidOperationException: The following sections have been defined but have not been rendered by the page at '/Shared/_Layout.cshtml': 'Scripts'. To ignore an unrendered section call IgnoreSection("sectionName"). というようなエラーが出るようになった。

要約すると Scriptsセクションが定義されているのにレンダリングされていません ということらしい。

こちらも検索すると @RenderSection("Scripts", false); のように書けばよいことがわかるので、そのようにする。


さしあたって本日はここまで。

とりあえずこれでプロジェクトの作成→モデル定義→ビューの確認までできるようになったことになる。 実はまだ私の環境だとCreateの操作が反映されないなどの問題があるので、追って挑戦したい。

.NET Core 3.0が正式リリースされたのでBlazor Server触ってみた

知り合いとWebサービス作らねえかって話になって、最近は何か面白そうなのあるのかなと探しているうちにBlazorを見つけた。 つい先日までBlazorの基盤である.NET Core 3.0がPreview段階だったので、まだ使えないかな~と思っていたがまさかの今日、 .NET Core 3.0が正式リリースされたとのことで触ってみることにした。

Step1 - VS2019を入れる

まだ入ってない場合は こちら Visual Studio 2019 | Visual Studio からVS 2019を入れます

ASP.NET CoreとかBlazorとか書いてある奴が必要なので適宜入れましょう。

Step 2 - Docker Desktopを入れる ※必須ではない

まだ入ってない場合はこちら Docker Desktop for Mac and Windows | Docker からDocker Desktopを入れます

Step 3 - Hyper-Vを使える状態にする ※必須ではない

まだ使える状態にしていない人は使える状態にします。

Step2-3はDocker環境を使用しない人には必須ではないです

Step 4 - Dockerを起動しておく

Docker Desktopのアイコン
これを起動します

Step 5 - プロジェクトを作成する

画面右下の「新しいプロジェクトを作成する」ボタンを押す
これをクリックして
Blazorプロジェクトを作成します
検索等を使ってBlazorプロジェクトを選択
プロジェクト名などを入力している画像
プロジェクト名などを入力
Blazorサーバーアプリの設定の確認と設定
HTTPS用の構成・Dockerサポートを有効にする・Linux(OSの選択)になっていることを確認

Step 6 - プロジェクトを起動してみる

Dockerで実行ボタン
デバッグボタンがDockerボタンに代わっているのでクリック

Step 7 - Dockerを使える状態にする

※この際、Dockerを起動していなかったりするとビルドが失敗するのであらかじめ起動しておく

Docker DesktopがShare driveを要求している
Share itを押す
Dockerが権限を要求している
Dockerが権限を要求しているのでパスワードなどを入力する

Step 8 - ページにアクセスしてみる

セキュリティ警告などが出るが、たぶん大丈夫なので、証明書を信用する・証明書をインストールするなどする

多分アプリケーション実行側のSSL証明書の信頼確認
はいを押すと良さそう
多分ブラウザ側のSSL証明書のインストール確認
はいを押すと良さそう
Firefoxで出る自己署名証明書の信頼確認
無視すると良さそう
レンダリングされたブラウザの画面
Hello world!

Step 9 - ページをちょっと改造してみる

Project > Pages > Index.razor を開く

@page "/"

<h1>Hello, world!</h1>

Welcome to your new app.

のように書かれているので

@page "/"

<h1>Hello, world!</h1>
<div>
    Omae no namae ha kyoukara SEN da.
</div>
Welcome to your new app.

のように書き足す。

再起動ボタンを押すと、ページも更新される。

再起動ボタン
再起動ボタン
更新されたページ。Hello world!の下に文言が追加されている
更新されたページ。Hello world!の下に文言が追加されている


以上、終わり。 ページビューが処理部分と別に作れるのがうれしいのと、Unityとかでやってるのと同じC#で処理が書けるっぽいのがうれしそう。 今回作るサービスにマッチするかどうかはまだ分からないのでもうちょっと触ってみないとだけど、簡単なウェブアプリとかには良さそう。 とにかく使ってみたい。

GitのSubmoduleのディレクトリを動かそうとして失敗したので備忘

状況

  1. Gitレポジトリに登録したSubmoduleを移動させたい
  2. 何を思ったのか、手作業で移動させてしまった
  3. アホなのでその移動をコミットした
  4. ファミマのバナナフラッペをキメてたので、その後普通に作業をして、コミットログがいくつか溜まっている(全部Resetとかかけてやり直すの無理)
  5. 上記のようなことがあったので、Submoduleに登録していたはずのファイルがすべてレポジトリ

やりたいこと

できればつらい思いをせずに移動後のあれそれをSubmoduleとしてまた扱えるようにしたい

やったこと

参考にしたのはこちらのページ 

chaika.hatenablog.com

まずは方法1にある通りのことを一通り行った。

しかし手順7-8あたりで、書いてあるような動作をしなかった(renamedにならない)ため、この方式をあきらめる

次に方法2を見たが、Submodule自体を移動してしまっているため、方法2で示されている手順を追うことができなかった。

結論 - 手順

  1. まず現状を受け入れる
  2. ブランチを切る(よくわからない作業をするため)
  3. .gitmodules中身を 削除してコミット
  4. git rm -r your/submodule/dir を実行(ディレクトリを消したいので --cachedは使わない )し、コミット
  5. .git/config からSubmodule関連の記述を削除
  6. 作業ブランチに戻ってmerge

上記のような手順でたぶん行けます。