<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ja"><title>バスキュール技術ブログ</title><subtitle>バスキュールが得意とするインタラクティブエンジニアリングを、あますことなくお届け！</subtitle><link href="https://blog.bascule.co.jp/"/><updated>2024-05-09T14:17:08+09:00</updated><author><name>bascule-dev</name></author><generator>Hugo</generator><id>hatenablog://blog/10328749687232777297</id><entry><title>宇宙に浮かぶひまわりの目からいろんな地球を見る</title><link href="https://blog.bascule.co.jp/entry/2024/05/09/141708"/><id>hatenablog://entry/6801883189102556085</id><published>2024-05-09T14:17:08+09:00</published><updated>2024-05-10T12:03:21+09:00</updated><summary type="html">こんにちは！ エンジニアの勝田（https://twitter.com/nichol76_yk）です！ 前回、武田がこんな記事↓を書いてました。 blog.bascule.co.jp 私たちが4月22日に配信した『KIBO宇宙放送局 #アースデイライブ 2024 〜丸い地球をほぼ生中継。〜 - YouTube』という番組があるのですが、 前回は企画・開発の裏側にフォーカスした記事でした。 システムの詳細は武田の記事を参考にしていただくとして、ざっと番組内容を説明すると、 気象衛星ひまわりの画像を取得して アップスケールしたり フレーム補完をしたりして ほぼリアルタイム生成された はちゃめちゃに…</summary><content type="html">&lt;p&gt;こんにちは！&#10;エンジニアの勝田（&lt;a href="https://twitter.com/nichol76_yk"&gt;https://twitter.com/nichol76_yk&lt;/a&gt;）です！&lt;/p&gt;&#10;&lt;p&gt;前回、武田がこんな記事↓を書いてました。&#10;&lt;iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.bascule.co.jp%2Fentry%2F2024%2F04%2F27%2F013120" title="22Kの地球タイムラプス映像をグリグリする - バスキュール技術ブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://blog.bascule.co.jp/entry/2024/04/27/013120"&gt;blog.bascule.co.jp&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;&lt;p&gt;私たちが4月22日に配信した『&lt;a href="https://www.youtube.com/live/2qFnXhXvo8I?si=24eeELNg5kNCOW2P"&gt;KIBO&amp;#x5B87;&amp;#x5B99;&amp;#x653E;&amp;#x9001;&amp;#x5C40; #&amp;#x30A2;&amp;#x30FC;&amp;#x30B9;&amp;#x30C7;&amp;#x30A4;&amp;#x30E9;&amp;#x30A4;&amp;#x30D6; 2024 &amp;#x301C;&amp;#x4E38;&amp;#x3044;&amp;#x5730;&amp;#x7403;&amp;#x3092;&amp;#x307B;&amp;#x307C;&amp;#x751F;&amp;#x4E2D;&amp;#x7D99;&amp;#x3002;&amp;#x301C; - YouTube&lt;/a&gt;』という番組があるのですが、&#10;前回は企画・開発の裏側にフォーカスした記事でした。&lt;/p&gt;&#10;&lt;p&gt;システムの詳細は武田の記事を参考にしていただくとして、ざっと番組内容を説明すると、&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;気象衛星ひまわりの画像を取得&lt;/strong&gt;して&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;アップスケール&lt;/strong&gt;したり&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;フレーム補完&lt;/strong&gt;をしたりして&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;ほぼリアルタイム生成&lt;/strong&gt;された&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;はちゃめちゃにきれいな地球映像&lt;/strong&gt;を&lt;strong&gt;超高解像度のLED&lt;/strong&gt;に映してみんなで見よう！&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;という感じです。&lt;/p&gt;&#10;&lt;p&gt;じゃあ実際どういう映像なの？というのを、番組では紹介できなかったものも含めてこの記事でお見せしたいと思います！&lt;/p&gt;&#10;&lt;p&gt;実際には22Kの動画を超でかい高解像度LEDに映すのでどうしても生でないと迫力は伝わらないのですが・・&lt;br/&gt;&#10;ぜひぜひ&lt;strong&gt;Youtubeの4K再生&lt;/strong&gt;で見てみてください！！&#10;　　&#10;　　&#10;　　&lt;/p&gt;&#10;&lt;h1 id="春夏秋冬"&gt;春夏秋冬&lt;/h1&gt;&#10;&lt;p&gt;&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/0t_Pkqu05oM?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen title="春夏秋冬の陽のあたり方 (横並び)"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://youtu.be/0t_Pkqu05oM?si=XhuD6XG9l9G1cr_-"&gt;youtu.be&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;&lt;p&gt;左から春・夏・秋・冬の24時間を比較した映像です。&lt;br/&gt;&#10;いかがでしょうか？&lt;br/&gt;&#10;同じ一日なのに3か月違うだけでこんなにも日の当たり方が違うのだと驚きます。&lt;/p&gt;&#10;&lt;p&gt;なんでこんなことが起こるのかについては、番組で気象予報士の井田キャスターに超わかりやすい説明をしていただいているので、ぜひご覧ください！&lt;/p&gt;&#10;&lt;p&gt;&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/2qFnXhXvo8I?start=1674&amp;feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen title="KIBO宇宙放送局 #アースデイライブ 2024 〜丸い地球をほぼ生中継。〜"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://www.youtube.com/live/2qFnXhXvo8I?si=XCUwlctiUxzpsn-B&amp;t=1674"&gt;www.youtube.com&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;&lt;h1 id="日食"&gt;日食&lt;/h1&gt;&#10;&lt;p&gt;いわゆる日食というと、↓のようなイメージの方が多いと思います。&lt;/p&gt;&#10;&lt;h4 id="地上から見た日食"&gt;地上から見た日食&lt;/h4&gt;&#10;&lt;p&gt;&lt;blockquote data-conversation="none" class="twitter-tweet" data-lang="ja"&gt;&lt;p lang="ja" dir="ltr"&gt;&lt;a href="https://twitter.com/hashtag/%E9%83%A8%E5%88%86%E6%97%A5%E9%A3%9F?src=hash&amp;amp;ref_src=twsrc%5Etfw"&gt;#部分日食&lt;/a&gt; の画像を公開しました。石垣島天文台で撮影した食が最大頃の部分日食です。&lt;a href="https://t.co/3gSpOrVh1P"&gt;https://t.co/3gSpOrVh1P&lt;/a&gt; &lt;a href="https://t.co/H6Bhz5SLmt"&gt;pic.twitter.com/H6Bhz5SLmt&lt;/a&gt;&lt;/p&gt;&amp;mdash; 国立天文台 (@prcnaoj) &lt;a href="https://twitter.com/prcnaoj/status/1648955220792340482?ref_src=twsrc%5Etfw"&gt;2023年4月20日&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt; &lt;/p&gt;&#10;&lt;p&gt;宇宙から見た日食の映像というのは見たことがない方も多いのではないでしょうか？&lt;br/&gt;&#10;ひまわりから見た日食の映像がこちらです！&lt;/p&gt;&#10;&lt;h4 id="宇宙から見える日食"&gt;宇宙から見える日食&lt;/h4&gt;&#10;&lt;p&gt;&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/2PeKap9B9BU?start=55&amp;feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen title="Solar Eclipse"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://youtu.be/2PeKap9B9BU?si=vUOWwj_FuUBvNYg2&amp;t=55"&gt;youtu.be&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;&lt;p&gt;左下から徐々に黒い影が地球を横切ってきます。&lt;br/&gt;&#10;これが&lt;strong&gt;月の影&lt;/strong&gt;なんです！！&lt;/p&gt;&#10;&lt;p&gt;こうして宇宙から見る日食と地上から見る日食、同じ現象なのに全然見え方が違うってすごいですよね。&lt;/p&gt;&#10;&lt;p&gt;日食のメカニズムについても番組で井田キャスターの説明がありますので、ぜひぜひあわせてご覧ください！&lt;/p&gt;&#10;&lt;p&gt;&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/2qFnXhXvo8I?start=1950&amp;feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen title="KIBO宇宙放送局 #アースデイライブ 2024 〜丸い地球をほぼ生中継。〜"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://www.youtube.com/live/2qFnXhXvo8I?si=9dyCqQTxs1219gWT&amp;t=1950"&gt;www.youtube.com&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;&lt;h1 id="カルマン渦列"&gt;カルマン渦列&lt;/h1&gt;&#10;&lt;p&gt;ここからは番組で紹介できなかった未公開映像となります！&lt;/p&gt;&#10;&lt;p&gt;&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/_fekGEW27qc?start=6&amp;feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen title="Karmans"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://youtu.be/_fekGEW27qc?si=1fAczu8mVB7-_lrZ&amp;t=6"&gt;youtu.be&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;&lt;p&gt;こちらは「&lt;strong&gt;カルマン渦列&lt;/strong&gt;」という、&lt;strong&gt;雲の流れが山や島にぶつかり、迂回しようと回り込んだときに起こる現象&lt;/strong&gt;です。&lt;br/&gt;&#10;&lt;strong&gt;渦ひとつが数十キロメートルほどの大きさ&lt;/strong&gt;になるので、地上から視認するのが難しいんですが、ひまわりの目はこんな巨大な自然現象も映すことができるんですね。&lt;/p&gt;&#10;&lt;h1 id="モーニンググローリー"&gt;モーニンググローリー&lt;/h1&gt;&#10;&lt;p&gt; まずはこちらをご覧ください！&lt;/p&gt;&#10;&lt;h4 id="地上から見たモーニンググローリー"&gt;地上から見たモーニンググローリー&lt;/h4&gt;&#10;&lt;p&gt;&lt;blockquote data-conversation="none" class="twitter-tweet" data-lang="ja"&gt;&lt;p lang="en" dir="ltr"&gt;Morning Glory cloud in the North. Australia&amp;#39;s Gulf of Carpentaria is the only known location where it can be observed &lt;a href="http://t.co/tBLKgvcxTf"&gt;pic.twitter.com/tBLKgvcxTf&lt;/a&gt;&lt;/p&gt;&amp;mdash; Woow pics (@photo98765) &lt;a href="https://twitter.com/photo98765/status/622784933640175617?ref_src=twsrc%5Etfw"&gt;2015年7月19日&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt; &lt;/p&gt;&#10;&lt;h4 id="ひまわりから見たモーニンググローリー"&gt;ひまわりから見たモーニンググローリー&lt;/h4&gt;&#10;&lt;p&gt;&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/m_C4qPm_FCI?start=13&amp;feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen title="Morning Glory"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://youtu.be/m_C4qPm_FCI?si=Sn0hCYVzBr0uiWXL&amp;t=13"&gt;youtu.be&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;&lt;p&gt;まるでSFやRPGのような現実離れした壮大な雲ですよね。&lt;/p&gt;&#10;&lt;h4 id="Whats-the-Story-Morning-Glory"&gt;(What's the Story) Morning Glory?&lt;/h4&gt;&#10;&lt;p&gt;&lt;strong&gt;海からの湿った風と陸の乾いた風がぶつかったときに起こる現象&lt;/strong&gt;で、オーストラリアの北部にあるカーペンタリア湾付近でよく見られるそうです。&lt;br/&gt;&#10;といってもレアな出来事なので、これを見るために世界中の雲クラスタが訪れるけれど見れずにしょぼんと帰ることも多いんだとか。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;この超かっこいい名前の語源は、朝に起こることが多いことと、その見た目の荘厳さ。&lt;/strong&gt;&lt;br/&gt;&#10;一日の始まりにこんな美しい光景を見たら「&lt;strong&gt;Morning Glory（朝の栄光）&lt;/strong&gt;」と名付けたくなる気持ちもわかる気がします。&lt;br/&gt;&#10;カルマン渦列は宇宙からしか見ることのできない現象でしたが、&lt;strong&gt;モーニンググローリーは地上、宇宙、両方から視認できます！&lt;/strong&gt;&lt;/p&gt;&#10;&lt;h1 id="森林火災"&gt;森林火災&lt;/h1&gt;&#10;&lt;p&gt;&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/VfkrhNiGx0g?start=10&amp;feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen title="Forest Fire（オーストラリアの森林火災）"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://youtu.be/VfkrhNiGx0g?si=q63S2w_wt5V5DTrN&amp;t=10"&gt;youtu.be&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;&lt;p&gt;少しショッキングな映像かもしれません。&lt;br/&gt;&#10;これは2019年から数か月に及んで続いたオーストラリアの森林火災の様子です。&lt;br/&gt;&#10;東に流れた煙が海を覆っているのがわかります。&lt;/p&gt;&#10;&lt;p&gt;この映像の中で多くのいのちや自然が失われていると思うと胸が痛みます。&lt;/p&gt;&#10;&lt;p&gt;ただそれと同時に、いいことも悪いこともひっくるめて世界中で同時にあらゆることが起こっている中で、&lt;br/&gt;&#10;それを冷静に見つめる宇宙からの目として、ありのままの事実をたくさんの人に見てもらうことって意義あることなんじゃないか？と思いました。&lt;/p&gt;&#10;&lt;h1 id="知らないことを知ること"&gt;知らないことを知ること&lt;/h1&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20240509/20240509144328.jpg" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&#10;もともと私はド文系で、理系には縁がなかった人間です。&lt;br/&gt;&#10;なのにこの一年あまりで気象や天体、自然科学についてたくさん触れることができました。&lt;br/&gt;&#10;知れば知るほど昨日とは違った目で世界が見れるということを感じられます。&lt;br/&gt;&#10;たとえば気象について知れば、ふと見上げた空に映った雲がどんなふうにできてるんだろう？と考えるきっかけになったり。&lt;br/&gt;&#10;現在だけではなく、きっと昔の人も同じように空を見上げて雲の成り立ちに思いをはせたんだろうか？と想像したり。&lt;/p&gt;&#10;&lt;p&gt;番組中で河森正治さんもおっしゃってましたが&lt;/p&gt;&#10;&lt;p&gt;「&lt;strong&gt;知識を体験に変える&lt;/strong&gt;」&lt;/p&gt;&#10;&lt;p&gt;ってそういうことなんだと思います。&lt;/p&gt;&#10;&lt;p&gt;そんな新たな体験をみなさんにお届けしたい！と思っているので、ぜひ万博に実際に見に来てください！&lt;/p&gt;&#10;&lt;p&gt;&lt;iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fshojikawamori.jp%2Fexpo2025%2F" title="EXPO 2025 大阪・関西万博 河森 正治 シグネチャーパビリオン「いのちめぐる冒険」 - SHOJI KAWAMORI | 河森正治 Official Web Site" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://shojikawamori.jp/expo2025/"&gt;shojikawamori.jp&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;</content><link rel="enclosure" href="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20240509/20240509144328.jpg" type="image/jpeg" length="0"/><author><name>bascule-dev</name></author></entry><entry><title>22Kの地球タイムラプス映像をグリグリする</title><link href="https://blog.bascule.co.jp/entry/2024/04/27/013120"/><id>hatenablog://entry/6801883189101638970</id><published>2024-04-27T01:31:20+09:00</published><updated>2024-04-30T15:53:24+09:00</updated><summary type="html">どうも！バスキュールの武田(@neko_300YEN)です。 弊社が、4/22 アースデイにやってる番組『KIBO宇宙放送局 #アースデイライブ』 ここで使われたシステムについてメモを残しておきます。 #アースデイライブ2024 www.youtube.com ⇧番組アーカイブはこちら プロジェクトがどんな建て付けなのかは、動画でも説明してる通り。 地球映像でコンテンツを作ろうと思ったきっかけ 2年前にトンガの噴火があった際に、ひまわりの衛星画像にその衝撃波が映ってるって話があって、 試しにひまわりの画像をつなぎ合わせて動画を作ってみたことから始まる。 今日のトンガの噴火(0:08~)。衛星写…</summary><content type="html">&lt;p&gt;どうも！バスキュールの武田(&lt;a href="https://twitter.com/neko_300YEN"&gt;@neko_300YEN&lt;/a&gt;)です。&lt;/p&gt;&#10;&lt;p&gt;弊社が、4/22 アースデイにやってる番組『KIBO宇宙放送局 #アースデイライブ』&lt;/p&gt;&#10;&lt;p&gt;ここで使われたシステムについてメモを残しておきます。&lt;/p&gt;&#10;&lt;h1 id="アースデイライブ2024"&gt;#アースデイライブ2024&lt;/h1&gt;&#10;&lt;p&gt;&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/2qFnXhXvo8I?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen title="KIBO宇宙放送局 #アースデイライブ 2024 〜丸い地球をほぼ生中継。〜"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://www.youtube.com/watch?v=2qFnXhXvo8I"&gt;www.youtube.com&lt;/a&gt;&lt;/cite&gt;&#10;⇧番組アーカイブはこちら&lt;/p&gt;&#10;&lt;p&gt;プロジェクトがどんな建て付けなのかは、動画でも説明してる通り。&lt;/p&gt;&#10;&lt;h2 id="地球映像でコンテンツを作ろうと思ったきっかけ"&gt;地球映像でコンテンツを作ろうと思ったきっかけ&lt;/h2&gt;&#10;&lt;p&gt;2年前にトンガの噴火があった際に、ひまわりの衛星画像にその衝撃波が映ってるって話があって、&#10;試しにひまわりの画像をつなぎ合わせて動画を作ってみたことから始まる。&lt;/p&gt;&#10;&lt;p&gt;&lt;blockquote data-conversation="none" class="twitter-tweet" data-lang="ja"&gt;&lt;p lang="ja" dir="ltr"&gt;今日のトンガの噴火(0:08~)。衛星写真で見ると地球という惑星規模で何かが起こったんだなぁという感じがする。 &lt;a href="https://t.co/cUap9w7gyg"&gt;pic.twitter.com/cUap9w7gyg&lt;/a&gt;&lt;/p&gt;&amp;mdash; 𝕟𝕖𝕜𝕠 𝟛𝟘𝟘𝕐𝔼ℕ (@neko_300YEN) &lt;a href="https://twitter.com/neko_300YEN/status/1482337751102279683?ref_src=twsrc%5Etfw"&gt;2022年1月15日&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt; &lt;/p&gt;&#10;&lt;p&gt;だいぶショッキングな映像で、不謹慎だけど自然現象の規模感とかパワーすげぇなと、圧倒されました。&lt;/p&gt;&#10;&lt;p&gt;こんなSF映画みたいなことが実際の地球上で今起こっているのかと。&lt;/p&gt;&#10;&lt;p&gt;いろいろ端折りますが、このリアルな映像をなんとか体験にしたいなぁと思ったところからコンテンツ作りが始まったわけです。&lt;/p&gt;&#10;&lt;h2 id="インタラクティブに地球映像をズームする"&gt;インタラクティブに地球映像をズームする&lt;/h2&gt;&#10;&lt;p&gt;気象衛星ひまわりの画像は11K。10分に1度撮影されています。&lt;/p&gt;&#10;&lt;p&gt;単純にこのひまわりの画像をタイムラプスとして繋ぎ合わせるだけだと映像がカクカクしてしまう。&#10;あと日本列島までズームすると、フルディスク画像だとさすがにボケる。&lt;/p&gt;&#10;&lt;p&gt;そこでひまわりの画像をもとに、機械学習を用いてフレーム補完をしたり、さらに22Kにアップスケールしたりして、高画質で滑らかな映像を作りました。&lt;/p&gt;&#10;&lt;p&gt;今回の番組では、コレを4KのLEDディスプレイに表示しています。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;※ 映像のクオリティをあげる試行錯誤については、また別の機会に&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;figure class="figure-image figure-image-fotolife" title="22K映像"&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20240426/20240426194946.png" width="1200" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;figcaption&gt;概念図&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&#10;&lt;p&gt;こんな感じのイメージで、22Kの映像をディスプレイ解像度の4Kで切り取って表示する再生システムを作りました。&lt;/p&gt;&#10;&lt;p&gt;TouchDesignerで動画再生しながら、ゲームパッドを使って地球上のいろんな場所に視点を移動したり、ズームできるようになっています。&lt;/p&gt;&#10;&lt;h2 id="システム構成"&gt;システム構成&lt;/h2&gt;&#10;&lt;p&gt;&lt;figure class="figure-image figure-image-fotolife" title="EarthDay システム図"&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20240426/20240426183439.png" width="1200" height="916" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/figure&gt;&lt;/p&gt;&#10;&lt;p&gt;特筆すべきことはないですが、LEDに出されている映像のポン出しはMIDIコンを使っています。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;AKAI MPD226&lt;/li&gt;&#10;&lt;li&gt;AKAI APC40 MKII&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;パッドに映像切り替え、ノブに映像の明るさとか色味のパラメータ調整をアサインして、適宜調整しています。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;ボタンはでかければデカい方がいいし、ノブも硬ければければ硬い方がいい。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;本番は焦るから・・・。&lt;/p&gt;&#10;&lt;p&gt;&lt;figure class="figure-image figure-image-fotolife" title="EarthDay MIDI"&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20240426/20240426184621.jpg" width="1200" height="900" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/figure&gt;&lt;/p&gt;&#10;&lt;p&gt;あと、APC40 MKIIのパッドのMIDI配列の並びがちょっと使い辛かった。&lt;/p&gt;&#10;&lt;p&gt;縦の列でチャンネル毎のパッドがあるんだとしたら下記のような並びが良かったんだけど、&#10;なんかZ型になってて、これはどういうことなんだろう？&lt;/p&gt;&#10;&lt;p&gt;MIDI初心者にはわからない何か理由があるんだろうな。　　&lt;/p&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20240427/20240427005340.png" width="1200" height="720" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;p&gt;並びを変えたAPC40 MKIIの入力をとるtoxは以下に置いておきます。&#10;MPD226も。&lt;/p&gt;&#10;&lt;p&gt;誰もいらないと思うけど。&lt;/p&gt;&#10;&lt;p&gt;⇨ &lt;a href="https://github.com/Putoshi/TouchDesigner-AKAI-MIDI"&gt;https://github.com/Putoshi/TouchDesigner-AKAI-MIDI&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;あと映像の送出はBlackMagicのDeckLink 8K Proを挿して、SDI出力。&lt;/p&gt;&#10;&lt;p&gt;滞ったWindowsアップデートやマカフィーの無料期間終了の通知が画面に出ちゃったら恥ずかしいので、ライブ系の案件ではデスクトップ拡張は避けてます。&lt;/p&gt;&#10;&lt;h2 id="映像ファイルがデカすぎる問題"&gt;映像ファイルがデカすぎる問題&lt;/h2&gt;&#10;&lt;p&gt;映像ファイルが1日分のデータで40秒くらいしかないのに、40GB近くある・・・。&lt;/p&gt;&#10;&lt;p&gt;Hapだとボケまくるので、AppleのProResで書き出して、Windows機で再生しています。&#10;Hapじゃないのにめちゃくちゃでかい。&lt;/p&gt;&#10;&lt;p&gt;コレを再生するのにボトルネックになったのは、GPUやCPUではありませんでした。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;SSDのデータ読み込みが間に合わない！！&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;40秒で40GBのファイルだから1GB/s以上の読み込み速度がないといけない。&#10;普通のSATA接続のSSDだとカタついてしまう。&#10;こういう問題は今まで初めて遭遇した。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;NVMe接続のM2.SSDにして解決。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;h2 id="TouchDesignerのFloating-Cloud-Licensesが外れる問題"&gt;TouchDesignerのFloating Cloud Licensesが外れる問題&lt;/h2&gt;&#10;&lt;p&gt;Floating Cloud Licensesというネットワーク認証のライセンスを初めて使ってみたんですが、なんかコレちょいちょい外れません？？&#10;実行中に急に無料ライセンス版に切り替わったりして、映像が22Kから1280pxになっちゃってびっくりした。&lt;/p&gt;&#10;&lt;p&gt;ネットワークの問題なのか、原因を追う時間がなかったので普通のオフラインの商用ライセンスに急遽変更。&lt;/p&gt;&#10;&lt;h2 id="ライブ系のプロジェクトで焦らないために"&gt;ライブ系のプロジェクトで焦らないために・・・。&lt;/h2&gt;&#10;&lt;p&gt;&lt;figure class="figure-image figure-image-fotolife" title="EarthDay スタジオ"&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20240426/20240426184547.jpg" width="1200" height="900" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/figure&gt;&lt;/p&gt;&#10;&lt;p&gt;ライブ系のプロジェクトでは、何かしらの都合で直前に番組の構成が変わったりとか、&lt;/p&gt;&#10;&lt;p&gt;何かしらのトラブルで番組中にシステム構成を変更したりしなきゃいけない事があったりします。&lt;/p&gt;&#10;&lt;p&gt;いくら自分の作ったものが万全だと思っていても、そういう事態になった時にはけっこう焦ります。&lt;/p&gt;&#10;&lt;p&gt;&lt;iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fkibo.space%2F" title="KIBO宇宙放送局" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://kibo.space/"&gt;kibo.space&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;&lt;p&gt;こういう宇宙系のものだと、番組スタッフやそれを実現するために動いてくれた人間が100人とか軽く超えてきてしまう。&#10;その人たちの姿を想像すると流石に震えてきたりしちゃうわけです。&lt;/p&gt;&#10;&lt;p&gt;パニックに陥った人間はびっくりするくらい頭が回らなくなるもので、&#10;前述したような、「&lt;strong&gt;ボタンはデカく！！！&lt;/strong&gt;」とか、そういう些細なことでも案外自分を救ったりします。&lt;/p&gt;&#10;&lt;p&gt;ちょっとした安心感をいっぱい作っとくと吉。&lt;/p&gt;&#10;&lt;p&gt;自分はそういう思想なので、ライブ系のプロジェクトのコントローラーにあたる部分のシステムには、&#10;TouchDesignerやMaxなどのノードベースのプログラミング環境を使うことが多いです。&lt;/p&gt;&#10;&lt;p&gt;パニクってる時は、文字がまともに読めません。&lt;/p&gt;&#10;&lt;p&gt;スタッフ用のヘッドセットから「&lt;strong&gt;なんで〇〇の映像でてないの！！？？&lt;/strong&gt;」とか、「&lt;strong&gt;音声聞こえてないぞ！！？？&lt;/strong&gt;」みたいな怒号が飛び交ってる中で、コンソールのちっちゃい文字読めますか？？&lt;/p&gt;&#10;&lt;p&gt;僕には無理です。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;ログの文字が読めるのは本番前日まで！&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;だから、文字じゃなくてビジュアルとして何が起きているかわかるノードベースのプログラミング環境を好んでいます。&#10;ビルドしないで動くものが好き。&lt;/p&gt;&#10;&lt;p&gt;なにか不具合があった際にログを追うよりも早く対応できる気がするし、本番中にプログラムを修正するのも全然怖くない！&lt;/p&gt;&#10;&lt;p&gt;あと僕はそもそも自分の書いたコードを信用できるほど、ちゃんとプログラムを書いてないという根本的な問題もある・・・。&lt;/p&gt;&#10;&lt;p&gt;以上です。乱文失礼しました！&lt;/p&gt;&#10;</content><category term="TouchDesigner" label="TouchDesigner"/><category term="Python" label="Python"/><category term="MIDI" label="MIDI"/><link rel="enclosure" href="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20240426/20240426184547.jpg" type="image/jpeg" length="0"/><author><name>bascule-dev</name></author></entry><entry><title>UnityでiPadをバーチャルカメラにしてイベント本番で使うときの問題点を解決した話をしますね</title><link href="https://blog.bascule.co.jp/entry/2022/12/02/142057"/><id>hatenablog://entry/4207112889941373767</id><published>2022-12-02T14:20:57+09:00</published><updated>2022-12-02T05:20:57+09:00</updated><summary type="html">バーチャルカメラを振り回していい感じのメタバースの映像を撮る 宇宙とISSを舞台にしたメタバース、THE ISS METAVERSE のテックリード 桟(@katapad) です。 ISSメタバースでは体験者の様子をより魅力的に見せるためのパブリックビューを用意している。iPadをバーチャルカメラにし、手持ちならではのカメラワークで無重力感ある映像を出力している。 このようにiPadがUnityのバーチャルカメラになり、直感的なカメラワークができる。 iPadだけでなくゲームコントローラでも操作できるようにしているが、やはりゲーム感が出てしまう。加速・減速、手ブレ感をチューニングすればそれっぽ…</summary><content type="html">&lt;h2 id="バーチャルカメラを振り回していい感じのメタバースの映像を撮る"&gt;バーチャルカメラを振り回していい感じのメタバースの映像を撮る&lt;/h2&gt;&#10;&lt;p&gt;宇宙とISSを舞台にしたメタバース、&lt;a href="https://kibo.space/metaverse/"&gt;THE ISS METAVERSE&lt;/a&gt; のテックリード &lt;a href="https://twitter.com/katapad"&gt;桟(@katapad)&lt;/a&gt; です。&lt;/p&gt;&#10;&lt;p&gt;ISSメタバースでは体験者の様子をより魅力的に見せるためのパブリックビューを用意している。iPadをバーチャルカメラにし、手持ちならではのカメラワークで無重力感ある映像を出力している。&lt;/p&gt;&#10;&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/NAbyfd1clyY" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;&#10;&lt;p&gt; &lt;iframe width="560" height="315" src="https://www.youtube.com/embed/j8XKTb_F9uU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;&lt;/p&gt;&#10;&lt;p&gt;このようにiPadがUnityのバーチャルカメラになり、直感的なカメラワークができる。&lt;/p&gt;&#10;&lt;p&gt;iPadだけでなくゲームコントローラでも操作できるようにしているが、やはりゲーム感が出てしまう。加速・減速、手ブレ感をチューニングすればそれっぽくもなるが、それはまた別の話。&lt;/p&gt;&#10;&lt;h2 id="前提条件-パブリックビューと管理画面をマルチディスプレイで出力"&gt;前提条件: パブリックビューと管理画面をマルチディスプレイで出力&lt;/h2&gt;&#10;&lt;p&gt;THE ISS METAVERSEではGameViewのDispaly1にゲーム画面(パブリックビュー)、Display2に管理画面を表示して運用している。&lt;/p&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20221201/20221201142649.png" width="1200" height="335" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;p&gt;この状態でiPadバーチャルカメラを振り回そうとしていた。&lt;/p&gt;&#10;&lt;h2 id="iPadバーチャルカメラとフルスクリーンの落とし穴"&gt;iPadバーチャルカメラとフルスクリーンの落とし穴&lt;/h2&gt;&#10;&lt;p&gt;いざ試してみると以下の障害があった。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Live CaptureがUnity Editor 専用。ビルドすると動かせない&lt;/strong&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;イベント本番で使うなら、Unity Editorをフルスクリーン化する必要あり&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Unity Editorをフルスクリーンにすると&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;マルチディスプレイにすると各ディスプレイでマウスの座標・クリック等が共有されてしまう&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;意図した画面をキャプチャできない&lt;/strong&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Live Captureのないシーンを経由するとLive Captureの接続が切れる&lt;/strong&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;バーチャルカメラとUnity Editorフルスクリーン化+マルチディスプレイを組み合わせると、以下の障害があった。&lt;/p&gt;&#10;&lt;p&gt;順番に説明していく。&lt;/p&gt;&#10;&lt;h2 id="Live-CaptureがUnity-Editor専用でビルドすると動かせない-裏技で動かせるらしい"&gt;Live CaptureがUnity Editor専用でビルドすると動かせない (裏技で動かせるらしい)&lt;/h2&gt;&#10;&lt;p&gt;このiPadをバーチャルカメラにする&lt;a href="https://docs.unity3d.com/Packages/com.unity.live-capture@1.0/manual/index.html"&gt;Live Capture&lt;/a&gt;が、実はビルドに対応しておらず、Unity Editorでのみ動作する。&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://forum.unity.com/threads/how-to-use-live-capture-to-build-pc-applications.1203229/"&gt;↓Unityの中の人の発言&lt;/a&gt;&lt;/p&gt;&#10;&lt;blockquote&gt;&lt;ol&gt;&#10;&lt;li&gt;&lt;p&gt;Yes, the app is only designed to work with the editor. It is theoretically possibly to use for other cases, but this is not officially supported at this time.&lt;/p&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;p&gt;Technically you can include the Live Capture package in a standalone build, but it is not officially supported at this time as there is still much work left to properly support this.&lt;/p&gt;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;↑ この話の流れを見る限り、まだビルドは正式にサポートされてないが、&lt;strong&gt;live-captureのパッケージをローカルに移動し、iPadとやりとりをするサーバーをpublicに変更したりCompanionAppServer.OnUpdate()を叩くとうまくいくらしい&lt;/strong&gt;。って今気づいたよ…。今度試すか…。&lt;/p&gt;&#10;&lt;h2 id="じゃあビルドせずにUnity-Editorで無理やり使おう"&gt;じゃあビルドせずにUnity Editorで無理やり使おう！&lt;/h2&gt;&#10;&lt;p&gt;ということで、まずはUnity Editorでフルスクリーンにできるアセットを導入。&lt;/p&gt;&#10;&lt;h2 id="Unity-Editorをフルスクリーンにするアセット"&gt;Unity Editorをフルスクリーンにするアセット&lt;/h2&gt;&#10;&lt;p&gt;&lt;a href="https://assetstore.unity.com/packages/tools/utilities/fullscreen-editor-69534"&gt;Fullscreen Editor | Utilities Tools | Unity Asset Store&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;このアセットを使うとUnity Editorでもツールバーの出ない完全なフルスクリーンができる。&lt;/p&gt;&#10;&lt;p&gt;しかもマルチディスプレイ対応。最高。&lt;/p&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20221201/20221201142754.png" width="1200" height="755" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;br/&gt;&#10;display1にUnity内display2の管理画面を持ってきてる&lt;/p&gt;&#10;&lt;p&gt;しかし、こういうアクロバティックなことをすると、だいたいうまくいかないもんである。&lt;/p&gt;&#10;&lt;h2 id="問題1-Unity-Editorでマルチディスプレイをするとマウス座標が各ディスプレイで共通になってめちゃくちゃになる"&gt;問題1: Unity Editorでマルチディスプレイをすると、マウス座標が各ディスプレイで共通になって、めちゃくちゃになる&lt;/h2&gt;&#10;&lt;p&gt;ISSメタバースはパブリックビューと管理画面を1つのアプリで完結させている。なので2画面のマルチディスプレイだ。&lt;/p&gt;&#10;&lt;p&gt;困ったことにUnity Editorでの再生時はマウスの位置やクリックなどの情報はすべてのGameViewで共有される。&lt;/p&gt;&#10;&lt;p&gt;つまり、&lt;strong&gt;GameView1の右上をクリックすると、GameView2の右上もクリックされたことになる。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20221201/20221201143006.png" width="1200" height="343" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;br/&gt;&#10;これは困る…。&lt;/p&gt;&#10;&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/eURiSrZgw8A" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;&#10;&lt;h2 id="解決策-対症療法で管理画面にロック機能をつけた"&gt;解決策: 対症療法で管理画面にロック機能をつけた&lt;/h2&gt;&#10;&lt;p&gt;ロック解除しても一定時間で必ずロックに戻るようにした。めんどくさい…。&lt;/p&gt;&#10;&lt;h2 id="問題2-フルスクリーン時に画面キャプチャーすると意図しない画面がキャプチャーされる"&gt;問題2: フルスクリーン時に画面キャプチャーすると、意図しない画面がキャプチャーされる&lt;/h2&gt;&#10;&lt;p&gt;THE ISS METAVERSEではカメラマンが記念写真を撮ってTwitterにアップする仕組みがある。&lt;/p&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20221201/20221201143045.png" width="1200" height="874" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://twitter.com/astro_cameraman"&gt;twitter.com/astro_cameraman&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;管理画面とパブリックビュー(ゲーム画面)を同じマシンで出力しており、Display1に管理画面、Display2にパブリックビューが表示される。&lt;/p&gt;&#10;&lt;p&gt;で、このキャプチャが難題だった。普通にCameraのTargetTexutreに設定したRenderTextureに焼き込むとポストエフェクトが乗らない。今回はポストエフェクトで太陽に対してレンズフレアを入れてるので、そこを削るわけにはいかない。そこでGameViewをそのままキャプチャする &lt;code&gt;texture.ReadPixels()&lt;/code&gt; を使った。似たようなメソッドで&lt;code&gt;ScreenCapture.CaptureScreenshotIntoRenderTexture(rt)&lt;/code&gt;もあったが、色がおかしくなるのと画像が反転していたので、深く追うのを諦めた。&lt;/p&gt;&#10;&lt;p&gt;で、ゲーム画面がキャプチャされるように設定したいところなのだが、フルスクリーンにするとうまくいかない。&lt;strong&gt;ゲーム画面ではなく管理画面がキャプチャされてしまうことがある。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;ちなみにビルドするとわりと意図した画面がキャプチャできる。&lt;/strong&gt;Unity EditorではGameViewの座標がマルチディスプレイの環境ではうまく動作しないのが仕様なので、もしかするとDisplay1とDisplay2の座標が同じになって、どっちをキャプチャするかがランダムになるのかもしれない。しらんけど。&lt;/p&gt;&#10;&lt;h2 id="解決策-フルスクリーンガチャをする"&gt;解決策: フルスクリーンガチャをする&lt;/h2&gt;&#10;&lt;p&gt;そんなこんなで何度か試していると、キャプチャ対象になる画面はフルスクリーンにしたときにランダムで決まることがわかった。&lt;/p&gt;&#10;&lt;p&gt;なので、フルスクリーンにしてキャプチャし、意図通りの画面をキャプれたらOK。失敗したらもう一度フルスクリーンにしてキャプチャ…、というガチャをすることで対応した…。&lt;/p&gt;&#10;&lt;h2 id="問題3-PhotonでシーンをロードするとLive-Captureが外れる"&gt;問題3: PhotonでシーンをロードするとLive Captureが外れる&lt;/h2&gt;&#10;&lt;p&gt;ISSメタバースはロビーとなるシーンを経て、実際のゲームルームシーンへ遷移する。&lt;/p&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20221201/20221201143201.png" width="1200" height="743" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;p&gt;Live Captureが含まれているのはロビー以降に読み込まれるゲームシーンのみ。なので、ロビーを経由した時点でiPadの接続が切れてしまう。&lt;/p&gt;&#10;&lt;h2 id="解決策-iOSのタスク切り替えを一瞬やると戻る"&gt;解決策: iOSのタスク切り替えを一瞬やると戻る&lt;/h2&gt;&#10;&lt;p&gt;Live CaptureのiOSアプリがタスク切り替え時に再コネクトしようとするらしく、それでうまくいく。&lt;/p&gt;&#10;&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/T793GHZnCGo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;&#10;&lt;h2 id="まとめ"&gt;まとめ&lt;/h2&gt;&#10;&lt;p&gt;そんな感じで無理やり対応したけど、めちゃくちゃいいカメラワークができたんじゃないか。&lt;/p&gt;&#10;&lt;p&gt;あと、&lt;strong&gt;地味にビルドせずにフルスクリーン化して動かすのもめっちゃ楽。&lt;/strong&gt;ずっといじくり倒せるのがいい。&lt;/p&gt;&#10;&lt;p&gt;でもビルドしてえよなあ。またいつかビルドできるように書き換えよう。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;キャプチャまわりはこうしたほうがいい！とかLive Captureビルドして使った！などの怪談がありましたら、教えてください！&lt;/strong&gt;待ってまーす！&lt;/p&gt;&#10;&lt;h2 id="おもしろがって作れる人材募集中"&gt;おもしろがって作れる人材募集中！&lt;/h2&gt;&#10;&lt;p&gt;ISSメタバースみたいな新しい遊び場を作って、自分らで遊び倒して、でっかいプロジェクトにしてみんなに遊び倒してもらう。&#10;そんなことやってみたいなーって思う人はJOIN US！！！&lt;/p&gt;&#10;&lt;p&gt;&lt;iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fbascule.co.jp%2Fjoin-us%2F" title="JOIN US - Bascule Inc." class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://bascule.co.jp/join-us/"&gt;bascule.co.jp&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;</content><link rel="enclosure" href="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20221207/20221207132941.png" type="image/png" length="0"/><author><name>bascule-dev</name></author></entry><entry><title>オカシなツリーを飾る技術とチャレンジ</title><link href="https://blog.bascule.co.jp/entry/2022/02/21/165750"/><id>hatenablog://entry/13574176438061621468</id><published>2022-02-21T16:57:50+09:00</published><updated>2022-02-21T07:57:50+09:00</updated><summary type="html">こんにちは、エンジニアの野島(@daumkuchen)です。 株式会社明治様の特設サイト「明治クリスマスこどもパーク」内メインコンテンツ「オカシなコーラスツリー！」の企画・制作を弊社が担当しました。 コロナ禍でのコミュニケーションを模索する中、合唱という形を通じてつながりを感じる体験ができるスマートフォンサイトです。カスタマイズしたキャラクターをポストし、インターネットを通じて世界中の参加者が1つのツリーに集まります。 bascule.co.jp テクニカルディレクション バックエンド開発 インフラ構築 自分の担当領域は主にこちらの3点です。 普段はフロントエンドを担当することが多い自分にとっ…</summary><content type="html">&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20220209/20220209183518.jpg" alt="f:id:bascule-dev:20220209183518j:plain" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;p&gt;こんにちは、エンジニアの野島(&lt;a href="https://twitter.com/daumkuchen"&gt;@daumkuchen&lt;/a&gt;)です。&lt;/p&gt;&#10;&lt;p&gt;株式会社明治様の特設サイト「&lt;strong&gt;明治クリスマスこどもパーク&lt;/strong&gt;」内メインコンテンツ「&lt;strong&gt;オカシなコーラスツリー！&lt;/strong&gt;」の企画・制作を弊社が担当しました。&lt;br /&gt;&#10;コロナ禍でのコミュニケーションを模索する中、合唱という形を通じてつながりを感じる体験ができるスマートフォンサイトです。カスタマイズしたキャラクターをポストし、インターネットを通じて世界中の参加者が1つのツリーに集まります。&lt;/p&gt;&#10;&lt;p&gt;&lt;iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fbascule.co.jp%2Fwork%2Fchorus-tree%2F" title="オカシなコーラスツリー！ | WORK - Bascule Inc." class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://bascule.co.jp/work/chorus-tree/"&gt;bascule.co.jp&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;テクニカルディレクション&lt;/li&gt;&#10;&lt;li&gt;バックエンド開発&lt;/li&gt;&#10;&lt;li&gt;インフラ構築&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;自分の担当領域は主にこちらの3点です。&lt;br /&gt;&#10;普段はフロントエンドを担当することが多い自分にとって、バックエンド・インフラ領域へのチャレンジとなるプロジェクトです。&lt;br /&gt;&#10;具体的にどういったことにチャレンジをして、どういったことに気をつけたのかなどを話します。&lt;/p&gt;&#10;&lt;h2&gt;チャレンジしたこと&lt;/h2&gt;&#10;&lt;h3&gt;技術選定&lt;/h3&gt;&#10;&lt;p&gt;新しい技術を使う部分と慣れた技術を使う部分を明確に分け、実現可能性とのバランスを取りました。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;新しい技術&#10;&lt;ul&gt;&#10;&lt;li&gt;Go（API）&lt;/li&gt;&#10;&lt;li&gt;Terraform（IaC）&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;慣れた技術&#10;&lt;ul&gt;&#10;&lt;li&gt;Nuxt.js（管理画面）&lt;/li&gt;&#10;&lt;li&gt;Python（ロードテスト）&lt;/li&gt;&#10;&lt;li&gt;Node.js（AWS Lambda）&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3&gt;セキュリティ対策&lt;/h3&gt;&#10;&lt;p&gt;参加型コンテンツということもあり、多くの方の個人情報を扱います。&lt;br /&gt;&#10;なのでセキュリティに関しては一般的なウェブサイト案件よりも厳しい基準を設けて臨みました。&lt;/p&gt;&#10;&lt;p&gt;インフラはAWSで構築しました。構成は以下になります。&lt;/p&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20220209/20220209193628.png" alt="f:id:bascule-dev:20220209193628p:plain" width="1200" height="1000" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;VPC：プライベートサブネットを使ったセキュアな構成&lt;/li&gt;&#10;&lt;li&gt;EC2：&lt;a href="https://www.trendmicro.com/ja_jp/business/products/hybrid-cloud/cloud-one-workload-security.html"&gt;Trend Micro Cloud One Workload Security&lt;/a&gt;を導入しセキュリティを強化&lt;/li&gt;&#10;&lt;li&gt;NLB：リクエストの分散処理&lt;/li&gt;&#10;&lt;li&gt;CloudWatch：パフォーマンスやリクエストのモニタリング&lt;/li&gt;&#10;&lt;li&gt;SNS, ChatBot：異常検知時の通知（メール、Slackチャンネル）&lt;/li&gt;&#10;&lt;li&gt;WAFv2：Dos攻撃対策&lt;/li&gt;&#10;&lt;li&gt;GuardDuty：脅威検出&lt;/li&gt;&#10;&lt;li&gt;S3：各種ログの保管&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;上記のようなセキュリティ対策を徹底し、運用期間中は問題もなく稼働させることができました。&lt;/p&gt;&#10;&lt;h2&gt;役に立ったこと&lt;/h2&gt;&#10;&lt;p&gt;特にGoはとても良い感触で作業することができました。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;動作が軽い&lt;/li&gt;&#10;&lt;li&gt;記述ルールが厳密で記述の揺れが少ない&lt;/li&gt;&#10;&lt;li&gt;ポインタや構造体もシンプルで扱いやすい&lt;/li&gt;&#10;&lt;li&gt;複雑化しやすいエラーハンドリングがきれいに書ける&lt;/li&gt;&#10;&lt;li&gt;APIに必要なアセットやリファレンスが充実している（例：gin, gin-swagger, gorm）&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;例：ユーザー作成時の処理の流れ&lt;/p&gt;&#10;&lt;pre class="code lang-go" data-lang="go" data-unlink&gt;&lt;span class="synComment"&gt;// 構造体を初期化&lt;/span&gt;&#10;user := User{}&#10;&#10;&lt;span class="synComment"&gt;// ユーザー作成&lt;/span&gt;&#10;createdUser, err := controller.Create(user)&#10;&#10;&lt;span class="synComment"&gt;// エラーが返って来れば、メッセージのレスポンスを返す&lt;/span&gt;&#10;&lt;span class="synStatement"&gt;if&lt;/span&gt; err != &lt;span class="synStatement"&gt;nil&lt;/span&gt; {&#10; c.JSON(&lt;span class="synConstant"&gt;500&lt;/span&gt;,&#10; gin.H{&#10; &lt;span class="synConstant"&gt;&amp;quot;message&amp;quot;&lt;/span&gt;: fmt.Sprintf(&lt;span class="synConstant"&gt;&amp;quot;Error: %s&amp;quot;&lt;/span&gt;, err),&#10; })&#10; &lt;span class="synStatement"&gt;return&lt;/span&gt;&#10;}&#10;&#10;c.JSON(&lt;span class="synConstant"&gt;200&lt;/span&gt;, createdUser)&#10;&lt;/pre&gt;&#10;&lt;p&gt;Goの特徴である並列処理を今回使う機会はありませんでしたが、それでも十分メリットを実感することができました。&lt;/p&gt;&#10;&lt;h2&gt;気をつけたこと&lt;/h2&gt;&#10;&lt;h3&gt;リスクを常に考える&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;課題は自分1人で抱えず、全体に周知する&lt;/li&gt;&#10;&lt;li&gt;少しでも不明な箇所は事前に相談する&lt;/li&gt;&#10;&lt;li&gt;運用・インシデント対応マニュアルを作成する&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;社内の先輩エンジニアとコミュニケーションをとり、常に視界をクリアにしながら進めることを心がけました。&lt;/p&gt;&#10;&lt;h2&gt;チャレンジした結果&lt;/h2&gt;&#10;&lt;p&gt;常に勉強しながらの作業は決して楽ではありませんでしたが、その分大きな学びとなりました。&lt;br /&gt;&#10;特にセキュリティ周りを厚めに対応したので、今後の案件でも活かす機会がありそうです。&lt;br /&gt;&#10;新しいことを覚えるには、実践を通して手を動かすことが一番身につきやすいと実感しました。&lt;/p&gt;&#10;&lt;h2&gt;成長機会を求めている方をバスキュールは探しています&lt;/h2&gt;&#10;&lt;p&gt;バスキュールはやりたいことに手を挙げればチャレンジさせてくれる会社で、適切に取り組めばそれをバックアップしてくれる環境があります。&lt;br /&gt;&#10;率先して何かにチャレンジしたいと思っている方は、是非ご気軽にお声掛けください。&lt;/p&gt;&#10;&lt;p&gt;（特にバックエンドエンジニア募集しています...）&lt;/p&gt;&#10;&lt;p&gt;&lt;iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fbascule.co.jp%2Fjoin-us%2F" title="JOIN US - Bascule Inc." class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://bascule.co.jp/join-us/"&gt;bascule.co.jp&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;</content><link rel="enclosure" href="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20220209/20220209183518.jpg" type="image/jpeg" length="0"/><author><name>bascule-dev</name></author></entry><entry><title>「#きぼうを見ようAR」逆ジオコーディングを作った話</title><link href="https://blog.bascule.co.jp/entry/2021/10/28/140057"/><id>hatenablog://entry/13574176438024825166</id><published>2021-10-28T14:00:57+09:00</published><updated>2021-10-28T05:00:57+09:00</updated><summary type="html">はじめまして、こんにちは。今年の4月から新卒入社したエンジニアの矢尾(@80momoka)です。 弊社が以前から運営している「#きぼうを見よう」に、新たに「ARきぼう予報」という機能が追加されました。 #きぼうを見ようhttps://lookup.kibo.space/AR機能はスマートフォンでご覧ください。 #きぼうを見よう のサイトに、新機能「🆕ARきぼう予報」が追加されました🎉スマホを空にかざすだけで、ISSの現在地や軌道予測がわかります✌️これからはもっとISSを探しやすくなりますよ！🛰️今夜のISSは日本上空を通過予定。ぜひ、あなたの上空の予報を見てみてね！https://t.co/…</summary><content type="html">&lt;p&gt;&lt;br /&gt;はじめまして、こんにちは。&lt;br /&gt;今年の4月から新卒入社したエンジニアの矢尾(&lt;a href="https://twitter.com/80momoka"&gt;@80momoka&lt;/a&gt;)です。&lt;/p&gt;&#10;&lt;p&gt;弊社が以前から運営している「#きぼうを見よう」に、&lt;br /&gt;新たに「ARきぼう予報」という&lt;span style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;"&gt;機能が追加されました。&lt;/span&gt;&lt;/p&gt;&#10;&lt;p&gt;#きぼうを見よう&lt;br /&gt;&lt;a href="https://lookup.kibo.space/"&gt;https://lookup.kibo.space/&lt;/a&gt;&lt;br /&gt;AR機能はスマートフォンでご覧ください。&lt;/p&gt;&#10;&lt;blockquote class="twitter-tweet" data-conversation="none" data-lang="ja"&gt;&#10;&lt;p dir="ltr" lang="ja"&gt;&lt;a href="https://twitter.com/hashtag/%E3%81%8D%E3%81%BC%E3%81%86%E3%82%92%E8%A6%8B%E3%82%88%E3%81%86?src=hash&amp;amp;ref_src=twsrc%5Etfw"&gt;#きぼうを見よう&lt;/a&gt; のサイトに、新機能「🆕ARきぼう予報」が追加されました🎉&lt;br /&gt;&lt;br /&gt;スマホを空にかざすだけで、ISSの現在地や軌道予測がわかります✌️これからはもっとISSを探しやすくなりますよ！🛰️&lt;br /&gt;今夜のISSは日本上空を通過予定。ぜひ、あなたの上空の予報を見てみてね！&lt;a href="https://t.co/qVOXuh8k5T"&gt;https://t.co/qVOXuh8k5T&lt;/a&gt;&lt;/p&gt;&#10;— KIBO宇宙放送局 (@KIBO_SPACE) &lt;a href="https://twitter.com/KIBO_SPACE/status/1445597431354302464?ref_src=twsrc%5Etfw"&gt;2021年10月6日&lt;/a&gt;&lt;/blockquote&gt;&#10;&lt;p&gt;&#10;&lt;script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;このARきぼう予報の中で、方角と位置関係をわかりやすくするために、周囲の都道府県がどの方角にあるかを示す表記をいれています。（下画像、赤い枠内に〇〇方面と表記）&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20211021/20211021163846.png" alt="f:id:bascule-dev:20211021163846p:plain" width="300" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /&gt;&lt;/p&gt;&#10;&lt;p&gt;この都道府県名を表示する際に、ユーザーが今いる県は非表示にしています。そのために現在地から都道府県を割り出す必要があり、簡易的な逆ジオコーディングを独自実装したので、それについて書きたいと思います。&lt;/p&gt;&#10;&lt;h2&gt;逆ジオコーディングとは&lt;/h2&gt;&#10;&lt;p&gt;まずジオコーディングとは、住所などから座標（緯度経度）を取得する機能のことで、&lt;br /&gt;逆ジオコーディングとは文字通り逆の、座標（緯度経度）から住所を取得する機能のことです。&lt;/p&gt;&#10;&lt;p&gt;ARきぼう予報ではISSの軌道予報を計算するために、既にユーザーの現在地の緯度経度を取得していたため、そのデータを使用して地名を取得しようとしました。&lt;/p&gt;&#10;&lt;p&gt;逆ジオコーディングのAPIを探しましたが、有料であったり利用制限があったりと、望ましいものが見つからなかったため作ることになりました。&lt;/p&gt;&#10;&lt;h2&gt;&lt;strong&gt;取得の流れ&lt;/strong&gt;&lt;/h2&gt;&#10;&lt;p&gt;全国各地の住所とその緯度経度のデータから、現在地の緯度経度と一番近い距離のデータを探し出し、その住所を取得しています。&lt;/p&gt;&#10;&lt;p&gt;全国各地の住所と緯度経度のデータは、国土交通省が提供している位置参照情報を使用しました。&lt;/p&gt;&#10;&lt;p&gt;国土交通省　位置参照情報ダウンロード&lt;br /&gt;&lt;a href="https://nlftp.mlit.go.jp/cgi-bin/isj/dls/_choose_method.cgi"&gt;https://nlftp.mlit.go.jp/cgi-bin/isj/dls/_choose_method.cgi&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;各データは以下のようになっています。&lt;/p&gt;&#10;&lt;pre class="code" data-lang="" data-unlink=""&gt;{&lt;br /&gt;    '都道府県コード': '01',&lt;br /&gt;    '都道府県名': '北海道',&lt;br /&gt;    '市区町村コード': ' 01101',&lt;br /&gt;    '市区町村名': '札幌市中央区',&lt;br /&gt;    '大字町丁目コード': ' 011010001001',&lt;br /&gt;    '大字町丁目名': '旭ケ丘一丁目',&lt;br /&gt;    '緯度': '43.042230',&lt;br /&gt;    '経度': '141.319722',&lt;br /&gt;    '原典資料コード': '0',&lt;br /&gt;   '大字・字・丁目区分コード': '3'&lt;br /&gt;}&#10;&lt;/pre&gt;&#10;&lt;p&gt;このデータが羅列されたCSVファイルのままだとサイズがとても大きく扱いにくいため、&lt;br /&gt;まずはJavaScriptから扱いやすいようにjsonを作成することにしました。&lt;/p&gt;&#10;&lt;p&gt;JavaScriptで現在地の都道府県を割り出す仕組みとしては、以下のように考えました。&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20211021/20211021171638.png" alt="f:id:bascule-dev:20211021171638p:plain" width="420" height="420" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /&gt;&lt;/p&gt;&#10;&lt;p&gt;上の画像のように、緯度経度の値ごとに区切った座標検索データを作って、現在地の緯度経度が含まれている範囲のデータの中から一番距離の近いデータを探し出します。&lt;br /&gt;円が重なっている所はデータが重複しますが、距離を計算するための緯度経度の値があればいいので、都道府県や市区町村などの住所のデータは別で分けて緯度経度のみのファイルにします。住所データは、緯度経度のデータと対になるidを付けて、id 1~100、101~200…とファイルを分けます。&lt;/p&gt;&#10;&lt;p&gt;&lt;br /&gt;ただ今回の実装では、都道府県のみ取得できればよく、そこまで厳密な市区町村情報までは不必要であったため、データの重複をなくし、下の画像のように経度ごとのみで分けました。&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20211021/20211021172743.png" alt="f:id:bascule-dev:20211021172743p:plain" width="300" loading="lazy" title="" class="hatena-fotolife" itemprop="image" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif; white-space: pre-wrap;" /&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20211021/20211021172926.png" alt="f:id:bascule-dev:20211021172926p:plain" width="313" loading="lazy" title="" class="hatena-fotolife" itemprop="image" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;" /&gt;&lt;/p&gt;&#10;&lt;p&gt;(左：経度ごとに分けるイメージ　右：分けて作成した緯度経度情報のみのjsonファイル)&lt;/p&gt;&#10;&lt;p&gt;今回は経度0.1度ずつに分けてjsonファイルにまとめました。&lt;br /&gt;件数の少ない経度127.5度以下や145度以上、128度以上129度未満(沖縄県の東側〜鹿児島県大島郡付近)はそれぞれまとめて一つのファイルにしています。&lt;/p&gt;&#10;&lt;p&gt;データを割り出す際は、現在地の経度と一致するjsonファイル(127.5度台であれば1275.json)を指定し、その中から現在地の緯度経度と一番距離が近いデータを探し出して割り出します。&lt;/p&gt;&#10;&lt;p&gt;しかしこのままだと、例えば下図のように赤●が現在地で★が一番近い市区町村だとしても、黄●の市区町村を取得してしまいます。精度を上げるために、赤●を含むデータとその左右のデータも含めた中から、一番近い距離のデータを探します。&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20211021/20211021173040.png" alt="f:id:bascule-dev:20211021173040p:plain" width="420" height="420" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /&gt;&lt;/p&gt;&#10;&lt;p&gt;今回の機能では、都道府県名のみ割り出せたらよく厳密な市区町村情報までは不必要であるため、現在地の経度を含む範囲のファイル内のみで割り出しています。&lt;/p&gt;&#10;&lt;p&gt;また、今後もし精度が求められる機能が追加されることがあった時に、データを使いやすいように緯度経度と住所に分けたままにしています。&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20211021/20211021173122.png" alt="f:id:bascule-dev:20211021173122p:plain" width="307" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20211021/20211021173133.png" alt="f:id:bascule-dev:20211021173133p:plain" width="301" loading="lazy" title="" class="hatena-fotolife" itemprop="image" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;" /&gt;&lt;/p&gt;&#10;&lt;p&gt;(左：緯度経度のみのファイル　右：住所のみのファイル)&lt;/p&gt;&#10;&lt;h2&gt;&lt;strong&gt;周囲の都道府県を選出・表示&lt;/strong&gt;&lt;/h2&gt;&#10;&lt;p&gt;まず各都道府県の県庁所在地の緯度経度と県名、ARに表示する名前をリストにしたものを用意します。&lt;/p&gt;&#10;&lt;pre class="code" data-lang="" data-unlink=""&gt;static list = [&lt;br /&gt;   {'lat':43.06417, 'lng':141.34694, 'name':'北海道',   'label':'北海道方面'},&lt;br /&gt;   {'lat':40.82444, 'lng':140.74,    'name':'青森県',   'label':'青森方面'},&lt;br /&gt; …&#10;&lt;/pre&gt;&#10;&lt;p&gt;このリストの中から、現在地の緯度経度と距離が近い順に取得します。&lt;br /&gt;ユーザーがいる場所の県名は非表示にしたかったので、逆ジオコーディングで取得した県名とリストのデータ内の県名を照らし合わせて、合致しているものは省きます。&lt;br /&gt;また今回は近隣の県のみ表示したかったので、ある程度距離が離れている県はこの時点で省きました。&lt;/p&gt;&#10;&lt;p&gt;県庁所在地がある方向を単純に2点の緯度経度から求めようとしたものの、地球は球体であるため簡単な計算ではズレが生じてしまいました。&lt;br /&gt;現在地から各地点への正確な方角を求めるのは、geodesyというライブラリ内のコードを使用しました。&lt;/p&gt;&#10;&lt;p&gt;geodesy&lt;br /&gt;&lt;a href="https://www.npmjs.com/package/geodesy"&gt;https://www.npmjs.com/package/geodesy&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;Geodesy functions | Class: LatLonEllipsoidal_Vincenty  finalBearingTo()&lt;br /&gt;&lt;a href="https://www.movable-type.co.uk/scripts/geodesy/docs/module-latlon-ellipsoidal-vincenty-LatLonEllipsoidal_Vincenty.html#finalBearingTo"&gt;https://www.movable-type.co.uk/scripts/geodesy/docs/module-latlon-ellipsoidal-vincenty-LatLonEllipsoidal_Vincenty.html#finalBearingTo&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;Javascriptで２地点間の距離と方角を計算するライブラリ&lt;br /&gt;&lt;a href="https://thr3a.hatenablog.com/entry/20190502/1556772556"&gt;https://thr3a.hatenablog.com/entry/20190502/1556772556&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;br /&gt;finalBearingTo()という関数にて取得した方角(0~360の値)をもとに、3D空間内に配置しています。&lt;/p&gt;&#10;&lt;pre class="code" data-lang="" data-unlink=""&gt;const len = 360;&lt;br /&gt;const step = Math.PI * 2 / len;&lt;br /&gt;const size = this.ar.dome.size;&lt;br /&gt;&lt;br /&gt;&#10;const point = { 'lat':lat, 'lon':lng };&lt;br /&gt;const angle = this.finalBearingTo(point);&lt;br /&gt;&lt;br /&gt;&#10;// pos&lt;br /&gt;let x = Math.sin(step * angle) * size;&lt;br /&gt;let y = offsetY;&lt;br /&gt;let z = Math.cos(step * angle) * size * -1;&#10;&lt;/pre&gt;&#10;&lt;p&gt;&lt;iframe width="420" height="315" src="https://www.youtube.com/embed/AE5fRvKTuh8?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" id="widget2"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://youtu.be/AE5fRvKTuh8"&gt;youtu.be&lt;/a&gt;&lt;/cite&gt;このようにユーザーの周りに県名を表示することで、自分がどこ方面を向いているのかわかるようになり、ISSがいま日本のどこの上空を通過しているのか想像しやすくなっているのではないかと思います。&lt;/p&gt;&#10;&lt;h2&gt;エンジニアを募集しています&lt;/h2&gt;&#10;&lt;p&gt;私自身はまだエンジニア一年生ですが、バスキュールには凄いエンジニアの方がたくさんいらっしゃいます。&lt;br /&gt;見たこともない新しいコンテンツを、企画から制作まで一緒に作っていけるのが非常に楽しいです。&lt;br /&gt;また、かっちり決まった部署や働き方があるわけではないので、望めば様々なことに挑戦できる環境だと思います。&lt;br /&gt;バスキュールの働き方に興味がありましたらぜひご応募ください。&lt;/p&gt;&#10;&lt;p&gt;&lt;iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fbascule.co.jp%2Fjoin-us%2F" title="JOIN US - Bascule Inc." class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://bascule.co.jp/join-us/"&gt;bascule.co.jp&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;</content><link rel="enclosure" href="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20211021/20211021163846.png" type="image/png" length="0"/><author><name>bascule-dev</name></author></entry><entry><title>ARきぼう予報を作って宇宙飛行士に手を振った話</title><link href="https://blog.bascule.co.jp/entry/2021/10/20/104350"/><id>hatenablog://entry/13574176438024173759</id><published>2021-10-20T10:43:50+09:00</published><updated>2021-10-20T01:43:50+09:00</updated><summary type="html">エンジニアの勝田です。こんにちは。 弊社で開発・運営している「#きぼうを見よう - 国際宇宙ステーションが見える予測日時をお知らせ」というサイトに新しく「ARきぼう予報」というWebAR機能を追加しました。 ARきぼう予報 | #きぼうを見よう - 国際宇宙ステーションが見える予測日時をお知らせ 国際宇宙ステーション（ISS）がどんな軌道を通るか、リアルタイムでどこにいるのかがわかるサービスです。 今日はそのAR機能についてお話したいと思います。 ISS観測について 上空付近をISSが通過するときにいくつかの条件がそろうと肉眼で観測することができます。 詳しくはこちらをご覧ください。 look…</summary><content type="html">&lt;p&gt;エンジニアの勝田です。こんにちは。&lt;/p&gt;&#10;&lt;p&gt;弊社で開発・運営している「&lt;a href="https://lookup.kibo.space/"&gt;#きぼうを見よう - 国際宇宙ステーションが見える予測日時をお知らせ&lt;/a&gt;」というサイトに新しく「ARきぼう予報」というWebAR機能を追加しました。&lt;/p&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20211019/20211019184944.jpg" alt="f:id:bascule-dev:20211019184944j:plain" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&#10;&lt;a href="https://lookup.kibo.space/ar/"&gt;ARきぼう予報 | #きぼうを見よう - 国際宇宙ステーションが見える予測日時をお知らせ&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;国際宇宙ステーション（ISS）がどんな軌道を通るか、リアルタイムでどこにいるのかがわかるサービスです。&lt;br /&gt;&#10;今日はそのAR機能についてお話したいと思います。&lt;/p&gt;&#10;&lt;h2&gt;ISS観測について&lt;/h2&gt;&#10;&lt;p&gt;上空付近をISSが通過するときにいくつかの条件がそろうと肉眼で観測することができます。&lt;br /&gt;&#10;詳しくはこちらをご覧ください。&lt;/p&gt;&#10;&lt;p&gt;&lt;iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Flookup.kibo.space%2Fhowto%2F" title="きぼうはなぜ見える | #きぼうを見よう - 国際宇宙ステーションが見える予測日時をお知らせ" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://lookup.kibo.space/howto/"&gt;lookup.kibo.space&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;&lt;p&gt;ISSは、&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;明るい星のような強い光&lt;/li&gt;&#10;&lt;li&gt;飛行機のようにチカチカせず&lt;/li&gt;&#10;&lt;li&gt;飛行機よりも速いスピードで&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;見えます。&lt;br /&gt;&#10;とても特徴的なので、一度見つければ「あれはISSだ！」とすぐにわかります。&lt;br /&gt;&#10;ただ、見つけるまではなかなかわかりにくく、ISSかな？と思って見ていると星だったり飛行機だったりします。特に初めてISS観測を行う人の場合、雲が多かったり、仰角（ISSが見える高さ）が低いとどれがISSなのかわからず、本当にISSは見えるんだろうか…と不安な気持ちで空を眺めて待つことになります。&lt;/p&gt;&#10;&lt;p&gt;そんなとき、ARきぼう予報を使ってスマホを空にかざすと、いまどこにISSがいて、いつごろ見えるかがビジュアライズされているため簡単に観測ができるようになります！&lt;br /&gt;&#10;こちらは開発中に撮影したキャプチャですが、こんな感じです。&lt;/p&gt;&#10;&lt;p&gt;&lt;blockquote data-conversation="none" class="twitter-tweet" data-lang="ja"&gt;&lt;p lang="ja" dir="ltr"&gt;「ARきぼう予報」ただいま鋭意開発中！🚀&lt;br&gt;&lt;br&gt;こんな感じで、ISSの軌道や通過時間の予測が見られるようになります！ サイトの使い方は、10/6(水)18:20頃〜Twitter Spacesでも紹介する予定なので、みなさんぜひ、スマホ片手に参加してくださいね！📱🛰️ &lt;a href="https://twitter.com/hashtag/%E3%81%8D%E3%81%BC%E3%81%86%E3%82%92%E8%A6%8B%E3%82%88%E3%81%86?src=hash&amp;amp;ref_src=twsrc%5Etfw"&gt;#きぼうを見よう&lt;/a&gt; &lt;a href="https://t.co/auVzFNiXW6"&gt;pic.twitter.com/auVzFNiXW6&lt;/a&gt;&lt;/p&gt;&amp;mdash; KIBO宇宙放送局 (@KIBO_SPACE) &lt;a href="https://twitter.com/KIBO_SPACE/status/1444950520456617986?ref_src=twsrc%5Etfw"&gt;2021年10月4日&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt; &lt;/p&gt;&#10;&lt;h2&gt;モックを作る&lt;/h2&gt;&#10;&lt;p&gt;3D構築にはThree.js、AR機能はスマホのカメラ、位置情報、ジャイロセンサーを使用しています。&lt;br /&gt;&#10;今回はISSの座標をAR上に表示するため、Three.js上での方角と現実世界の方角を合致させる必要がありました。&lt;br /&gt;&#10;そのため、まず、シンプルに真北にキューブを表示するだけのモックを作成することにしました。&lt;/p&gt;&#10;&lt;p&gt;モックの完成品はこちらからどうぞ。&lt;br /&gt;&#10;&lt;iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fyuko-katsuda%2Farmock" title="GitHub - yuko-katsuda/armock" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://github.com/yuko-katsuda/armock"&gt;github.com&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20211019/20211019185444.gif" alt="f:id:bascule-dev:20211019185444g:plain" width="200" height="433" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;p&gt;プレビューページ（PC非対応です）&lt;br /&gt;&#10;&lt;a href="https://yuko-katsuda.github.io/armock/index.html"&gt;https://yuko-katsuda.github.io/armock/index.html&lt;/a&gt;&lt;/p&gt;&#10;&lt;h3&gt;3D環境のベースを作る&lt;/h3&gt;&#10;&lt;pre class="code lang-html" data-lang="html" data-unlink&gt;&lt;span class="synComment"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;&#10;&lt;span class="synIdentifier"&gt;&amp;lt;&lt;/span&gt;&lt;span class="synStatement"&gt;html&lt;/span&gt;&lt;span class="synIdentifier"&gt;&amp;gt;&lt;/span&gt;&#10;&lt;span class="synIdentifier"&gt;&amp;lt;&lt;/span&gt;&lt;span class="synStatement"&gt;head&lt;/span&gt;&lt;span class="synIdentifier"&gt;&amp;gt;&lt;/span&gt;&#10;&lt;span class="synPreProc"&gt; &lt;/span&gt;&lt;span class="synIdentifier"&gt;&amp;lt;&lt;/span&gt;&lt;span class="synStatement"&gt;meta&lt;/span&gt;&lt;span class="synIdentifier"&gt; &lt;/span&gt;&lt;span class="synType"&gt;charset&lt;/span&gt;&lt;span class="synIdentifier"&gt;=&lt;/span&gt;&lt;span class="synConstant"&gt;&amp;quot;utf-8&amp;quot;&lt;/span&gt;&lt;span class="synIdentifier"&gt;/&amp;gt;&lt;/span&gt;&#10;&lt;span class="synPreProc"&gt; &lt;/span&gt;&lt;span class="synIdentifier"&gt;&amp;lt;&lt;/span&gt;&lt;span class="synStatement"&gt;script&lt;/span&gt;&lt;span class="synIdentifier"&gt; &lt;/span&gt;&lt;span class="synType"&gt;type&lt;/span&gt;&lt;span class="synIdentifier"&gt;=&lt;/span&gt;&lt;span class="synConstant"&gt;&amp;quot;module&amp;quot;&lt;/span&gt;&lt;span class="synIdentifier"&gt;&amp;gt;&lt;/span&gt;&#10;&lt;span class="synSpecial"&gt; &lt;/span&gt;&lt;span class="synStatement"&gt;import&lt;/span&gt;&lt;span class="synSpecial"&gt; * as THREE from &lt;/span&gt;&lt;span class="synConstant"&gt;'&amp;lt;https://cdn.skypack.dev/three&amp;gt;'&lt;/span&gt;&lt;span class="synSpecial"&gt;;&lt;/span&gt;&#10;&#10;&lt;span class="synSpecial"&gt; &lt;/span&gt;&lt;span class="synStatement"&gt;window&lt;/span&gt;&lt;span class="synSpecial"&gt;.addEventListener&lt;/span&gt;(&lt;span class="synConstant"&gt;'load'&lt;/span&gt;&lt;span class="synSpecial"&gt;, init&lt;/span&gt;)&lt;span class="synSpecial"&gt;;&lt;/span&gt;&#10;&#10;&lt;span class="synSpecial"&gt; &lt;/span&gt;&lt;span class="synIdentifier"&gt;var&lt;/span&gt;&lt;span class="synSpecial"&gt; $view;&lt;/span&gt;&#10;&#10;&lt;span class="synSpecial"&gt; &lt;/span&gt;&lt;span class="synIdentifier"&gt;var&lt;/span&gt;&lt;span class="synSpecial"&gt; width = &lt;/span&gt;&lt;span class="synStatement"&gt;window&lt;/span&gt;&lt;span class="synSpecial"&gt;.innerWidth;&lt;/span&gt;&#10;&lt;span class="synSpecial"&gt; &lt;/span&gt;&lt;span class="synIdentifier"&gt;var&lt;/span&gt;&lt;span class="synSpecial"&gt; height = &lt;/span&gt;&lt;span class="synStatement"&gt;window&lt;/span&gt;&lt;span class="synSpecial"&gt;.innerHeight;&lt;/span&gt;&#10;&#10;&lt;span class="synSpecial"&gt; &lt;/span&gt;&lt;span class="synIdentifier"&gt;function&lt;/span&gt;&lt;span class="synSpecial"&gt; init&lt;/span&gt;()&#10;&lt;span class="synSpecial"&gt; &lt;/span&gt;&lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10;&lt;span class="synSpecial"&gt; $view = &lt;/span&gt;&lt;span class="synStatement"&gt;document&lt;/span&gt;&lt;span class="synSpecial"&gt;.querySelector&lt;/span&gt;(&lt;span class="synConstant"&gt;'#view'&lt;/span&gt;)&lt;span class="synSpecial"&gt;;&lt;/span&gt;&#10;&lt;span class="synSpecial"&gt; setup3D&lt;/span&gt;()&lt;span class="synSpecial"&gt;;&lt;/span&gt;&#10;&lt;span class="synSpecial"&gt; &lt;/span&gt;&lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&#10;&lt;span class="synSpecial"&gt; &lt;/span&gt;&lt;span class="synIdentifier"&gt;function&lt;/span&gt;&lt;span class="synSpecial"&gt; setup3D&lt;/span&gt;()&#10;&lt;span class="synSpecial"&gt; &lt;/span&gt;&lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10;&lt;span class="synSpecial"&gt; &lt;/span&gt;&lt;span class="synStatement"&gt;const&lt;/span&gt;&lt;span class="synSpecial"&gt; renderer = &lt;/span&gt;&lt;span class="synStatement"&gt;new&lt;/span&gt;&lt;span class="synSpecial"&gt; THREE.WebGLRenderer&lt;/span&gt;(&lt;span class="synIdentifier"&gt;{&lt;/span&gt;&lt;span class="synSpecial"&gt;alpha: &lt;/span&gt;&lt;span class="synConstant"&gt;true&lt;/span&gt;&lt;span class="synSpecial"&gt;, canvas: $view&lt;/span&gt;&lt;span class="synIdentifier"&gt;}&lt;/span&gt;)&lt;span class="synSpecial"&gt;;&lt;/span&gt;&#10;&lt;span class="synSpecial"&gt; renderer.setPixelRatio&lt;/span&gt;(&lt;span class="synStatement"&gt;window&lt;/span&gt;&lt;span class="synSpecial"&gt;.devicePixelRatio&lt;/span&gt;)&lt;span class="synSpecial"&gt;;&lt;/span&gt;&#10;&lt;span class="synSpecial"&gt; renderer.setSize&lt;/span&gt;(&lt;span class="synSpecial"&gt;width, height&lt;/span&gt;)&lt;span class="synSpecial"&gt;;&lt;/span&gt;&#10;&#10;&lt;span class="synSpecial"&gt; &lt;/span&gt;&lt;span class="synStatement"&gt;const&lt;/span&gt;&lt;span class="synSpecial"&gt; scene = &lt;/span&gt;&lt;span class="synStatement"&gt;new&lt;/span&gt;&lt;span class="synSpecial"&gt; THREE.Scene&lt;/span&gt;()&lt;span class="synSpecial"&gt;;&lt;/span&gt;&#10;&#10;&lt;span class="synSpecial"&gt; &lt;/span&gt;&lt;span class="synStatement"&gt;const&lt;/span&gt;&lt;span class="synSpecial"&gt; camera = &lt;/span&gt;&lt;span class="synStatement"&gt;new&lt;/span&gt;&lt;span class="synSpecial"&gt; THREE.PerspectiveCamera&lt;/span&gt;(80&lt;span class="synSpecial"&gt;, width / height, &lt;/span&gt;0&lt;span class="synSpecial"&gt;.&lt;/span&gt;1&lt;span class="synSpecial"&gt;, &lt;/span&gt;100)&lt;span class="synSpecial"&gt;;&lt;/span&gt;&#10;&lt;span class="synSpecial"&gt; camera.position.set&lt;/span&gt;(0&lt;span class="synSpecial"&gt;, &lt;/span&gt;0&lt;span class="synSpecial"&gt;, &lt;/span&gt;10)&lt;span class="synSpecial"&gt;;&lt;/span&gt;&#10;&#10;&lt;span class="synSpecial"&gt; &lt;/span&gt;&lt;span class="synStatement"&gt;const&lt;/span&gt;&lt;span class="synSpecial"&gt; geometry = &lt;/span&gt;&lt;span class="synStatement"&gt;new&lt;/span&gt;&lt;span class="synSpecial"&gt; THREE.BoxGeometry&lt;/span&gt;(1&lt;span class="synSpecial"&gt;, &lt;/span&gt;1&lt;span class="synSpecial"&gt;, &lt;/span&gt;1)&lt;span class="synSpecial"&gt;;&lt;/span&gt;&#10;&lt;span class="synSpecial"&gt; &lt;/span&gt;&lt;span class="synStatement"&gt;const&lt;/span&gt;&lt;span class="synSpecial"&gt; material = &lt;/span&gt;&lt;span class="synStatement"&gt;new&lt;/span&gt;&lt;span class="synSpecial"&gt; THREE.MeshNormalMaterial&lt;/span&gt;()&lt;span class="synSpecial"&gt;;&lt;/span&gt;&#10;&lt;span class="synSpecial"&gt; &lt;/span&gt;&lt;span class="synStatement"&gt;const&lt;/span&gt;&lt;span class="synSpecial"&gt; box = &lt;/span&gt;&lt;span class="synStatement"&gt;new&lt;/span&gt;&lt;span class="synSpecial"&gt; THREE.Mesh&lt;/span&gt;(&lt;span class="synSpecial"&gt;geometry, material&lt;/span&gt;)&lt;span class="synSpecial"&gt;;&lt;/span&gt;&#10;&lt;span class="synSpecial"&gt; box.position.set&lt;/span&gt;(0&lt;span class="synSpecial"&gt;, &lt;/span&gt;0&lt;span class="synSpecial"&gt;, &lt;/span&gt;0)&lt;span class="synSpecial"&gt;;&lt;/span&gt;&#10;&lt;span class="synSpecial"&gt; scene.add&lt;/span&gt;(&lt;span class="synSpecial"&gt;box&lt;/span&gt;)&lt;span class="synSpecial"&gt;;&lt;/span&gt;&#10;&#10;&lt;span class="synSpecial"&gt; update&lt;/span&gt;()&lt;span class="synSpecial"&gt;;&lt;/span&gt;&#10;&lt;span class="synSpecial"&gt; resolve&lt;/span&gt;()&lt;span class="synSpecial"&gt;;&lt;/span&gt;&#10;&#10;&lt;span class="synSpecial"&gt; &lt;/span&gt;&lt;span class="synIdentifier"&gt;function&lt;/span&gt;&lt;span class="synSpecial"&gt; update&lt;/span&gt;()&#10;&lt;span class="synSpecial"&gt; &lt;/span&gt;&lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10;&lt;span class="synSpecial"&gt; requestAnimationFrame&lt;/span&gt;(&lt;span class="synSpecial"&gt;update&lt;/span&gt;)&lt;span class="synSpecial"&gt;;&lt;/span&gt;&#10;&lt;span class="synSpecial"&gt; renderer.render&lt;/span&gt;(&lt;span class="synSpecial"&gt;scene, camera&lt;/span&gt;)&lt;span class="synSpecial"&gt;;&lt;/span&gt;&#10;&lt;span class="synSpecial"&gt; &lt;/span&gt;&lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&lt;span class="synSpecial"&gt; &lt;/span&gt;&lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&#10;&lt;span class="synSpecial"&gt; &lt;/span&gt;&lt;span class="synIdentifier"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="synStatement"&gt;script&lt;/span&gt;&lt;span class="synIdentifier"&gt;&amp;gt;&lt;/span&gt;&#10;&#10;&lt;span class="synIdentifier"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="synStatement"&gt;head&lt;/span&gt;&lt;span class="synIdentifier"&gt;&amp;gt;&lt;/span&gt;&#10;&lt;span class="synIdentifier"&gt;&amp;lt;&lt;/span&gt;&lt;span class="synStatement"&gt;body&lt;/span&gt;&lt;span class="synIdentifier"&gt;&amp;gt;&lt;/span&gt;&#10; &lt;span class="synIdentifier"&gt;&amp;lt;&lt;/span&gt;&lt;span class="synStatement"&gt;canvas&lt;/span&gt;&lt;span class="synIdentifier"&gt; &lt;/span&gt;&lt;span class="synType"&gt;id&lt;/span&gt;&lt;span class="synIdentifier"&gt;=&lt;/span&gt;&lt;span class="synConstant"&gt;&amp;quot;view&amp;quot;&lt;/span&gt;&lt;span class="synIdentifier"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="synStatement"&gt;canvas&lt;/span&gt;&lt;span class="synIdentifier"&gt;&amp;gt;&lt;/span&gt;&#10;&lt;span class="synIdentifier"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="synStatement"&gt;body&lt;/span&gt;&lt;span class="synIdentifier"&gt;&amp;gt;&lt;/span&gt;&#10;&lt;span class="synIdentifier"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="synStatement"&gt;html&lt;/span&gt;&lt;span class="synIdentifier"&gt;&amp;gt;&lt;/span&gt;&#10;&lt;/pre&gt;&#10;&lt;p&gt;以下のページを参考にしています。&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://ics.media/tutorial-three/quickstart/"&gt;&amp;#x7C21;&amp;#x5358;&amp;#x306A;Three.js&amp;#x306E;&amp;#x30B5;&amp;#x30F3;&amp;#x30D7;&amp;#x30EB;&amp;#x3092;&amp;#x8A66;&amp;#x305D;&amp;#x3046; - ICS MEDIA&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;キューブの座標を(0, 0, 0)に設定していますが、z:0 = 現実世界の真北 にしたいので、キューブが常に真北に表示されるようになればモックの完成です。&lt;/p&gt;&#10;&lt;h3&gt;デバイスの向きの取得&lt;/h3&gt;&#10;&lt;p&gt;Three.jsのDeviceOrientationControlsを使用してデバイスの向きと3D上の世界を同期させます。&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://threejs.org/docs/index.html?q=device#examples/en/controls/DeviceOrientationControls"&gt;three.js docs DeviceOrientationControls&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;ですが、デフォルトのままだと、iOSでは起動時の向きがz:0に補正され、Androidでは起動するたびに0の向きが変わってしまいます。&lt;/p&gt;&#10;&lt;h3&gt;絶対値で向きを取得するイベントに差し替える&lt;/h3&gt;&#10;&lt;p&gt;deviceorientationabsoluteというイベントがあり、その名のとおり絶対値でデバイスの向きを取得できるAPIです。&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/ondeviceorientationabsolute"&gt;Window.ondeviceorientationabsolute - Web APIs | MDN&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;まず、Three.jsのDeviceOrientationControlsクラスを複製して新しいjsを作ります。すべての内容を載せると長くなるので、ポイントのみ記述します。&lt;/p&gt;&#10;&lt;p&gt;なお、完成品のDeviceOrientationControlsクラスはこちらになりますので、先に載せておきます。&lt;br /&gt;&#10;&lt;a href="https://yuko-katsuda.github.io/armock/DeviceOrientationControls.js"&gt;https://yuko-katsuda.github.io/armock/DeviceOrientationControls.js&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;複製したファイル内でdeviceorientationイベントを走らせているところを探し、そこをdeviceorientationabsoluteに書き換えました。&lt;br /&gt;&#10;ライブラリのバージョンによって内容に若干違いはあるかもしれませんが、this.connect関数の中にあります。&lt;/p&gt;&#10;&lt;p&gt;変更前&lt;/p&gt;&#10;&lt;pre class="code lang-javascript" data-lang="javascript" data-unlink&gt;&lt;span class="synIdentifier"&gt;this&lt;/span&gt;.connect = &lt;span class="synIdentifier"&gt;function&lt;/span&gt; () &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10;&#10; onScreenOrientationChangeEvent(); &lt;span class="synComment"&gt;// run once on load&lt;/span&gt;&#10;&#10; &lt;span class="synComment"&gt;// iOS 13+&lt;/span&gt;&#10;&#10; &lt;span class="synStatement"&gt;if&lt;/span&gt; ( &lt;span class="synStatement"&gt;window&lt;/span&gt;.DeviceOrientationEvent !== &lt;span class="synStatement"&gt;undefined&lt;/span&gt; &amp;amp;&amp;amp; &lt;span class="synStatement"&gt;typeof&lt;/span&gt; &lt;span class="synStatement"&gt;window&lt;/span&gt;.DeviceOrientationEvent.requestPermission === &lt;span class="synConstant"&gt;'function'&lt;/span&gt; ) &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10;&#10; &lt;span class="synStatement"&gt;window&lt;/span&gt;.DeviceOrientationEvent.requestPermission().then( &lt;span class="synIdentifier"&gt;function&lt;/span&gt; ( response ) &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10;&#10; &lt;span class="synStatement"&gt;if&lt;/span&gt; ( response == &lt;span class="synConstant"&gt;'granted'&lt;/span&gt; ) &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10;&#10; &lt;span class="synStatement"&gt;window&lt;/span&gt;.addEventListener( &lt;span class="synConstant"&gt;'orientationchange'&lt;/span&gt;, onScreenOrientationChangeEvent );&#10; &lt;span class="synStatement"&gt;window&lt;/span&gt;.addEventListener( &lt;span class="synConstant"&gt;'deviceorientation'&lt;/span&gt;, onDeviceOrientationChangeEvent );&#10;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt; ).&lt;span class="synStatement"&gt;catch&lt;/span&gt;( &lt;span class="synIdentifier"&gt;function&lt;/span&gt; ( error ) &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10;&#10; console.error( &lt;span class="synConstant"&gt;'THREE.DeviceOrientationControls: Unable to use DeviceOrientation API:'&lt;/span&gt;, error );&#10;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt; );&#10;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt; &lt;span class="synStatement"&gt;else&lt;/span&gt; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10;&#10; &lt;span class="synStatement"&gt;window&lt;/span&gt;.addEventListener( &lt;span class="synConstant"&gt;'orientationchange'&lt;/span&gt;, onScreenOrientationChangeEvent );&#10; &lt;span class="synStatement"&gt;window&lt;/span&gt;.addEventListener( &lt;span class="synConstant"&gt;'deviceorientation'&lt;/span&gt;, onDeviceOrientationChangeEvent );&#10;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&#10; scope.enabled = &lt;span class="synConstant"&gt;true&lt;/span&gt;;&#10;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;;&#10;&lt;/pre&gt;&#10;&lt;p&gt;変更後&lt;/p&gt;&#10;&lt;pre class="code lang-javascript" data-lang="javascript" data-unlink&gt;&lt;span class="synIdentifier"&gt;this&lt;/span&gt;.connect = &lt;span class="synIdentifier"&gt;function&lt;/span&gt; () &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10;&#10; onScreenOrientationChangeEvent(); &lt;span class="synComment"&gt;// run once on load&lt;/span&gt;&#10;&#10; &lt;span class="synComment"&gt;// iOS 13+&lt;/span&gt;&#10;&#10; &lt;span class="synStatement"&gt;if&lt;/span&gt; ( &lt;span class="synStatement"&gt;window&lt;/span&gt;.DeviceOrientationEvent !== &lt;span class="synStatement"&gt;undefined&lt;/span&gt; &amp;amp;&amp;amp; &lt;span class="synStatement"&gt;typeof&lt;/span&gt; &lt;span class="synStatement"&gt;window&lt;/span&gt;.DeviceOrientationEvent.requestPermission === &lt;span class="synConstant"&gt;'function'&lt;/span&gt; ) &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10;&#10; &lt;span class="synStatement"&gt;window&lt;/span&gt;.DeviceOrientationEvent.requestPermission().then( &lt;span class="synIdentifier"&gt;function&lt;/span&gt; ( response ) &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10;&#10; &lt;span class="synStatement"&gt;if&lt;/span&gt; ( response == &lt;span class="synConstant"&gt;'granted'&lt;/span&gt; ) &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10;&#10; &lt;span class="synStatement"&gt;window&lt;/span&gt;.addEventListener( &lt;span class="synConstant"&gt;'orientationchange'&lt;/span&gt;, onScreenOrientationChangeEvent );&#10; &lt;span class="synStatement"&gt;window&lt;/span&gt;.addEventListener( &lt;span class="synConstant"&gt;'deviceorientation'&lt;/span&gt;, onDeviceOrientationChangeEvent );&#10;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt; ).&lt;span class="synStatement"&gt;catch&lt;/span&gt;( &lt;span class="synIdentifier"&gt;function&lt;/span&gt; ( error ) &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10;&#10; console.error( &lt;span class="synConstant"&gt;'THREE.DeviceOrientationControls: Unable to use DeviceOrientation API:'&lt;/span&gt;, error );&#10;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt; );&#10;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt; &lt;span class="synStatement"&gt;else&lt;/span&gt; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10;&#10; &lt;span class="synStatement"&gt;window&lt;/span&gt;.addEventListener( &lt;span class="synConstant"&gt;'orientationchange'&lt;/span&gt;, onScreenOrientationChangeEvent );&#10; &lt;span class="synStatement"&gt;window&lt;/span&gt;.addEventListener( &lt;span class="synConstant"&gt;'deviceorientationabsolute&lt;/span&gt;&#10;&lt;span class="synConstant"&gt;', onDeviceOrientationChangeEvent );&lt;/span&gt;&#10;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&#10; scope.enabled = &lt;span class="synConstant"&gt;true&lt;/span&gt;;&#10;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;;&#10;&lt;/pre&gt;&#10;&lt;p&gt;iOS Safariはdeviceorientationabsoluteに対応していません。&lt;br /&gt;&#10;なので、以下のようにコードを書き換えました。&lt;/p&gt;&#10;&lt;pre class="code lang-javascript" data-lang="javascript" data-unlink&gt;&lt;span class="synStatement"&gt;const&lt;/span&gt; ua = navigator.userAgent&#10;&lt;span class="synStatement"&gt;if&lt;/span&gt;(&lt;span class="synConstant"&gt;/android/i&lt;/span&gt;.test(ua))&#10;&lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synStatement"&gt;window&lt;/span&gt;.addEventListener( &lt;span class="synConstant"&gt;'orientationchange'&lt;/span&gt;, onScreenOrientationChangeEvent, &lt;span class="synConstant"&gt;false&lt;/span&gt; );&#10; &lt;span class="synStatement"&gt;window&lt;/span&gt;.addEventListener( &lt;span class="synConstant"&gt;'deviceorientationabsolute'&lt;/span&gt;, onDeviceOrientationChangeEvent, &lt;span class="synConstant"&gt;false&lt;/span&gt; );&#10;&lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&lt;span class="synStatement"&gt;else&lt;/span&gt;&#10;&lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synStatement"&gt;window&lt;/span&gt;.addEventListener( &lt;span class="synConstant"&gt;'orientationchange'&lt;/span&gt;, onScreenOrientationChangeEvent, &lt;span class="synConstant"&gt;false&lt;/span&gt; );&#10; &lt;span class="synStatement"&gt;window&lt;/span&gt;.addEventListener( &lt;span class="synConstant"&gt;'deviceorientation'&lt;/span&gt;, onDeviceOrientationChangeEvent, &lt;span class="synConstant"&gt;false&lt;/span&gt; );&#10;&lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&lt;/pre&gt;&#10;&lt;p&gt;出来上がったDeviceOrientationControlsクラスをimportして、setup3D関数でコネクトします。&lt;/p&gt;&#10;&lt;p&gt;ライブラリのimport&lt;/p&gt;&#10;&lt;pre class="code lang-javascript" data-lang="javascript" data-unlink&gt; &lt;span class="synStatement"&gt;import&lt;/span&gt; * as THREE from &lt;span class="synConstant"&gt;'&amp;lt;https://cdn.skypack.dev/three&amp;gt;'&lt;/span&gt;;&#10; &lt;span class="synStatement"&gt;import&lt;/span&gt; &lt;span class="synIdentifier"&gt;{&lt;/span&gt; MathUtils &lt;span class="synIdentifier"&gt;}&lt;/span&gt; from &lt;span class="synConstant"&gt;'&amp;lt;https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.module.js&amp;gt;'&lt;/span&gt;;&#10; &lt;span class="synStatement"&gt;import&lt;/span&gt; &lt;span class="synIdentifier"&gt;{&lt;/span&gt; DeviceOrientationControls &lt;span class="synIdentifier"&gt;}&lt;/span&gt; from &lt;span class="synConstant"&gt;'./DeviceOrientationControls.js'&lt;/span&gt;;&#10;&lt;/pre&gt;&#10;&lt;p&gt;setup3D関数&lt;/p&gt;&#10;&lt;pre class="code lang-javascript" data-lang="javascript" data-unlink&gt; &lt;span class="synIdentifier"&gt;function&lt;/span&gt; setup3D()&#10; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synStatement"&gt;const&lt;/span&gt; renderer = &lt;span class="synStatement"&gt;new&lt;/span&gt; THREE.WebGLRenderer(&lt;span class="synIdentifier"&gt;{&lt;/span&gt;alpha: &lt;span class="synConstant"&gt;true&lt;/span&gt;, canvas: $view&lt;span class="synIdentifier"&gt;}&lt;/span&gt;);&#10; renderer.setPixelRatio(&lt;span class="synStatement"&gt;window&lt;/span&gt;.devicePixelRatio);&#10; renderer.setSize(width, height);&#10;&#10; &lt;span class="synStatement"&gt;const&lt;/span&gt; scene = &lt;span class="synStatement"&gt;new&lt;/span&gt; THREE.Scene();&#10;&#10; &lt;span class="synStatement"&gt;const&lt;/span&gt; camera = &lt;span class="synStatement"&gt;new&lt;/span&gt; THREE.PerspectiveCamera(80, width / height, 0.1, 100);&#10; camera.position.set(0, 0, 10);&#10;&#10; &lt;span class="synComment"&gt;// DeviceOrientationControlsをコネクトさせる&lt;/span&gt;&#10; &lt;span class="synIdentifier"&gt;var&lt;/span&gt; controls = &lt;span class="synStatement"&gt;new&lt;/span&gt; DeviceOrientationControls(camera);&#10; controls.connect();&#10;&#10; &lt;span class="synStatement"&gt;const&lt;/span&gt; geometry = &lt;span class="synStatement"&gt;new&lt;/span&gt; THREE.BoxGeometry(1, 1, 1);&#10; &lt;span class="synStatement"&gt;const&lt;/span&gt; material = &lt;span class="synStatement"&gt;new&lt;/span&gt; THREE.MeshNormalMaterial();&#10; &lt;span class="synStatement"&gt;const&lt;/span&gt; box = &lt;span class="synStatement"&gt;new&lt;/span&gt; THREE.Mesh(geometry, material);&#10; box.position.set(0, 0, 0);&#10; scene.add(box);&#10;&#10; update();&#10;&#10; &lt;span class="synIdentifier"&gt;function&lt;/span&gt; update()&#10; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; requestAnimationFrame(update);&#10; renderer.render(scene, camera);&#10;&#10; &lt;span class="synComment"&gt;// コントロールを更新&lt;/span&gt;&#10; controls.update();&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&lt;/pre&gt;&#10;&lt;h3&gt;iOSでの向きを地球軸にする&lt;/h3&gt;&#10;&lt;p&gt;iOSではThree.jsを実行した向きが0として補正されてしまいます。&lt;br /&gt;&#10;なので、ページを表示した瞬間のalpha値をalphaOffsetに渡し、強制的に補正前に戻すようにしました。&lt;/p&gt;&#10;&lt;pre class="code lang-javascript" data-lang="javascript" data-unlink&gt; &lt;span class="synComment"&gt;// DeviceOrientationControlsをコネクトさせる&lt;/span&gt;&#10; &lt;span class="synIdentifier"&gt;var&lt;/span&gt; controls = &lt;span class="synStatement"&gt;new&lt;/span&gt; DeviceOrientationControls(camera);&#10; controls.connect();&#10;&#10; &lt;span class="synComment"&gt;// iOSのとき、一度だけalphaOffsetに補正値を渡す&lt;/span&gt;&#10; &lt;span class="synStatement"&gt;if&lt;/span&gt;(os == &lt;span class="synConstant"&gt;'ios'&lt;/span&gt;)&#10; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synIdentifier"&gt;var&lt;/span&gt; deg = 0;&#10; &lt;span class="synIdentifier"&gt;var&lt;/span&gt; diff = &lt;span class="synStatement"&gt;null&lt;/span&gt;;&#10; &lt;span class="synIdentifier"&gt;var&lt;/span&gt; doneCalibration = &lt;span class="synConstant"&gt;false&lt;/span&gt;;&#10;&#10; &lt;span class="synStatement"&gt;window&lt;/span&gt;.addEventListener(&lt;span class="synConstant"&gt;'deviceorientation'&lt;/span&gt;, (e)=&amp;gt; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; deg = e.webkitCompassHeading;&#10; &lt;span class="synStatement"&gt;if&lt;/span&gt;(deg != 0 &amp;amp;&amp;amp; diff == &lt;span class="synStatement"&gt;null&lt;/span&gt;)&#10; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; diff = deg;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&#10; &lt;span class="synStatement"&gt;if&lt;/span&gt;(!doneCalibration)&#10; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; doneCalibration = &lt;span class="synConstant"&gt;true&lt;/span&gt;;&#10; controls.alphaOffset = MathUtils.degToRad( - diff );&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;, &lt;span class="synConstant"&gt;true&lt;/span&gt;);&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&lt;/pre&gt;&#10;&lt;h3&gt;カメラの取得&lt;/h3&gt;&#10;&lt;p&gt;続いてカメラ映像の取得です。&lt;br /&gt;&#10;Media Streams APIを使用してストリームを取得し、htmlのvideo要素に渡しています。&lt;/p&gt;&#10;&lt;pre class="code lang-javascript" data-lang="javascript" data-unlink&gt; &lt;span class="synIdentifier"&gt;function&lt;/span&gt; setupCamera()&#10; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synStatement"&gt;return&lt;/span&gt; &lt;span class="synStatement"&gt;new&lt;/span&gt; Promise((resolve, reject)=&amp;gt;&lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synStatement"&gt;window&lt;/span&gt;.mediaDevices = navigator.mediaDevices || ((navigator.mozGetUserMedia || navigator.webkitGetUserMedia) ?&#10; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; getUserMedia: (c)=&amp;gt; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synStatement"&gt;return&lt;/span&gt; &lt;span class="synStatement"&gt;new&lt;/span&gt; Promise((y, n)=&amp;gt; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; (navigator.mozGetUserMedia ||&#10; navigator.webkitGetUserMedia).call(navigator, c, y, n);&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;);&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt; : &lt;span class="synStatement"&gt;null&lt;/span&gt;);&#10;&#10; &lt;span class="synStatement"&gt;const&lt;/span&gt; mediaConstraints = &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; audio: &lt;span class="synConstant"&gt;false&lt;/span&gt;,&#10; video: &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; width: 1920,&#10; height: 1080,&#10; facingMode: &lt;span class="synIdentifier"&gt;{&lt;/span&gt; exact: &lt;span class="synConstant"&gt;'environment'&lt;/span&gt; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;;&#10;&#10; &lt;span class="synStatement"&gt;window&lt;/span&gt;.mediaDevices.getUserMedia(mediaConstraints)&#10; .then((stream)=&amp;gt;&#10; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; $video.srcObject = stream;&#10;&#10; $video.onloadedmetadata = (e) =&amp;gt;&#10; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; $video.play();&#10; resolve();&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;)&#10; .&lt;span class="synStatement"&gt;catch&lt;/span&gt;((err) =&amp;gt;&#10; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; reject();&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;);&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;);&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&lt;/pre&gt;&#10;&lt;p&gt;&lt;a href="https://qiita.com/Futo_Horio/items/bff1ce1d2e1b219b243d"&gt;「MediaDevices.getUserMedia() 」について - Qiita&lt;/a&gt;&lt;a href="https://developer.mozilla.org/ja/docs/Web/API/MediaDevices/getUserMedia"&gt;MediaDevices.getUserMedia() - Web API | MDN&lt;/a&gt;&lt;/p&gt;&#10;&lt;h3&gt;位置情報の取得&lt;/h3&gt;&#10;&lt;p&gt;位置情報の取得にはGeolocation APIを使用しています。&lt;br /&gt;&#10;getCurrentPositionというメソッドを実行すると、latitude（緯度）、longitude（経度）などの値が返ってきます。&lt;/p&gt;&#10;&lt;pre class="code lang-javascript" data-lang="javascript" data-unlink&gt; &lt;span class="synIdentifier"&gt;function&lt;/span&gt; setupGeo()&#10; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synStatement"&gt;return&lt;/span&gt; &lt;span class="synStatement"&gt;new&lt;/span&gt; Promise((resolve, reject)=&amp;gt;&lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; navigator.geolocation.getCurrentPosition((data)=&amp;gt;&lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; resolve(data);&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;,&#10; (e)=&amp;gt;&lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; reject(e);&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;);&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10; &lt;span class="synStatement"&gt;catch&lt;/span&gt;(e)&#10; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; reject(e);&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&lt;/pre&gt;&#10;&lt;p&gt;&lt;a href="https://developer.mozilla.org/ja/docs/Web/API/Geolocation/getCurrentPosition"&gt;Geolocation.getCurrentPosition() - Web API | MDN&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;これで3つの機能の取得ができました。&lt;/p&gt;&#10;&lt;h3&gt;デバイスの許可フローを作る&lt;/h3&gt;&#10;&lt;p&gt;deviceorientationを取得するためには、ユーザーになんらかのアクションをさせ、そのイベント内で許可フローを実行しなければなりません。&lt;br /&gt;&#10;また、カメラで取得した映像をJavaScriptから再生させるためにも事前のユーザーインタラクションが必要になるので、以下のようにボタン押下イベントを起点とする許可フローを作成しました。&lt;/p&gt;&#10;&lt;pre class="code lang-javascript" data-lang="javascript" data-unlink&gt; &lt;span class="synIdentifier"&gt;function&lt;/span&gt; init()&#10; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synComment"&gt;// html要素を取得&lt;/span&gt;&#10; $view = &lt;span class="synStatement"&gt;document&lt;/span&gt;.querySelector(&lt;span class="synConstant"&gt;'#view'&lt;/span&gt;);&#10; $video = &lt;span class="synStatement"&gt;document&lt;/span&gt;.querySelector(&lt;span class="synConstant"&gt;'#video'&lt;/span&gt;);&#10; $button = &lt;span class="synStatement"&gt;document&lt;/span&gt;.querySelector(&lt;span class="synConstant"&gt;'#button'&lt;/span&gt;);&#10;&#10; &lt;span class="synComment"&gt;// ボタンをクリックするとonClickを実行&lt;/span&gt;&#10; $button.addEventListener(&lt;span class="synConstant"&gt;'click'&lt;/span&gt;, ()=&amp;gt;&lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; onClick();&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;);&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&#10; &lt;span class="synIdentifier"&gt;function&lt;/span&gt; onClick()&#10; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synComment"&gt;// ボタン押下後、非表示にする&lt;/span&gt;&#10; $button.classList.add(&lt;span class="synConstant"&gt;'hide'&lt;/span&gt;);&#10;&#10; &lt;span class="synComment"&gt;// 3D、カメラ、位置情報を順番に取得していく&lt;/span&gt;&#10; setup3D(()=&amp;gt;&lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; Promise.resolve()&#10; .then( () =&amp;gt; setupCamera() )&#10; .then( () =&amp;gt; setupGeo() )&#10; .then( (data) =&amp;gt; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; console.log(data);&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;)&#10; ;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;);&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&lt;/pre&gt;&#10;&lt;p&gt;ARきぼう予報では、ジャイロ、カメラ、位置情報の3種類すべてを使用しているため、それぞれの機能をPromiseで繋いでいます。&lt;/p&gt;&#10;&lt;p&gt;なお、モックでは端折っていますが、ユーザー側での拒否や端末の非対応など何らかのエラーが発生するとrejectに繋いでヘルプページに遷移するようにしていました。&lt;/p&gt;&#10;&lt;p&gt;これでモックの完成です。&lt;br /&gt;&#10;このように、ジャイロ、カメラ、位置情報の3つを同期させ、さらに、ISSの位置情報や、地球の自転、公転による天体との位置関係を組み合わせることでARきぼう予報を実現しています。&lt;/p&gt;&#10;&lt;p&gt;&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/OexFv-eJMIk?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://youtu.be/OexFv-eJMIk"&gt;youtu.be&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;&lt;h2&gt;どこまで機能するか？&lt;/h2&gt;&#10;&lt;p&gt;ジャイロの精度が端末ごとにかなりぶれがあり、完璧ではないという問題がありました（特にAndroid）&lt;br /&gt;&#10;比較的安定しているiOSも、deviceorientationのずれを補正するのに起動直後の値を使用しているため、AR実行時にスマホを持つ手が大きく揺れていたりするとずれてしまう可能性がありました。&lt;/p&gt;&#10;&lt;p&gt;デバイスのセンサーを取得しているため、デバイス側がずれているとどうしようもないわけです。そのような限界がある中で、ISSの観測ツールとしてARきぼう予報がどこまで機能するか？という不安がありました。&lt;br /&gt;&#10;ちなみにISSは毎日見えるわけではありません。条件が合わないと1ヶ月近く肉眼で見えるタイミングがなかったりします。&lt;br /&gt;&#10;なかなかISS観測をするチャンスがないまま、開発を進めていました。&lt;/p&gt;&#10;&lt;h3&gt;開発版のチェック&lt;/h3&gt;&#10;&lt;p&gt;きぼうを見ようチームのメンバーは関東と関西それぞれ住んでいるところはバラバラです。&lt;br /&gt;&#10;AR機能がある程度動作するようになったころ、全国的にISSを観測できる好条件がそろった日があり、開発版をチームでチェックすることになりました。&lt;br /&gt;&#10;なお、この時点で私はARを使ったISSの観測が一度もできていません。使い物にならなかったらどうしよう…とドキドキしながらチェックに臨みました。&lt;/p&gt;&#10;&lt;p&gt;結果、少しずれはありましたが、ARを使うことでこれまでよりも格段にISSの観測がしやすくなることがわかりました。&lt;br /&gt;&#10;ISSは弱い光が自分の真上に近づくにつれて徐々に強い光へと変わっていくのですが、かなり弱い光の時点からISSを発見することができました。リアルタイムのISSの軌道がAR上に表示されているため、肉眼で確認するよりもずっと早くISSが昇ってくる位置の目星がつけられるのです。これなら誰にでも簡単にISSを見つけることができると確信しました。&lt;/p&gt;&#10;&lt;p&gt;ちなみにこの日、「みんなで #きぼうを見よう LIVE」というTwitter Spacesで全国の方とお話しながらISSを観測するイベントがKIBO宇宙放送局Twitterアカウントで行われていたのですが、LIVEの前に星出彰彦宇宙飛行士からこんなツイートがありました。&lt;/p&gt;&#10;&lt;p&gt;&lt;blockquote data-conversation="none" class="twitter-tweet" data-lang="en"&gt;&lt;p lang="ja" dir="ltr"&gt;手を振りますねー！ &lt;a href="https://t.co/iaRKZ4C4ho"&gt;https://t.co/iaRKZ4C4ho&lt;/a&gt;&lt;/p&gt;&amp;mdash; 星出 彰彦 (JAXA宇宙飛行士) (@Aki_Hoshide) &lt;a href="https://twitter.com/Aki_Hoshide/status/1393089690182602758?ref_src=twsrc%5Etfw"&gt;May 14, 2021&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt; &lt;/p&gt;&#10;&lt;p&gt;地上とISSは約400km以上離れています。地上からはISSは星の光のように見えます。&lt;br /&gt;&#10;あの光の場所に人がいるんだなあと思うと、なんだか信じられないような不思議な感動があります。そして、いま私と同じように空を眺めている人たちが日本中にいて、会ったことはないけど同じ感動を共有してるってすごいことだと思いました。&lt;br /&gt;&#10;（その感動は、「今見上げている人数」という機能に引き継がれました）&lt;/p&gt;&#10;&lt;p&gt;星出宇宙飛行士もそんな気持ちだったらいいなあと思いながら、いっぱい手を振りました！&lt;/p&gt;&#10;&lt;p&gt;&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/3KR2n6nG_S4?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://youtu.be/3KR2n6nG_S4"&gt;youtu.be&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;&lt;p&gt;そのときのTwitter Spacesの様子がこちらです。&lt;br /&gt;&#10;全国のみなさんといっしょに空を渡るISSを探しながら、星出宇宙飛行士に手を振った、すてきな時間でした。&lt;/p&gt;&#10;&lt;p&gt;「みんなで #きぼうを見よう LIVE」はときどき開催しておりますので、ぜひAR機能を使って参加してみてください！肉眼で見えるISSは本当に綺麗です。&lt;br /&gt;&#10;KIBO宇宙放送局のTwitterで随時お知らせ中です。&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://twitter.com/KIBO_SPACE"&gt;KIBO&amp;#x5B87;&amp;#x5B99;&amp;#x653E;&amp;#x9001;&amp;#x5C40; (@KIBO_SPACE) | Twitter&lt;/a&gt;&lt;/p&gt;&#10;&lt;h2&gt;エンジニア募集してます！&lt;/h2&gt;&#10;&lt;p&gt;そして、バスキュールではただいま新しい仲間を募集しています。&lt;br /&gt;&#10;まだ誰も体験したことのないようなものを作る感動を味わってみたいという方、一緒に働きませんか？&lt;br /&gt;&#10;ぜひぜひご応募お待ちしております！！&#10;&lt;iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fbascule.co.jp%2Fjoin-us%2F" title="JOIN US - Bascule Inc." class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://bascule.co.jp/join-us/"&gt;bascule.co.jp&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;</content><link rel="enclosure" href="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20211019/20211019184944.jpg" type="image/jpeg" length="0"/><author><name>bascule-dev</name></author></entry><entry><title>Nuxt.jsを使用したスクロール連動の演出実装</title><link href="https://blog.bascule.co.jp/entry/2021/09/15/110834"/><id>hatenablog://entry/26006613803876208</id><published>2021-09-15T11:08:34+09:00</published><updated>2021-09-15T02:08:34+09:00</updated><summary type="html">こんにちは、エンジニアの野島(@daumkuchen)です。 弊社で企画・制作した、⾃然エネルギーの爆発的普及を実現する新会社「PowerX」のサイトを先日ローンチしました。 https://power-x.jp/ ⾃然エネルギーの爆発的普及を実現する新会社「PowerX」のサイトを企画・制作しました。「洋上で発電した電力を蓄電し、船で陸地に運ぶ」という規格外なビジョンを持ったベンチャー企業のパワーとスケール感を、全面にプレゼンテーションするサイトになっています。https://t.co/s9e9uEXkfM— Bascule Inc. - Project Design Studio (@Ba…</summary><content type="html">&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20210902/20210902211228.jpg" alt="f:id:bascule-dev:20210902211228j:plain" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;p&gt;こんにちは、エンジニアの野島(&lt;a href="https://twitter.com/daumkuchen"&gt;@daumkuchen&lt;/a&gt;)です。&lt;/p&gt;&#10;&lt;p&gt;弊社で企画・制作した、⾃然エネルギーの爆発的普及を実現する新会社「&lt;strong&gt;PowerX&lt;/strong&gt;」のサイトを先日ローンチしました。&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://power-x.jp/"&gt;https://power-x.jp/&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;blockquote data-conversation="none" class="twitter-tweet" data-lang="ja"&gt;&lt;p lang="ja" dir="ltr"&gt;⾃然エネルギーの爆発的普及を実現する新会社「PowerX」のサイトを企画・制作しました。&lt;br&gt;&lt;br&gt;「洋上で発電した電力を蓄電し、船で陸地に運ぶ」という規格外なビジョンを持ったベンチャー企業のパワーとスケール感を、全面にプレゼンテーションするサイトになっています。&lt;a href="https://t.co/s9e9uEXkfM"&gt;https://t.co/s9e9uEXkfM&lt;/a&gt;&lt;/p&gt;&amp;mdash; Bascule Inc. - Project Design Studio (@Bascule_Inc) &lt;a href="https://twitter.com/Bascule_Inc/status/1430399138579972099?ref_src=twsrc%5Etfw"&gt;2021年8月25日&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt; &lt;/p&gt;&#10;&lt;p&gt;こちらのサイトはフロントエンドをNuxt.jsで構築しており、その中でスクロール連動の演出実装にチャレンジしました。&lt;br /&gt;&#10;そこから技術的な見どころを少しご紹介させていただきます。&lt;/p&gt;&#10;&lt;h2&gt;サイトの見どころ&lt;/h2&gt;&#10;&lt;h3&gt;スムーススクロール + 無限スクロール&lt;/h3&gt;&#10;&lt;p&gt;&lt;figure class="figure-image figure-image-fotolife" title="モック"&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20210902/20210902210931.gif" alt="f:id:bascule-dev:20210902210931g:plain" width="720" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;figcaption&gt;モック&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;figure class="figure-image figure-image-fotolife" title="完成"&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20210902/20210902204916.gif" alt="f:id:bascule-dev:20210902204916g:plain" width="720" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;figcaption&gt;完成&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&#10;&lt;p&gt;「&lt;strong&gt;スムーススクロール&lt;/strong&gt;」については、ブラウザのデフォルトのスクロール機能は使っていません。&lt;br /&gt;&#10;DOMのコンテナをjsでアニメーションすることによって擬似的にスクロールさせる、一般的(?)に実装されるスムーススクロールと同じような作りになります。&lt;/p&gt;&#10;&lt;pre class="code lang-javascript" data-lang="javascript" data-unlink&gt;&lt;span class="synComment"&gt;// amount : wheelやtouchmoveの量の値&lt;/span&gt;&#10;&lt;span class="synComment"&gt;// ease : 減衰値（例：0.1）&lt;/span&gt;&#10;&lt;span class="synIdentifier"&gt;this&lt;/span&gt;.scroll.value += (amount - &lt;span class="synIdentifier"&gt;this&lt;/span&gt;.scroll.value) * ease;&#10;&#10;gsap.set(&lt;span class="synConstant"&gt;'.l-scroll-container'&lt;/span&gt;, &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; y: &lt;span class="synIdentifier"&gt;this&lt;/span&gt;.scroll.value,&#10;&lt;span class="synIdentifier"&gt;}&lt;/span&gt;);&#10;&lt;/pre&gt;&#10;&lt;p&gt;「&lt;strong&gt;無限スクロール&lt;/strong&gt;」は一番下までスクロールするとmod関数によりスクロール値を0に戻す作りになっています。&lt;br /&gt;&#10;カルーセルスライダーのループと同じようなロジックです。&lt;/p&gt;&#10;&lt;pre class="code lang-javascript" data-lang="javascript" data-unlink&gt;&lt;span class="synStatement"&gt;const&lt;/span&gt; mod = (i, j) =&amp;gt; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synStatement"&gt;return&lt;/span&gt; (i % j) &amp;lt; 0 ? (i % j) + 0 + (j &amp;lt; 0 ? -j : j) : (i % j + 0);&#10;&lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&lt;/pre&gt;&#10;&lt;pre class="code lang-javascript" data-lang="javascript" data-unlink&gt;&lt;span class="synComment"&gt;// page_height : ページ全体の高さ&lt;/span&gt;&#10;&lt;span class="synComment"&gt;// clone_height : 無限にスクロールに見せるため1番上のsectionを複製して1番下に置く、その高さ&lt;/span&gt;&#10;&lt;span class="synIdentifier"&gt;this&lt;/span&gt;.scroll.amount += (amount - &lt;span class="synIdentifier"&gt;this&lt;/span&gt;.scroll.amount) * ease;&#10;&lt;span class="synIdentifier"&gt;this&lt;/span&gt;.scroll.value = mod(&lt;span class="synIdentifier"&gt;this&lt;/span&gt;.scroll.amount, page_height - clone_height);&#10;&lt;/pre&gt;&#10;&lt;p&gt;まずこちらのスクロールの仕組みのモックを事前に作成し、技術的課題をチェックしたり、想定している演出の可否チェックなどを行いました。&lt;/p&gt;&#10;&lt;h3&gt;スクロールと連動した演出の仕組み&lt;/h3&gt;&#10;&lt;p&gt;各sectionごとにスクロール値を正規化してコンポーネントにpropsで渡しています。&lt;/p&gt;&#10;&lt;pre class="code lang-javascript" data-lang="javascript" data-unlink&gt;&amp;lt;template&amp;gt;&#10; &amp;lt;Child&#10; :move=&lt;span class="synConstant"&gt;&amp;quot;move&amp;quot;&lt;/span&gt; /&amp;gt;&#10;&amp;lt;/template&amp;gt;&#10;&#10;&amp;lt;script&amp;gt;&#10;&lt;span class="synStatement"&gt;export&lt;/span&gt; &lt;span class="synStatement"&gt;default&lt;/span&gt; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; props: &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; move: 0.0,&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;,&#10; methods: &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; update()&lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; requestAnimationFrame(&lt;span class="synIdentifier"&gt;this&lt;/span&gt;.update.bind(&lt;span class="synIdentifier"&gt;this&lt;/span&gt;));&#10;&#10; &lt;span class="synStatement"&gt;const&lt;/span&gt; value = -&lt;span class="synIdentifier"&gt;this&lt;/span&gt;.scroll.value;&#10; &lt;span class="synComment"&gt;// min : sectionに入るタイミングのスクロール値&lt;/span&gt;&#10; &lt;span class="synComment"&gt;// max : sectionから出るタイミングのスクロール値&lt;/span&gt;&#10;&#10; &lt;span class="synStatement"&gt;if&lt;/span&gt; (min &amp;lt;= value &amp;amp;&amp;amp; max &amp;gt;= value)&lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10;&#10; &lt;span class="synComment"&gt;// sectionに入ったタイミングで発火！&lt;/span&gt;&#10; &#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt; &lt;span class="synStatement"&gt;else&lt;/span&gt; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10;&#10; &lt;span class="synComment"&gt;// sectionに入っている間毎フレーム発火！&lt;/span&gt;&#10;&#10; &lt;span class="synComment"&gt;// min、max、スクロール値を元に0~1に正規化した値を渡す&lt;/span&gt;&#10; &lt;span class="synStatement"&gt;const&lt;/span&gt; height = max - min;&#10; &lt;span class="synStatement"&gt;const&lt;/span&gt; scroll = (value - min);&#10; &lt;span class="synStatement"&gt;const&lt;/span&gt; move = scroll / height;&#10; &lt;span class="synIdentifier"&gt;this&lt;/span&gt;.move = move;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&amp;lt;/script&amp;gt;&#10;&lt;/pre&gt;&#10;&lt;p&gt;実際はフラグ処理や分岐等でもっとコード量は多いので、正規化のロジックのみ取り出しています。&lt;br /&gt;&#10;このように値をpropsに渡してあげることで、演出を各コンポーネント内で完結でき、作業の分担をしやすくしました。&lt;/p&gt;&#10;&lt;p&gt;「&lt;strong&gt;ここの仕組みがもっと知りたい！&lt;/strong&gt;」などあれば、コミュニケーションとっていただけるとありがたいです！&lt;/p&gt;&#10;&lt;h2&gt;困ったけど解決したこと&lt;/h2&gt;&#10;&lt;h3&gt;watchメソッドの最適化&lt;/h3&gt;&#10;&lt;p&gt;モックを作り、本番環境を整え、一度全ての要素を入れ込んだ状態で最初にぶつかった壁はパフォーマンスでした。&lt;br /&gt;&#10;ボトルネックを探した結果、上記で子コンポーネントに渡していたmoveをwatchしていたメソッドが怪しいと感じました。&lt;br /&gt;&#10;フラグを設置して、明示的にON/OFFするようにしたところ、負荷が劇的に改善されました。&lt;br /&gt;&#10;おそらくmoveが書き換わらなくても、watchメソッドが裏側で動作していたと思われます。&lt;/p&gt;&#10;&lt;pre class="code lang-javascript" data-lang="javascript" data-unlink&gt;&lt;span class="synComment"&gt;// BEFORE&lt;/span&gt;&#10;&lt;span class="synStatement"&gt;export&lt;/span&gt; &lt;span class="synStatement"&gt;default&lt;/span&gt; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; props: &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; move: &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; type: &lt;span class="synType"&gt;Number&lt;/span&gt;,&#10; &lt;span class="synStatement"&gt;default&lt;/span&gt;: 0.0&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;,&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;,&#10; watch: &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; move: &lt;span class="synIdentifier"&gt;function&lt;/span&gt; () &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synComment"&gt;// 演出実装&lt;/span&gt;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&lt;/pre&gt;&#10;&lt;pre class="code lang-javascript" data-lang="javascript" data-unlink&gt;&lt;span class="synComment"&gt;// AFTER&lt;/span&gt;&#10;&lt;span class="synStatement"&gt;export&lt;/span&gt; &lt;span class="synStatement"&gt;default&lt;/span&gt; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; props: &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; move: &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; type: &lt;span class="synType"&gt;Number&lt;/span&gt;,&#10; &lt;span class="synStatement"&gt;default&lt;/span&gt;: 0.0&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;,&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;,&#10; data() &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synStatement"&gt;return&lt;/span&gt; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; isUpdate: &lt;span class="synConstant"&gt;true&lt;/span&gt;,&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;,&#10; methods: &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; init() &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synIdentifier"&gt;this&lt;/span&gt;.frameUpdate();&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;,&#10; frameUpdate() &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; requestAnimationFrame(() =&amp;gt; &lt;span class="synIdentifier"&gt;this&lt;/span&gt;.frameUpdate() );&#10; &lt;span class="synStatement"&gt;if&lt;/span&gt; (!&lt;span class="synIdentifier"&gt;this&lt;/span&gt;.isUpdate) &lt;span class="synStatement"&gt;return&lt;/span&gt;;&#10; &lt;span class="synIdentifier"&gt;this&lt;/span&gt;.isUpdate = &lt;span class="synConstant"&gt;false&lt;/span&gt;;&#10; &lt;span class="synComment"&gt;// 演出実装&lt;/span&gt;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;,&#10; watch: &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; move: &lt;span class="synIdentifier"&gt;function&lt;/span&gt; () &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synIdentifier"&gt;this&lt;/span&gt;.isUpdate = &lt;span class="synConstant"&gt;true&lt;/span&gt;;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&lt;/pre&gt;&#10;&lt;p&gt;今回はwatchメソッド内で重い処理を走らせていたので気づきましたが、普段は中々気づきにくいところかもしれません。&lt;/p&gt;&#10;&lt;h2&gt;改善したいこと&lt;/h2&gt;&#10;&lt;h3&gt;nuxt generate後に生成されるhtmlのmetaが書き換わらない&lt;/h3&gt;&#10;&lt;p&gt;/pages/配下で指定したページのmetaの内容が、書き出されたhtmlに反映されず、nuxt.config.jsの内容のままになっていることが発覚しました。&lt;br /&gt;&#10;色々調査しましたが直接的な原因は分からず。&lt;br /&gt;&#10;結局nuxt generate後に書き出されたhtmlをnode.jsで直接書き換えることになりました。&lt;/p&gt;&#10;&lt;pre class="code lang-javascript" data-lang="javascript" data-unlink&gt;&#10;&lt;span class="synStatement"&gt;const&lt;/span&gt; LIST = &lt;span class="synIdentifier"&gt;[&lt;/span&gt;&#10; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synConstant"&gt;'ja'&lt;/span&gt;: &lt;span class="synConstant"&gt;/旧タイトル/g&lt;/span&gt;,&#10; &lt;span class="synConstant"&gt;'en'&lt;/span&gt;: &lt;span class="synConstant"&gt;'新タイトル'&lt;/span&gt;,&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;,&#10; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synConstant"&gt;'ja'&lt;/span&gt;: &lt;span class="synConstant"&gt;/旧ディスクリプション/g&lt;/span&gt;,&#10; &lt;span class="synConstant"&gt;'en'&lt;/span&gt;: &lt;span class="synConstant"&gt;'新ディスクリプション'&lt;/span&gt;,&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;,&#10; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synConstant"&gt;'ja'&lt;/span&gt;: &lt;span class="synConstant"&gt;'lang=&amp;quot;旧言語設定&amp;quot;'&lt;/span&gt;,&#10; &lt;span class="synConstant"&gt;'en'&lt;/span&gt;: &lt;span class="synConstant"&gt;'lang=&amp;quot;新言語設定&amp;quot;'&lt;/span&gt;,&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;,&#10; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synConstant"&gt;'ja'&lt;/span&gt;: &lt;span class="synConstant"&gt;'&amp;lt;meta data-n-head=&amp;quot;1&amp;quot; data-hid=&amp;quot;og:url&amp;quot; property=&amp;quot;og:url&amp;quot; content=&amp;quot;旧シェアURL&amp;quot;&amp;gt;'&lt;/span&gt;,&#10; &lt;span class="synConstant"&gt;'en'&lt;/span&gt;: &lt;span class="synConstant"&gt;'&amp;lt;meta data-n-head=&amp;quot;1&amp;quot; data-hid=&amp;quot;og:url&amp;quot; property=&amp;quot;og:url&amp;quot; content=&amp;quot;新シェアURL&amp;quot;&amp;gt;'&lt;/span&gt;,&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&lt;span class="synIdentifier"&gt;]&lt;/span&gt;;&#10;&#10;&lt;span class="synIdentifier"&gt;function&lt;/span&gt; replace(path, list) &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synStatement"&gt;const&lt;/span&gt; data = fs.readFileSync(path, &lt;span class="synConstant"&gt;'utf8'&lt;/span&gt;);&#10;&#10; &lt;span class="synIdentifier"&gt;let&lt;/span&gt; result = data;&#10; list.forEach(e =&amp;gt; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; result = result.replace(e.ja, e.en);&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;);&#10;&#10; fs.writeFile(path, result, &lt;span class="synConstant"&gt;'utf8'&lt;/span&gt;, &lt;span class="synIdentifier"&gt;function&lt;/span&gt; (err) &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synStatement"&gt;if&lt;/span&gt; (err) &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synStatement"&gt;throw&lt;/span&gt; err;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;);&#10;&lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&#10;replace(&lt;span class="synConstant"&gt;'./dist/index.html'&lt;/span&gt;, LIST);&#10;&lt;/pre&gt;&#10;&lt;p&gt;今でも明確な答えは分からずじまい...&#10;困った...困った...&lt;/p&gt;&#10;&lt;p&gt;...！！！&lt;/p&gt;&#10;&lt;h2&gt;そんな答えの分かるエンジニアをバスキュールは探しています&lt;/h2&gt;&#10;&lt;p&gt;使うフレームワークは自分で決められる！個人の裁量が大きいバスキュールに来てみませんか！？&#10;フロントエンドからバックエンド、肩書きの垣根を超えて活躍したい方はぜひ一度お話ししましょう！&lt;/p&gt;&#10;&lt;p&gt;&lt;iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fbascule.co.jp%2Fjoin-us%2F" title="JOIN US - Bascule Inc." class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://bascule.co.jp/join-us/"&gt;bascule.co.jp&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;</content><link rel="enclosure" href="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20210902/20210902211228.jpg" type="image/jpeg" length="0"/><author><name>bascule-dev</name></author></entry><entry><title>ExoplanetLanderのゲーム開発と、そこに至るまでの話</title><link href="https://blog.bascule.co.jp/entry/2021/06/28/175918"/><id>hatenablog://entry/26006613780940885</id><published>2021-06-28T17:59:18+09:00</published><updated>2021-06-28T08:59:18+09:00</updated><summary type="html">前回のぷとしくん（タケダ）の記事に続き、今回も「ExoplanetLander」の開発について。 前回は機材周りの話が中心だったので、今回は僕（ワダ）が担当したゲーム部分の話を中心に書いてくことにします。 最初に、このコンテンツを作るに至った経緯について簡単に触れておきます。 （以下、文体を変えます） 元々は、人材募集に難航していたので、まず会社のことを知ってもらうためにPRコンテンツを作ろう。というのがことの発端。 で、PRコンテンツを作るにあたって、オフィスの移転を目前に控えていることもあって、いまのオフィスを使って何かがしたいということになった。 そこで オフィスでAR オフィスでデジタ…</summary><content type="html">&lt;p&gt;前回のぷとしくん（タケダ）の記事に続き、今回も「ExoplanetLander」の開発について。&lt;/p&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20210623/20210623013757.jpg" alt="f:id:bascule-dev:20210623013757j:plain" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;p&gt;前回は機材周りの話が中心だったので、今回は僕（ワダ）が担当したゲーム部分の話を中心に書いてくことにします。&lt;/p&gt;&#10;&lt;p&gt;最初に、このコンテンツを作るに至った経緯について簡単に触れておきます。&lt;br /&gt;&#10;（以下、文体を変えます）&lt;/p&gt;&#10;&lt;hr /&gt;&#10;&lt;p&gt;元々は、人材募集に難航していたので、まず会社のことを知ってもらうためにPRコンテンツを作ろう。というのがことの発端。&#10;で、PRコンテンツを作るにあたって、オフィスの移転を目前に控えていることもあって、いまのオフィスを使って何かがしたいということになった。&lt;/p&gt;&#10;&lt;p&gt;そこで&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;オフィスでAR&lt;/li&gt;&#10;&lt;li&gt;オフィスでデジタルツイン&lt;/li&gt;&#10;&lt;li&gt;オフィスにレーザーで照射&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;みたいな話があって、レーザーで何を照射するのかって話でLunarLanderがネタとして上がったんだけど、レーザーが1台しかなくて全然描画できなかったのと、安全面的にレーザー常設はやっぱり厳しいなって話でそれもナシに。&lt;/p&gt;&#10;&lt;p&gt;オフィスでARは、あんまりオフィス内が丸見えなのも困るから水没させようって言ってこんなことをしてた。&#10;&lt;blockquote data-conversation="none" class="twitter-tweet" data-lang="ja"&gt;&lt;p lang="ja" dir="ltr"&gt;&lt;a href="https://twitter.com/hashtag/ExoplanetLander?src=hash&amp;amp;ref_src=twsrc%5Etfw"&gt;#ExoplanetLander&lt;/a&gt; このコンテンツを開発するに至る前に、何をするかっていう段階であがってた別案のテスト。オフィスの中でARで何かしようとしてた。&lt;br&gt;放置された例の筐体がひっそりと佇んでるのが写り込んでる。後にここがビカビカすることになる。 &lt;a href="https://t.co/fRwyXd7XOU"&gt;pic.twitter.com/fRwyXd7XOU&lt;/a&gt;&lt;/p&gt;&amp;mdash; Michiyasu Wada (@seyself) &lt;a href="https://twitter.com/seyself/status/1408476544385699843?ref_src=twsrc%5Etfw"&gt;2021年6月25日&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt; &lt;/p&gt;&#10;&lt;p&gt;前々回の記事にあったTOIOのやつも実はその過程で生まれたもの。&#10;&lt;iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.bascule.co.jp%2Fentry%2Fsocketio2021" title="Socket.ioでデバイスを繋いでアナログメタバースを作る - バスキュール技術ブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://blog.bascule.co.jp/entry/socketio2021"&gt;blog.bascule.co.jp&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;&lt;p&gt;レーザーの話は、最初Webカムでアクセスして自分の姿が他人のオフィスに勝手に写しだされてたらそれだけで面白いんじゃないか。みたいなノリで話が先行し、やってたのがこれ。&#10;&lt;blockquote data-conversation="none" class="twitter-tweet" data-lang="ja"&gt;&lt;p lang="ja" dir="ltr"&gt;&lt;a href="https://twitter.com/hashtag/ExoplanetLander?src=hash&amp;amp;ref_src=twsrc%5Etfw"&gt;#ExoplanetLander&lt;/a&gt; 何の使いみちも無く興味本位で買ったレーザーをなんとかコンテンツに使えないかと、Webカメラ越しに遠隔の映像を壁に照射しようとしたりも。（なぜ脱いでるのか。。。） &lt;a href="https://t.co/QmAoaMJANt"&gt;pic.twitter.com/QmAoaMJANt&lt;/a&gt;&lt;/p&gt;&amp;mdash; Michiyasu Wada (@seyself) &lt;a href="https://twitter.com/seyself/status/1408477863775404034?ref_src=twsrc%5Etfw"&gt;2021年6月25日&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt; &lt;/p&gt;&#10;&lt;p&gt;レーザーは、上記で使っていたのは LaserCube だけど、僕が使ってたのは LightingCraft A600RGB + EtherDream2。いつの間にか EtherDream3 が出てる。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://www.laseros.com/"&gt;LaserCube&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://lighting-craft.jp/SHOP/A600RGB.html"&gt;LightingCraft A600RGB&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://ether-dream.com/"&gt;EtherDream&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;レーザーは取り扱い要注意なので、利用の際はあくまで自己責任で。&#10;&lt;iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fseyself%2FEtherDreamForUnity" title="seyself/EtherDreamForUnity" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://github.com/seyself/EtherDreamForUnity"&gt;github.com&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;&lt;hr /&gt;&#10;&lt;h3&gt;閑話休題&lt;/h3&gt;&#10;&lt;p&gt;そんなこんなで最終的に目についたのがあの筐体で、この中にLunarLanderを入れて、筐体を遠隔でジャックしてゲームプレイできたら楽しそう。&lt;br /&gt;&#10;よその会社のコーポレートサイトの、しかも採用ページで、さらにその会社のオフィスにまで勝手に入り込んでゲームして遊ぶとかなかなか無いよね。&lt;br /&gt;&#10;ってなって今に至る。&lt;/p&gt;&#10;&lt;h3&gt;機体の作成と衝突判定&lt;/h3&gt;&#10;&lt;p&gt;まずは失敗したときの巻き添えシステム&#10;&lt;blockquote data-conversation="none" class="twitter-tweet" data-lang="ja"&gt;&lt;p lang="ja" dir="ltr"&gt;開発話②：難しいという声を多く頂いていますが、最初に作ってみたらほぼこの難易度だったので後はこれを崩さないように周りを固めていきました。でも実は公開前の社内テストで一部理不尽な仕様に非難もあったので少し難易度は下げました。ちなみに一番最初にやったのは誘爆の実装。 &lt;a href="https://twitter.com/hashtag/ExoplanetLander?src=hash&amp;amp;ref_src=twsrc%5Etfw"&gt;#ExoplanetLander&lt;/a&gt; &lt;a href="https://t.co/5w368h1YUy"&gt;pic.twitter.com/5w368h1YUy&lt;/a&gt;&lt;/p&gt;&amp;mdash; Michiyasu Wada (@seyself) &lt;a href="https://twitter.com/seyself/status/1406604713844305922?ref_src=twsrc%5Etfw"&gt;2021年6月20日&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt; &lt;/p&gt;&#10;&lt;p&gt;衝突判定はいたってシンプルで、機体の線は全部 Sprite の矩形で構成されていて、その矩形それぞれに BoxCollider2D が設定されてる。&lt;/p&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20210628/20210628173643.png" alt="f:id:bascule-dev:20210628173643p:plain" width="341" height="297" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20210628/20210628173701.png" alt="f:id:bascule-dev:20210628173701p:plain" width="458" height="793" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;p&gt;機体の動きは、いったんキーボードの入力をそのまま受け付けるようにしておいて&lt;br /&gt;&#10;あとでキー入力をそのままWebSocketからの入力に置き換えた。&lt;/p&gt;&#10;&lt;h3&gt;ステージの作成&lt;/h3&gt;&#10;&lt;p&gt;最初はぷとしくんが描いたXDのラフが元 &lt;br /&gt;&#10;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20210628/20210628174204.png" alt="f:id:bascule-dev:20210628174204p:plain" width="1200" height="506" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&#10;イスカンダルもナメックもめっちゃ簡単。&lt;br /&gt;&#10;（今見るとぷとしくんの良心がとてもまぶしい・・・）&lt;/p&gt;&#10;&lt;p&gt;それを僕がイラレに持っていって、パスを調整したのがこれ&#10;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20210628/20210628174222.png" alt="f:id:bascule-dev:20210628174222p:plain" width="1200" height="472" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&#10;悪意しか感じられない。。。&lt;/p&gt;&#10;&lt;p&gt;このイラレで編集したパスをSVGで保存して、それをUnityで読み込んで描画。&#10;&lt;iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fseyself%2FSVGParserForUnity" title="seyself/SVGParserForUnity" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://github.com/seyself/SVGParserForUnity"&gt;github.com&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;&lt;p&gt;これを使って、こんな感じで矩形をパスに沿って繋いでいってる。&lt;br /&gt;&#10;この地形の線も、機体同様に BoxCollider 付きの Sprite をひたすら繋いだものになってるので、機体が当たれば同様に OnTriggerEnter で衝突判定が受け取れる。&#10;&lt;script src="https://gist.github.com/seyself/dbd9901f704b584feb0c42bbff7de767.js"&gt; &lt;/script&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://gist.github.com/seyself/dbd9901f704b584feb0c42bbff7de767"&gt;gist.github.com&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;&lt;h3&gt;遠隔での操作性&lt;/h3&gt;&#10;&lt;p&gt;遠隔での操作性については割と早い段階からテストはしていて確証はあった。&lt;br /&gt;&#10;前回の記事にも最初に少し触れていたけど、ぷとしくんは東京、僕は奈良なのでテストする時はZoom越しにテストしていて、特に操作に支障はなかったのであとは何で配信するか。&lt;br /&gt;&#10;で、今回は配信の解像度や料金を比較してAgora.ioを使った。&lt;/p&gt;&#10;&lt;p&gt;ここではAgora.ioについての詳細は省くけど、tokenの発行等で参考にさせてもらったのはこちら&lt;/p&gt;&#10;&lt;h5&gt;＜参考リンク＞&lt;/h5&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://qiita.com/v-cube/items/c43dd8b0241f03fedc1d"&gt;agora.io &amp;#x306E; Security Key &amp;#x306B;&amp;#x3064;&amp;#x3044;&amp;#x3066; - Qiita&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://zenn.dev/arahabica/articles/0f54f2cdb1a29d"&gt;Clubhouse&amp;#x3082;&amp;#x4F7F;&amp;#x3063;&amp;#x3066;&amp;#x3044;&amp;#x308B;&amp;#x3089;&amp;#x3057;&amp;#x3044;Agora&amp;#x3092;&amp;#x4F7F;&amp;#x3063;&amp;#x3066;&amp;#x7C21;&amp;#x5358;&amp;#x306B;&amp;#x30D3;&amp;#x30C7;&amp;#x30AA;&amp;#x901A;&amp;#x8A71;&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Agora.io はUnity用のSDKも配布してるけど、今回は配信もWeb版を使用している。&lt;/p&gt;&#10;&lt;p&gt;開発のコミュニケーションもSlackとZoomで、ゲームの基本操作までの実装は3日くらいで、あとはこれを触りながら他の仕様とか絵を決めていったので、開発期間のほとんどが複数人でこれをわいわいプレイしながらあーだこーだ言ってた気がする。&lt;/p&gt;&#10;&lt;hr /&gt;&#10;&lt;h3&gt;みんなでやったら楽しい&lt;/h3&gt;&#10;&lt;p&gt;テストプレイ時はこんな感じで社内のみんなでわいわいやってました。&#10;&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/Kh1x2axbH0M?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://youtu.be/Kh1x2axbH0M"&gt;youtu.be&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;&lt;p&gt;誰かと一緒にプレイするとまた楽しみ方が変わってくるので&lt;br /&gt;&#10;仕事終わりにお酒を飲みながらでも、Zoomでご家族、ご友人と一緒に楽しんでいただければ幸いです。&lt;br /&gt;&#10;そして着陸に成功したら、そのまま勢いで応募しちゃってください。&lt;br /&gt;&#10;未知の領域に挑んでやろうという気概を持った方の応募お待ちしています！&#10;&lt;iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fbascule.co.jp%2Fjoin-us%2F" title="Exoplanet Lander | 求む、ヒーロー！ - Bascule Inc." class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://bascule.co.jp/join-us/"&gt;bascule.co.jp&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;</content><link rel="enclosure" href="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20210623/20210623013757.jpg" type="image/jpeg" length="0"/><author><name>bascule-dev</name></author></entry><entry><title>『Exoplanet Lander』 公開しました。</title><link href="https://blog.bascule.co.jp/entry/2021/06/23/001339"/><id>hatenablog://entry/26006613778804081</id><published>2021-06-23T00:13:39+09:00</published><updated>2021-06-22T16:51:09+09:00</updated><summary type="html">エンジニアのタケダ(@neko_300YEN)です。 先週『Exoplanet Lander』というWebコンテンツをリリースしました。 弊社オフィスにあるアーケードゲーム筐体のディスプレイに映し出された着陸機を操作して、目的地に着陸させるというゲームです。 ゲームが動いているのは実機なんですが、それを操作するのはネット越しの外から繋がっている人で、ZOOM的なもので現場の映像を見ながら繊細な操作を必要とするゲームができるのか？ 果たして面白いのか？ そんなことを実験的にやってみたというのがこのコンテンツです。 使った機材とか実装について書いていきます。 現代に蘇る『Lunar Lander』…</summary><content type="html">&lt;p&gt;エンジニアのタケダ(&lt;a href="https://twitter.com/neko_300YEN"&gt;@neko_300YEN&lt;/a&gt;)です。&lt;/p&gt;&#10;&lt;p&gt;先週『Exoplanet Lander』というWebコンテンツをリリースしました。&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://youtu.be/_hIw7uLtGTw" target="_blank"&gt;&#10;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20210623/20210623013757.jpg" alt="20210623013757" title="20210623013757" width="1200" height="630" class="foto" style=""&gt;&#10;&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;弊社オフィスにあるアーケードゲーム筐体のディスプレイに映し出された着陸機を操作して、目的地に着陸させるというゲームです。&lt;/p&gt;&#10;&lt;p&gt;ゲームが動いているのは実機なんですが、それを操作するのはネット越しの外から繋がっている人で、ZOOM的なもので現場の映像を見ながら&lt;b&gt;繊細な操作を必要とするゲームができるのか？&#10;果たして面白いのか？&lt;/b&gt;&lt;/p&gt;&#10;&lt;p&gt;そんなことを実験的にやってみたというのがこのコンテンツです。&lt;/p&gt;&#10;&lt;p&gt;使った機材とか実装について書いていきます。&lt;/p&gt;&#10;&lt;h1&gt;現代に蘇る『Lunar Lander』&lt;/h1&gt;&#10;&lt;p&gt;『Exoplanet Lander』はオリジナルというわけではなく、ゲーム自体はそもそも1979年にリリースされた『Lunar Lander』というアーケードゲームがベースになっています。&lt;/p&gt;&#10;&lt;p&gt;&lt;b&gt;LunarLanderの良いところを踏襲しつつ、現代らしくアップデートしてみよう！！&#10;&lt;/b&gt;&lt;/p&gt;&#10;&lt;p&gt;みたいな信条を持って下記の要素を付け足してみました。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;マルチユーザー対応&lt;/li&gt;&#10;&lt;li&gt;リモート参加&lt;/li&gt;&#10;&lt;li&gt;リアルな場で起こるインタラクティブな演出&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h1&gt;構成&lt;/h1&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20210705/20210705162535.png" width="1200" height="804" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&#10;今回メインで動いていたのは自分含めたエンジニア2人。&lt;/p&gt;&#10;&lt;p&gt;自分は配信まわり、Web実装全般、照明とか筐体まわり。&lt;/p&gt;&#10;&lt;p&gt;奈良に住んでいるもうひとりのエンジニアがサーバーの実装と筐体内のゲームをUnityで作りました。&lt;/p&gt;&#10;&lt;h1&gt;アーケードゲーム筐体に映像を映す&lt;/h1&gt;&#10;&lt;p&gt;筐体はこちら&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://www.amazon.co.jp/Arcade1Up-%E3%82%B9%E3%83%9A%E3%83%BC%E3%82%B9%E3%82%A4%E3%83%B3%E3%83%99%E3%83%BC%E3%83%80%E3%83%BC-SPACE-INVADERS-%E6%97%A5%E6%9C%AC%E4%BB%95%E6%A7%98%E9%9B%BB%E6%BA%90%E7%89%88%EF%BC%89/dp/B07KPHF3C4/ref=sr_1_4?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&amp;dchild=1&amp;keywords=arcade1up&amp;qid=1624358543&amp;sr=8-4"&gt;https://www.amazon.co.jp/Arcade1Up-%E3%82%B9%E3%83%9A%E3%83%BC%E3%82%B9%E3%82%A4%E3%83%B3%E3%83%99%E3%83%BC%E3%83%80%E3%83%BC-SPACE-INVADERS-%E6%97%A5%E6%9C%AC%E4%BB%95%E6%A7%98%E9%9B%BB%E6%BA%90%E7%89%88%EF%BC%89/dp/B07KPHF3C4/ref=sr_1_4?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&amp;dchild=1&amp;keywords=arcade1up&amp;qid=1624358543&amp;sr=8-4&lt;/a&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://www.amazon.co.jp/Arcade1Up-%E3%82%B9%E3%83%9A%E3%83%BC%E3%82%B9%E3%82%A4%E3%83%B3%E3%83%99%E3%83%BC%E3%83%80%E3%83%BC-SPACE-INVADERS-%E6%97%A5%E6%9C%AC%E4%BB%95%E6%A7%98%E9%9B%BB%E6%BA%90%E7%89%88%EF%BC%89/dp/B07KPHF3C4/ref=sr_1_4?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&amp;dchild=1&amp;keywords=arcade1up&amp;qid=1624358543&amp;sr=8-4"&gt;www.amazon.co.jp&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;&lt;p&gt;どうしてこれが3つもオフィスにあるのかは謎ですが、たぶん代表の私物だから壊れても大丈夫◎&lt;/p&gt;&#10;&lt;p&gt;キルアがバラシ屋ジョネスの心臓をくり抜いたかの如く、中から基盤をサッと抜き取ります。ｶｴｼﾃ…。&lt;/p&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20210622/20210622200241.jpg" width="756" height="858" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;p&gt;あとは下記のインバーターを取り付ければ完成！&lt;/p&gt;&#10;&lt;p&gt;HDMI入力できる17インチの液晶ディスプレイとなります。&#10;これを3つぶん。&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://store.shopping.yahoo.co.jp/truejp/a-B08C37NB29-20210521.html"&gt;https://store.shopping.yahoo.co.jp/truejp/a-B08C37NB29-20210521.html&lt;/a&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://store.shopping.yahoo.co.jp/truejp/a-B08C37NB29-20210521.html"&gt;store.shopping.yahoo.co.jp&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;&lt;h1&gt;中継カメラ&lt;/h1&gt;&#10;&lt;p&gt;今回カメラふたつ使ってます。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;ゲーム画面映像 : SIGMA fpをTytpe-CでPCと接続。UVCでAgoraに映像配信。&lt;/li&gt;&#10;&lt;li&gt;俯瞰映像 : Canon5Dの画面をHDMIで出力。キャプボで取り込んでOBSからYouTubeに配信。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Agoraの方はHDで配信しています。&#10;&lt;b&gt;レイテンシー200~300ms&lt;/b&gt;というところでしょうか。&lt;/p&gt;&#10;&lt;p&gt;音質はだいぶ悪いです。デフォルトの状態だと恐らく人間の声の周波数ぐらいを境にローをごっそり削られます。&#10;ノイズ抑制の機能でしょうか？&#10;AGCやANSもOFFの状態にしてもあまり変わらず。&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://docs.agora.io/en/Video/API%20Reference/web/interfaces/agorartc.stream.html#setaudioprofile"&gt;Agora Web SDK API Reference - Video Call - Agora Documentation&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;"high_quality"で配信しているのですが全然音質良くない。&#10;何か実装が間違っているのかも。。。&lt;/p&gt;&#10;&lt;p&gt;また、約1ヶ月近くずっと中継し続けないといけないので、機材の痛みも気になるところです。&lt;/p&gt;&#10;&lt;p&gt;なので、カメラも代表の私物を利用させてもらっています◎&lt;/p&gt;&#10;&lt;p&gt;あとSIGMA fpはUVCモードで使っている時に電源が切れるとフォーカスがデフォルトの状態に戻ってしまいます。&#10;死ぬまで電源を落とさないように下記の外部電源を購入しました。&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://www.yodobashi.com/product-detail/100000001005177981/"&gt;https://www.yodobashi.com/product-detail/100000001005177981/&lt;/a&gt;&lt;/p&gt;&#10;&lt;h1&gt;照明&lt;/h1&gt;&#10;&lt;h2&gt;制御基盤 + 電源装置&lt;/h2&gt;&#10;&lt;p&gt;別案件で作ったやつの流用ですが、制御基板はこんな感じ。&#10;これをゲーム筐体の中に入れています。&lt;/p&gt;&#10;&lt;p&gt;5Vのスイッチング電源と分電回路。&#10;ArtNetでデータを受けてDMX信号とLEDを光らせるための基盤「Advatek社PixLitex16 mk2 」と、ArduinoとW5100モジュールが一緒になったMCUボード「KEYESTUDIO KS0304」を使っています。&#10;何故、制御基板を2つ使うことになったかというのは後述します。&lt;/p&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20210622/20210622211143.jpg" width="717" height="791" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;h2&gt;Advatek社 PixLite16mk2&lt;/h2&gt;&#10;&lt;p&gt;コレ便利。ArtNetで送った信号をLED16ポート+DMXout4ポートで出力できます。&#10;設定アプリも良くできているので、今回のようなサッサと光らせたい時に最適です。&lt;/p&gt;&#10;&lt;p&gt;5VフルカラーLEDで一般的なWS2811~WS2815とかSK9822とかいろんなチップに対応しています。&lt;/p&gt;&#10;&lt;p&gt;一点ダメな点としては、5ポート以上使うとだんだん出力電圧が下がってLEDがジカジカし始めます。&#10;5Vだから？？&lt;/p&gt;&#10;&lt;p&gt;16ポートフルで使える方法あったら教えて下さい。&lt;/p&gt;&#10;&lt;h2&gt;KEYESTUDIO KS0304&lt;/h2&gt;&#10;&lt;p&gt;コレ便利。売り切れだけど。&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://www.amazon.co.jp/keyestudio-KEYESTUDIO-Arduino%E7%94%A8Ethernet-W5500%E3%82%A4%E3%83%BC%E3%82%B5%E3%83%8D%E3%83%83%E3%83%88%E9%96%8B%E7%99%BA%E3%83%9C%E3%83%BC%E3%83%89%EF%BC%88Poe%E3%81%AA%E3%81%97%EF%BC%89/dp/B079G4WDGW?ref_=ast_sto_dp"&gt;https://www.amazon.co.jp/keyestudio-KEYESTUDIO-Arduino%E7%94%A8Ethernet-W5500%E3%82%A4%E3%83%BC%E3%82%B5%E3%83%8D%E3%83%83%E3%83%88%E9%96%8B%E7%99%BA%E3%83%9C%E3%83%BC%E3%83%89%EF%BC%88Poe%E3%81%AA%E3%81%97%EF%BC%89/dp/B079G4WDGW?ref_=ast_sto_dp&lt;/a&gt;&lt;/p&gt;&#10;&lt;h2&gt;LEDについて&lt;/h2&gt;&#10;&lt;p&gt;こちらも別案件で使った機材の余り物のSK6812のLEDテープ。&#10;RGBWの4chです。&lt;/p&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20210622/20210622212253.jpg" width="756" height="646" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;p&gt;粒の中に黄色い部分があってそこがWhite。これがものすごく明るくなります。&lt;/p&gt;&#10;&lt;p&gt;なので、例えば赤色に光らせたいと思って、R=100%、G=0%、B=0%、W=33%とか、あるいはガンマ補正した値を入れてしまうと、白にうっすら赤みがあるかなぁ・・・くらいのほぼ白になります。&lt;/p&gt;&#10;&lt;p&gt;今回の様にゲーセンっぽい原色強めの雰囲気を作ろうとしても普通にRGBの各値からWを計算してしまうといい感じになりません。&#10;ゲームの中で機体がクラッシュした時などにフラッシュ的な演出も入れたかったので、RBG=100%に近づいた時にだけW=100%になるようなイレギュラーな制御をするようにしました。&#10;ソフト側で処理しても良かったのですが、今回はArtNetは3chのまま受けて、Arduinoの中でWを足してLEDに信号を流すような形にしました。&lt;/p&gt;&#10;&lt;p&gt;PixLite16で完結しなかったのはそのためです。&lt;/p&gt;&#10;&lt;p&gt;実際のコードはこちら&lt;/p&gt;&#10;&lt;p&gt;&lt;iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2FPutoshi%2FArduinoENC_Artnet-WS2811%2Fblob%2FPutoshi-patch-1%2FArduinoENC_Artnet-WS2811.ino" title="ArduinoENC_Artnet-WS2811/ArduinoENC_Artnet-WS2811.ino at Putoshi-patch-1 · Putoshi/ArduinoENC_Artnet-WS2811" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://github.com/Putoshi/ArduinoENC_Artnet-WS2811/blob/Putoshi-patch-1/ArduinoENC_Artnet-WS2811.ino"&gt;github.com&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;&lt;h2&gt;UV Lightについて&lt;/h2&gt;&#10;&lt;p&gt;筐体の装飾を際立たせるために、DMX制御できるUVライトを2灯つけています。&lt;/p&gt;&#10;&lt;p&gt;&lt;iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.gantom.com%2Flighting-fixtures%2Fgantom-7%2F" title="Gantom 7 - Gantom" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://www.gantom.com/lighting-fixtures/gantom-7/"&gt;www.gantom.com&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;&lt;p&gt;12V動作でchogoriという防水コネクタがついています。&lt;/p&gt;&#10;&lt;p&gt;DMXチャンネルについては以下の通り。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;1ch - Master Dimmer&lt;/li&gt;&#10;&lt;li&gt;2ch - Red&lt;/li&gt;&#10;&lt;li&gt;3ch - Green&lt;/li&gt;&#10;&lt;li&gt;4ch - Blue&lt;/li&gt;&#10;&lt;li&gt;5ch - Amber&lt;/li&gt;&#10;&lt;li&gt;6ch - Cool White&lt;/li&gt;&#10;&lt;li&gt;7ch - Lime&lt;/li&gt;&#10;&lt;li&gt;8ch - UV&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20210622/20210622224625.jpg" width="756" height="1008" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;p&gt;ピンアサインはこちら。[自分用メモ]&lt;/p&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20210622/20210622225001.jpg" width="756" height="694" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;h1&gt;照明制御PC&lt;/h1&gt;&#10;&lt;p&gt;ユーザー参加時、機体のブースト時、クラッシュ時、ランディング成功時など、ユーザーのインタラクションに合わせて、LED + 警告灯 + UV Lightを良い感じに光らせます。&lt;/p&gt;&#10;&lt;p&gt;&lt;blockquote data-conversation="none" class="twitter-tweet" data-lang="ja"&gt;&lt;p lang="ja" dir="ltr"&gt;裏側はこんな感じだよ&lt;a href="https://twitter.com/hashtag/ExoplanetLander?src=hash&amp;amp;ref_src=twsrc%5Etfw"&gt;#ExoplanetLander&lt;/a&gt; &lt;a href="https://t.co/92vBpOrH1m"&gt;pic.twitter.com/92vBpOrH1m&lt;/a&gt;&lt;/p&gt;&amp;mdash; 𝕟𝕖𝕜𝕠 𝟛𝟘𝟘𝕐𝔼ℕ (@neko_300YEN) &lt;a href="https://twitter.com/neko_300YEN/status/1405954023329435651?ref_src=twsrc%5Etfw"&gt;2021年6月18日&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt; &lt;/p&gt;&#10;&lt;p&gt;Unityから送出されたOSCをMaxで受けて、それをトリガーにVezerのタイムラインを再生し、アニメーションをLED制御アプリにOSCで送ってArtNetを送出しています。&#10;今回は大雑把にしかアニメーションしてないし、Vezerの方から直でArtNetを送っても良かったかも。&lt;/p&gt;&#10;&lt;p&gt;WinにもVezer的な細かいOSCシーケンス組める使い勝手の良いソフトはないでしょうか。。。&lt;/p&gt;&#10;&lt;p&gt;誰か知ってたら教えてほしいです。&lt;/p&gt;&#10;&lt;p&gt;&lt;iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fimimot.com%2Fvezer%2F" title="Vezér" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://imimot.com/vezer/"&gt;imimot.com&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;&lt;h1&gt;求む、ヒーロー！！&lt;/h1&gt;&#10;&lt;p&gt;弊社ではただいまエンジニアやデザイナーを強く募集しております！！&lt;/p&gt;&#10;&lt;p&gt;ゲームとか宇宙が好きな方、工作が好きな方、企画から考えて一気通貫で制作したい方。&lt;/p&gt;&#10;&lt;p&gt;ポディション問わず様々な方と出会えることを望んでおります。&lt;/p&gt;&#10;&lt;p&gt;ゲームをクリアすると応募フォーム出てくるので、是非そこから応募してください！！よろしくお願いいたします。&lt;/p&gt;&#10;&lt;p&gt;↓↓ チャレンジしてみてね ↓↓&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://bascule.co.jp/join-us/" target="_blank"&gt;&#10;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20210623/20210623013757.jpg" alt="20210623013757" title="20210623013757" width="1200" height="630" class="foto" style=""&gt;&#10;&lt;/a&gt;&lt;/p&gt;&#10;</content><category term="AWS" label="AWS"/><category term="Agora.io" label="Agora.io"/><category term="Arduino" label="Arduino"/><category term="ArtNet" label="ArtNet"/><category term="Max" label="Max"/><category term="DMX" label="DMX"/><category term="OBS" label="OBS"/><category term="Node.js" label="Node.js"/><category term="Socket.IO" label="Socket.IO"/><category term="TouchDesigner" label="TouchDesigner"/><category term="Unity" label="Unity"/><category term="WebSocket" label="WebSocket"/><category term="ゲーム" label="ゲーム"/><category term="電子工作" label="電子工作"/><link rel="enclosure" href="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20210623/20210623013757.jpg" type="image/jpeg" length="0"/><author><name>bascule-dev</name></author></entry><entry><title>Socket.ioでデバイスを繋いでアナログメタバースを作る</title><link href="https://blog.bascule.co.jp/entry/socketio2021"/><id>hatenablog://entry/26006613774068010</id><published>2021-06-10T11:13:14+09:00</published><updated>2021-06-10T02:13:14+09:00</updated><summary type="html">エンジニアの桟(@katapad)です。 PC・スマホだけでなく、さまざまなデバイスとやりとりするにはSocket.ioやOSCのようなリアルタイムメッセージ通信が必要になる。いつもSocket.ioの書き方を忘れるので、2021年におけるSocket.ioの実装例をここに残しておく。 小さなサンプルを作るだけならつまらないので、なんらかの体験のプロトレベルのものを作ることにした。 TOIOをリモートから操作できるアバターにし、会社でマルバツクイズをして、不正解なら落とし穴が発動する。というアナログメタバースを会社ブログ用に作ってました。映像では謎会社バスキュールにまつわるクイズをやってます。…</summary><content type="html">&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20210609/20210609212821.png" alt="f:id:bascule-dev:20210609212821p:plain" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;p&gt;エンジニアの桟(&lt;a href="https://twitter.com/katapad"&gt;@katapad&lt;/a&gt;)です。&lt;/p&gt;&#10;&lt;p&gt;PC・スマホだけでなく、さまざまなデバイスとやりとりするには&lt;a href="https://socket.io/"&gt;Socket.io&lt;/a&gt;やOSCのようなリアルタイムメッセージ通信が必要になる。いつもSocket.ioの書き方を忘れるので、2021年におけるSocket.ioの実装例をここに残しておく。&lt;/p&gt;&#10;&lt;p&gt;小さなサンプルを作るだけならつまらないので、なんらかの体験のプロトレベルのものを作ることにした。&lt;/p&gt;&#10;&lt;p&gt;&lt;blockquote data-conversation="none" class="twitter-tweet" data-lang="ja"&gt;&lt;p lang="ja" dir="ltr"&gt;TOIOをリモートから操作できるアバターにし、会社でマルバツクイズをして、不正解なら落とし穴が発動する。というアナログメタバースを会社ブログ用に作ってました。&lt;br&gt;映像では謎会社バスキュールにまつわるクイズをやってます。&lt;br&gt;ブログのリンクは後ほどぶらさげます&lt;a href="https://twitter.com/hashtag/madewithunity?src=hash&amp;amp;ref_src=twsrc%5Etfw"&gt;#madewithunity&lt;/a&gt; &lt;a href="https://twitter.com/hashtag/toio?src=hash&amp;amp;ref_src=twsrc%5Etfw"&gt;#toio&lt;/a&gt; &lt;a href="https://twitter.com/hashtag/gamedev?src=hash&amp;amp;ref_src=twsrc%5Etfw"&gt;#gamedev&lt;/a&gt; &lt;a href="https://t.co/wCB91Hv9Dg"&gt;pic.twitter.com/wCB91Hv9Dg&lt;/a&gt;&lt;/p&gt;&amp;mdash; 水たまりの水を飲んだことがある桟よしお (@katapad) &lt;a href="https://twitter.com/katapad/status/1402810118685659137?ref_src=twsrc%5Etfw"&gt;2021年6月10日&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt; &lt;/p&gt;&#10;&lt;h2&gt;謎の会社であるバスキュールをアナログかつバーチャル訪問するサンプルを作る。落とし穴付きで。&lt;/h2&gt;&#10;&lt;p&gt;バスキュールが謎会社すぎるとの声をたくさん聞くようになったので、気軽にバーチャル会社訪問でもしてくれたらなあという動機で作ってみる。&lt;/p&gt;&#10;&lt;h3&gt;TOIOでバスキュール社内のリアル空間を動き回ってもらう&lt;/h3&gt;&#10;&lt;p&gt;コロナ禍で注目されたどうぶつの森やフォートナイトといったメタバース。それをアナログに置き換えるとどうなるのか？というテーマを掲げ、ユーザーがリモートでアバター(&lt;a href="https://toio.io/"&gt;TOIO&lt;/a&gt;)を動かせるようにし、バスキュール社内に設置したリアル空間を動き回ってもらう。&lt;/p&gt;&#10;&lt;p&gt;&lt;figure class="figure-image figure-image-fotolife" title="TOIOをアバターに"&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20210609/20210609213100.jpg" alt="f:id:bascule-dev:20210609213100j:plain" width="900" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;figcaption&gt;TOIOをアバターに&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&#10;&lt;p&gt;ブラウザでリモートからTOIOを操作して社内の空間を歩き回ってもらう&lt;/p&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20210609/20210609213343.gif" alt="f:id:bascule-dev:20210609213343g:plain" width="1165" height="400" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;h3&gt;○✗クイズの落とし穴をObnizで作る&lt;/h3&gt;&#10;&lt;p&gt;会社を知るには○✗クイズというのが鉄板(？)である。さらに鉄板を重ねるならば、不正解時には床を落とすことも必要だろう。ここではObnizでサーボモーターを制御し、TOIOを落とし穴にハメるギミックを作る。&lt;/p&gt;&#10;&lt;p&gt;このようにして、バスキュール社内をウロウロして社内の空気に触れながら、クイズで理解を深めてもらい、床から転落してもらうといった、誰も見たことのない体験をしてもらうことにした。&lt;/p&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20210609/20210609215929.gif" alt="f:id:bascule-dev:20210609215929g:plain" width="533" height="300" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;h2&gt;構成&lt;/h2&gt;&#10;&lt;p&gt;&lt;figure class="figure-image figure-image-fotolife" title="構成"&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20210609/20210609212302.png" alt="f:id:bascule-dev:20210609212302p:plain" width="1200" height="992" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/figure&gt;&lt;/p&gt;&#10;&lt;p&gt;ブラウザの画面も載せる(コントローラー、落とし穴とか)&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Node.js&#10;&lt;ul&gt;&#10;&lt;li&gt;Socket.ioサーバーをたてる&lt;/li&gt;&#10;&lt;li&gt;toio.jsでTOIOを制御する&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;ブラウザ&#10;&lt;ul&gt;&#10;&lt;li&gt;TOIOを操作する&lt;/li&gt;&#10;&lt;li&gt;落とし穴を操作する&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Unity&#10;&lt;ul&gt;&#10;&lt;li&gt;TOIOの位置情報を取得&lt;/li&gt;&#10;&lt;li&gt;AR的に情報をオーバーレイする&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;デバイス&#10;&lt;ul&gt;&#10;&lt;li&gt;Obnizでサーボを制御し、床を落とす&lt;/li&gt;&#10;&lt;li&gt;ObnizはSocket.ioを介さずブラウザから直接操作することもできるが、最大接続数の制限があるのでサーバーで一括管理した。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;といった連携をSocket.ioでやっている。&lt;/p&gt;&#10;&lt;h3&gt;使ったもの&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="http://socket.io"&gt;Socket.io&lt;/a&gt; v4&#10;&lt;ul&gt;&#10;&lt;li&gt;Node.js&lt;/li&gt;&#10;&lt;li&gt;JavaScript (Browser)&lt;/li&gt;&#10;&lt;li&gt;Unity (&lt;a href="https://assetstore.unity.com/packages/tools/network/best-http-2-155981?locale=ja-JP"&gt;Best HTTP/2&lt;/a&gt;)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;なお、この試作には入っていないが、&lt;a href="https://python-socketio.readthedocs.io/en/latest/index.html"&gt;PythonでもSocket.io v4系が使えるライブラリ&lt;/a&gt;がある。&lt;/p&gt;&#10;&lt;h2&gt;そもそもSocket.ioで何ができるの？&lt;/h2&gt;&#10;&lt;p&gt;主にWebSocketを用いてリアルタイムで双方向メッセージングをする技術。チャットなどが最も適当な例に当たる。&lt;/p&gt;&#10;&lt;p&gt;Socket.ioにはWebSocketのみでは実現できない便利機能がたくさんある。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;room, namespace&#10;&lt;ul&gt;&#10;&lt;li&gt;room: 同じ空間にいくつか部屋が分かれているイメージ&#10;&lt;ul&gt;&#10;&lt;li&gt;空間に一斉にメッセージ可&lt;/li&gt;&#10;&lt;li&gt;特定の部屋のみにメッセージ可&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;namespace: 空間がそもそも違う&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;接続が切れても自動で再接続してくれる&lt;/li&gt;&#10;&lt;li&gt;メッセージが届くまでリトライしてくれる&lt;/li&gt;&#10;&lt;li&gt;volatileを使うと、送りすぎて不要になったメッセージを捨ててくれる&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;他、簡単に扱えるメッセージング技術としてはOSCもあるが、ブラウザではUDPが使えないためWebSocketで実装されたOSCのライブラリを使う必要がある。(WebRTCを使うとUDPが使えるが、PeerToPeer接続が面倒)&lt;/p&gt;&#10;&lt;p&gt;最近はブラウザでも気軽にUDPが使えるWebTransportなる技術が作られてる最中なので、NodeやUnityなどで使える日を気長に待とう。&lt;/p&gt;&#10;&lt;h2&gt;サンプルコード&lt;/h2&gt;&#10;&lt;h3&gt;Node.js Server&lt;/h3&gt;&#10;&lt;p&gt;全部書くと長くなるので、かいつまんで書いていく&lt;/p&gt;&#10;&lt;pre class="code lang-javascript" data-lang="javascript" data-unlink&gt;&lt;span class="synComment"&gt;// fastifyでサーバー作成&lt;/span&gt;&#10;&lt;span class="synStatement"&gt;const&lt;/span&gt; fastify = require(&lt;span class="synConstant"&gt;'fastify'&lt;/span&gt;)(&lt;span class="synIdentifier"&gt;{&lt;/span&gt; logger: &lt;span class="synConstant"&gt;true&lt;/span&gt; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;)&#10;fastify.register(require(&lt;span class="synConstant"&gt;'fastify-cors'&lt;/span&gt;))&#10;fastify.register(require(&lt;span class="synConstant"&gt;'fastify-socket.io'&lt;/span&gt;), &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; allowEIO3: &lt;span class="synConstant"&gt;true&lt;/span&gt;, &lt;span class="synComment"&gt;// Socket.io v3も対応させる&lt;/span&gt;&#10; cors: &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; origin: &lt;span class="synConstant"&gt;&amp;quot;*&amp;quot;&lt;/span&gt;,&#10; methods: &lt;span class="synIdentifier"&gt;[&lt;/span&gt;&lt;span class="synConstant"&gt;&amp;quot;GET&amp;quot;&lt;/span&gt;, &lt;span class="synConstant"&gt;&amp;quot;POST&amp;quot;&lt;/span&gt;&lt;span class="synIdentifier"&gt;]&lt;/span&gt;,&#10; allowedHeaders: &lt;span class="synIdentifier"&gt;[&lt;/span&gt;&lt;span class="synConstant"&gt;&amp;quot;Origin, X-Requested-With, Content-Type, Accept&amp;quot;&lt;/span&gt;&lt;span class="synIdentifier"&gt;]&lt;/span&gt;,&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&lt;span class="synIdentifier"&gt;}&lt;/span&gt;)&#10;&#10;&lt;span class="synStatement"&gt;const&lt;/span&gt; start = async () =&amp;gt; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synStatement"&gt;try&lt;/span&gt; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; await fastify.listen(8080, &lt;span class="synConstant"&gt;'0.0.0.0'&lt;/span&gt;)&#10; fastify.io.on(&lt;span class="synConstant"&gt;'connect'&lt;/span&gt;, socket =&amp;gt; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synComment"&gt;// クライアントが接続時にクエリを投げるようにしたので、クエリにあわせてroomにjoinさせる&lt;/span&gt;&#10; &lt;span class="synStatement"&gt;switch&lt;/span&gt; (socket.handshake.query.client) &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synComment"&gt;// ブラウザのTOIOコントローラー&lt;/span&gt;&#10; &lt;span class="synStatement"&gt;case&lt;/span&gt; &lt;span class="synConstant"&gt;'controller'&lt;/span&gt;:&#10; socket.join(&lt;span class="synConstant"&gt;'controller'&lt;/span&gt;)&#10; &lt;span class="synStatement"&gt;break&lt;/span&gt;&#10; &lt;span class="synComment"&gt;// TOIOを制御する別のNode.jsプロセス&lt;/span&gt;&#10; &lt;span class="synStatement"&gt;case&lt;/span&gt; &lt;span class="synConstant"&gt;'toio'&lt;/span&gt;:&#10; socket.join(&lt;span class="synConstant"&gt;'toio'&lt;/span&gt;)&#10; &lt;span class="synStatement"&gt;break&lt;/span&gt; &#10; &lt;span class="synComment"&gt;//.....&lt;/span&gt;&#10; &lt;span class="synComment"&gt;// というような部屋の割り振り記述が続く&lt;/span&gt;&#10; &lt;span class="synComment"&gt;//.....&lt;/span&gt;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10; &lt;span class="synComment"&gt;// ブラウザからTOIOを動かす指示がきたら、TOIOのroomに揮発メッセージで送る&lt;/span&gt;&#10; socket.on(&lt;span class="synConstant"&gt;'move'&lt;/span&gt;, data =&amp;gt; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; socket.to(&lt;span class="synConstant"&gt;'toio'&lt;/span&gt;).&lt;span class="synStatement"&gt;volatile&lt;/span&gt;.emit(&lt;span class="synConstant"&gt;'toioMove'&lt;/span&gt;, data)&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;)&#10;&#10; &lt;span class="synComment"&gt;// TOIOから自己位置の情報が来たら、ブラウザとUnityに揮発メッセージで送る&lt;/span&gt;&#10; socket.on(&lt;span class="synConstant"&gt;'toio/position'&lt;/span&gt;, data =&amp;gt; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; socket.to(&lt;span class="synConstant"&gt;'controller'&lt;/span&gt;).&lt;span class="synStatement"&gt;volatile&lt;/span&gt;.emit(&lt;span class="synConstant"&gt;'toio/position'&lt;/span&gt;, data)&#10; socket.to(&lt;span class="synConstant"&gt;'unity'&lt;/span&gt;).&lt;span class="synStatement"&gt;volatile&lt;/span&gt;.emit(&lt;span class="synConstant"&gt;'toio/position'&lt;/span&gt;, data)&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;)&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;)&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt; &lt;span class="synStatement"&gt;catch&lt;/span&gt; (err) &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; fastify.log.error(err)&#10; process.exit(1)&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;start()&#10;&lt;/pre&gt;&#10;&lt;h3&gt;JavaScript Client&lt;/h3&gt;&#10;&lt;p&gt;以下はブラウザからTOIOを操作する画面のコードをかいつまんだもの。&lt;/p&gt;&#10;&lt;p&gt;ブラウザからのメッセージを受けて、実際にTOIOデバイスを操作するのは&lt;a href="https://github.com/toio/toio.js/"&gt;toio.js&lt;/a&gt; を使っている。こちらもだいたい同じようなコードになる。&lt;/p&gt;&#10;&lt;pre class="code lang-javascript" data-lang="javascript" data-unlink&gt;&lt;span class="synStatement"&gt;import&lt;/span&gt; &lt;span class="synIdentifier"&gt;{&lt;/span&gt; io &lt;span class="synIdentifier"&gt;}&lt;/span&gt; from &lt;span class="synConstant"&gt;'socket.io-client'&lt;/span&gt;&#10;&lt;span class="synStatement"&gt;const&lt;/span&gt; SOCKET_HOST = &lt;span class="synConstant"&gt;'http://localhost:8080'&lt;/span&gt;&#10;&#10;&lt;span class="synStatement"&gt;class&lt;/span&gt; SocketManager &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; connect() &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synComment"&gt;// Socketの&lt;/span&gt;&#10; &lt;span class="synIdentifier"&gt;this&lt;/span&gt;.socket = io(SOCKET_HOST, &lt;span class="synIdentifier"&gt;{&lt;/span&gt; query: &lt;span class="synIdentifier"&gt;{&lt;/span&gt; client: &lt;span class="synConstant"&gt;'controller'&lt;/span&gt; &lt;span class="synIdentifier"&gt;}&lt;/span&gt; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;)&#10; &lt;span class="synIdentifier"&gt;this&lt;/span&gt;.socket.connect()&#10; &lt;span class="synIdentifier"&gt;this&lt;/span&gt;.socket.on(&lt;span class="synConstant"&gt;'connect'&lt;/span&gt;, (socket) =&amp;gt; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; console.log(&lt;span class="synConstant"&gt;'connected'&lt;/span&gt;)&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;)&#10; &lt;span class="synIdentifier"&gt;this&lt;/span&gt;.socket.on(&lt;span class="synConstant"&gt;'toio/position'&lt;/span&gt;, (data) =&amp;gt; &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synComment"&gt;// TOIOデバイスから来た自己位置を取得&lt;/span&gt;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;)&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&#10; disconnect() &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synStatement"&gt;if&lt;/span&gt; (!&lt;span class="synIdentifier"&gt;this&lt;/span&gt;.socket) &lt;span class="synStatement"&gt;return&lt;/span&gt;&#10; &lt;span class="synIdentifier"&gt;this&lt;/span&gt;.socket.off()&#10; &lt;span class="synIdentifier"&gt;this&lt;/span&gt;.socket.disconnect()&#10; &lt;span class="synIdentifier"&gt;this&lt;/span&gt;.socket = &lt;span class="synStatement"&gt;null&lt;/span&gt;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&#10; &lt;span class="synComment"&gt;// TOIOを動かすメッセージを送る。送り過ぎたら不要になるのでvolatileで送る。&lt;/span&gt;&#10; move(left, right, duration = 100, ottikiId = 0) &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synIdentifier"&gt;this&lt;/span&gt;.socket.&lt;span class="synStatement"&gt;volatile&lt;/span&gt;.emit(&lt;span class="synConstant"&gt;'move'&lt;/span&gt;, &lt;span class="synIdentifier"&gt;{&lt;/span&gt; left, right, duration &lt;span class="synIdentifier"&gt;}&lt;/span&gt;)&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&#10;&lt;span class="synStatement"&gt;export&lt;/span&gt; &lt;span class="synStatement"&gt;default&lt;/span&gt; &lt;span class="synStatement"&gt;new&lt;/span&gt; SocketManager()&#10;&lt;/pre&gt;&#10;&lt;h3&gt;Unityのコード&lt;/h3&gt;&#10;&lt;p&gt;Socket.ioを実装したライブラリがGithubにいくつも上がっているが、だいたいが開発停止している。&lt;/p&gt;&#10;&lt;p&gt;ただひとつ、&lt;a href="https://assetstore.unity.com/packages/tools/network/best-http-2-155981"&gt;Best HTTP/2&lt;/a&gt; というアセットが Socket.io v3に対応している。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;サーバーをv4にしても動作していた&lt;/li&gt;&#10;&lt;li&gt;namespace, room対応&lt;/li&gt;&#10;&lt;li&gt;型を渡すと &lt;code&gt;On&lt;/code&gt;時にパースしてくれるようになった&lt;/li&gt;&#10;&lt;li&gt;volatile にも対応&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;pre class="code csharp" data-lang="csharp" data-unlink&gt;// Connect&#10;SocketOptions options = new SocketOptions();&#10;options.AdditionalQueryParams = new PlatformSupport.Collections.ObjectModel.ObservableDictionary&amp;lt;string, string&amp;gt;();&#10;options.AdditionalQueryParams.Add(&amp;#34;client&amp;#34;, &amp;#34;unity&amp;#34;); // Connect時にパラメータを渡せる&#10;_manager = new SocketManager(new Uri(&amp;#34;http://localhost:8080&amp;#34;), options);&#10;&#10;// 受信&#10;// 型を渡すとパースしてくれる&#10;_manager.Socket.On&amp;lt;CubeInfo&amp;gt;(&amp;#34;toio/position&amp;#34;, (CubeInfo data) =&amp;gt;&#10;{&#10; Debug.Log($&amp;#34;CubeInfo: {data.index}, {data.x}, {data.y}, {data.angle}&amp;#34;);&#10;});&#10;&#10;// 送信&#10;_manager.Socket.Emit(&amp;#39;hoge&amp;#39;)&lt;/pre&gt;&#10;&lt;h2&gt;アナログメタバースの知見&lt;/h2&gt;&#10;&lt;p&gt;こんな小さな実験でもいくつかの知見が得られた。&lt;/p&gt;&#10;&lt;h3&gt;アナログアバターに魂が乗る&lt;/h3&gt;&#10;&lt;p&gt;&lt;figure class="figure-image figure-image-fotolife" title="アバターを撫でる"&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20210610/20210610105019.gif" alt="f:id:bascule-dev:20210610105019g:plain" width="720" height="396" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;figcaption&gt;アバターを撫でる&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&#10;&lt;p&gt;リモートユーザーから「リアルに現場にいるひとにからアバターを撫でられるとどうなるのか？」との問いがあった。撫でられると「ちょっとうれしい」らしい。逆に横から叩いてなぎ倒してみると、やはりつらい思いがあったらしい。実在する人物から叩かれると心理的な影響が大きいのかもしれない。ゲームのメタバースで同じことをしても、ここまでの感情になるのだろうか？&lt;/p&gt;&#10;&lt;h3&gt;リアル側の人間がリアルのアバターを見ると、言葉は通わせないが意思疎通はできる小動物とみなせる&lt;/h3&gt;&#10;&lt;p&gt;&lt;figure class="figure-image figure-image-fotolife" title="ボールを持ってきてもらう"&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20210610/20210610105010.gif" alt="f:id:bascule-dev:20210610105010g:plain" width="716" height="299" loading="lazy" title="" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;figcaption&gt;ボールを持ってきてもらう&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&#10;&lt;p&gt;アバターにボールを持ってこさせるという実験をした。リアル側の人間から見ると、きちんと意思の疎通ができる小動物と遊んでいる感覚があった。だがそれは人間でも犬でもない、別の生物。珍妙このうえない気持ちになる。&lt;/p&gt;&#10;&lt;h2&gt;まとめ&lt;/h2&gt;&#10;&lt;p&gt;Socket.ioはすごく簡単かつ、確実にメッセージを届けられるライブラリ。OSCと併用するともっと大量のメッセージのやりとりもできるようになる。&lt;/p&gt;&#10;&lt;h2&gt;エンジニア募集中&lt;/h2&gt;&#10;&lt;p&gt;バスキュールではコードを書くだけではなく、自分が信じるおもしろさを考え、実現するエンジニアを募集しています！フロントもバックエンドも募集中。&lt;/p&gt;&#10;&lt;p&gt;&lt;iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fbascule.co.jp%2Fjoin-us%2F" title="JOIN US - Bascule Inc." class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://bascule.co.jp/join-us/"&gt;bascule.co.jp&lt;/a&gt;&lt;/cite&gt;&#10;&lt;iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.green-japan.com%2Fjob%2F125439%3Fcase%3Dlogin" title="バスキュールの求人情報 | 転職サイトGreen（グリーン）" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://www.green-japan.com/job/125439?case=login"&gt;www.green-japan.com&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;&lt;h2&gt;で、もうすぐ別のおもしろコンテンツが出ます&lt;/h2&gt;&#10;&lt;p&gt;まもなく、こんなモッククオリティではない、バスキュールらしいおもしろコンテンツがリリースされます。乞うご期待！&lt;/p&gt;&#10;&lt;h2&gt;参考リンク&lt;/h2&gt;&#10;&lt;p&gt;fokaさんのUnityとTOIOを連動させる記事&lt;br /&gt;&#10;&lt;a href="https://qiita.com/foka22ok/items/9466b0eb80988821808a"&gt;WindowsのUnityからBLEアプリ経由してtoioで遊ぶ - Qiita&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;Obnizでサーボ動かしてる記事&lt;br /&gt;&#10;&lt;a href="https://zenn.dev/ryotkbb/articles/2124c6492a93a189a526"&gt;kintoneで反応速度ゲームを作ってみた&lt;/a&gt;&lt;/p&gt;&#10;</content><link rel="enclosure" href="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20210609/20210609212821.png" type="image/png" length="0"/><author><name>bascule-dev</name></author></entry><entry><title>Detectronでおしゃ文字をつくる</title><link href="https://blog.bascule.co.jp/entry/2019/04/10/000000"/><id>hatenablog://entry/17680117127008727337</id><published>2019-04-09T14:00:00+09:00</published><updated>2019-04-09T05:35:05+09:00</updated><summary type="html">エンジニアの丸山（@maruware）です。 1年ほど前にデイリーポータルZで「現実化したフォント」がおしゃれなのでマスターしたい :: デイリーポータルZ という記事が掲載されていました。 こういうの（記事内より引用) おしゃれですね。記事内ではこれを"おしゃ文字"と名付けていました。 一方でDeep Learning的な研究の進歩が著しい昨今、セマンティックセグメンテーション（タグ付きのマスクを検出するようなもの）の分野も盛んに研究されていて、Mask R-CNNなどすごい精度が上がってきています。 (Detectronより引用) (matterport/Mask_RCNN より引用) こ…</summary><content type="html">&lt;p&gt;エンジニアの丸山（@maruware）です。&lt;/p&gt;&#10;&lt;p&gt;1年ほど前にデイリーポータルZで&lt;a href="https://dailyportalz.jp/kiji/180320202386"&gt;「現実化したフォント」がおしゃれなのでマスターしたい :: デイリーポータルZ&lt;/a&gt; という記事が掲載されていました。&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://backnumber.dailyportalz.jp/2018/03/21/c/img/pc/016.jpg" alt="oshamoji" /&gt;&lt;/p&gt;&#10;&lt;p&gt;こういうの（記事内より引用)&lt;/p&gt;&#10;&lt;p&gt;おしゃれですね。記事内ではこれを"おしゃ文字"と名付けていました。&lt;/p&gt;&#10;&lt;p&gt;一方でDeep Learning的な研究の進歩が著しい昨今、セマンティックセグメンテーション（タグ付きのマスクを検出するようなもの）の分野も盛んに研究されていて、Mask R-CNNなどすごい精度が上がってきています。&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://github.com/facebookresearch/Detectron/raw/master/demo/output/33823288584_1d21cf0a26_k_example_output.jpg" alt="" /&gt;&lt;/p&gt;&#10;&lt;p&gt;(&lt;a href="https://github.com/facebookresearch/Detectron"&gt;Detectron&lt;/a&gt;より引用)&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://github.com/matterport/Mask_RCNN/raw/master/assets/4k_video.gif" alt="" /&gt;&lt;/p&gt;&#10;&lt;p&gt;(&lt;a href="https://github.com/matterport/Mask_RCNN"&gt;matterport/Mask_RCNN&lt;/a&gt; より引用)&lt;/p&gt;&#10;&lt;p&gt;これがあればおしゃ文字が自動でつくれるんじゃないか、ということで&lt;/p&gt;&#10;&lt;p&gt;先日、&lt;a href="https://nihonbashi-sakurafes.art/"&gt;NIHONBASHI SAKURA FESTIVAL&lt;/a&gt;というイベントの中のPhoto Sessionという枠で&lt;strong&gt;「写真家が現地で記念撮影をしてくれて、それをおしゃ文字化する」&lt;/strong&gt;という形で実施してみたので紹介します。&lt;/p&gt;&#10;&lt;h2&gt;Detectronを使う&lt;/h2&gt;&#10;&lt;p&gt;&lt;a href="https://github.com/facebookresearch/Detectron"&gt;Detectron&lt;/a&gt;はfacebookがOSSとして公開しているセマンティックセグメンテーション用のライブラリです。あまりライブラリとしてのAPIやドキュメントは整備されていないためコードを追っていく必要があります。&lt;/p&gt;&#10;&lt;p&gt;（記事の最後におしゃ文字部分とまとめてjupyter notebookのサンプルのリンクを記載しました）&lt;/p&gt;&#10;&lt;p&gt;検出処理を実行するとバウンディングボックス、セグメント、クラス（物体のカテゴリ）が得られます。&lt;/p&gt;&#10;&lt;p&gt;モデルの一覧は&lt;a href="https://github.com/facebookresearch/Detectron/blob/master/MODEL_ZOO.md"&gt;MODEL_ZOO.md&lt;/a&gt;にあり、学習済みのモデルデータも提供されています。今回はいくつか試した結果、&lt;code&gt;End-to-End Faster &amp;amp; Mask R-CNN BaselinesのR-101-FPN 2x&lt;/code&gt; を使用しました。&lt;/p&gt;&#10;&lt;p&gt;検出処理の時間はGTX 1070やK80を積んだマシンで0.2〜0.3秒くらいでした。初回実行時のみ2〜3秒ほどかかります。&lt;/p&gt;&#10;&lt;h2&gt;おしゃ文字をつくる&lt;/h2&gt;&#10;&lt;p&gt;今回はイベントの趣旨から、主に記念撮影の被写体である人間を前景としてマスクのターゲットにします。&lt;/p&gt;&#10;&lt;p&gt;ざっくりした流れとしては&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Detectronで検出&lt;/li&gt;&#10;&lt;li&gt;検出結果のリストから人間クラスのマスクのみ抽出&lt;/li&gt;&#10;&lt;li&gt;元画像からマスクで抜いた前景画像を作成&lt;/li&gt;&#10;&lt;li&gt;文字を描画したテキスト画像を作成&lt;/li&gt;&#10;&lt;li&gt;元画像、テキスト画像、前景画像と重ねて合成&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;というものになります。&lt;/p&gt;&#10;&lt;p&gt;描画まわりは&lt;a href="https://github.com/emcconville/wand"&gt;pythonのImageMagick bindingライブラリのwand&lt;/a&gt;を使用しました。最初は&lt;a href="https://github.com/python-pillow/Pillow"&gt;Pillow&lt;/a&gt;を使っていましたが、カーニングを調整することができないようなのでwandに移行しました。&lt;/p&gt;&#10;&lt;p&gt;アプリケーションとして実装する上で得られたTipsを以下で紹介していきます。&lt;/p&gt;&#10;&lt;h3&gt;Tips 1. 後ろの方にいる被写体ではない人を除外したい&lt;/h3&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20190405/20190405202101.jpg" alt="f:id:bascule-dev:20190405202101j:plain" title="f:id:bascule-dev:20190405202101j:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&#10;例えばこちらのような開けた場所では後ろの方に小さく人が写ります。文字の上に重なるとレイヤー感がおかしく見え、不具合のように見えます。&lt;/p&gt;&#10;&lt;p&gt;この問題を解決するために、&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;面積で閾値処理: 1番大きく検出されたバウンディングボックスの面積との比率で判定&lt;/li&gt;&#10;&lt;li&gt;Y座標で閾値処理: バウンディングボックスの下端を比較して判定&lt;/li&gt;&#10;&lt;li&gt;深度推定をして閾値処理: 別途、2Dによる深度推定を行って判定&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;といった手法を検討しました。&lt;/p&gt;&#10;&lt;p&gt;1は子供連れの方で誤る可能性、3は試したもののまだ現実的ではなさそう、ということで2を採用しました。&lt;/p&gt;&#10;&lt;p&gt;ただし、幼児を抱いた状態で幼児が除外される、などが起きる懸念があります。&lt;/p&gt;&#10;&lt;h3&gt;Tips 2. 被写体が持つ手荷物もいっしょに抜きたい&lt;/h3&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20190405/20190405202123.jpg" alt="f:id:bascule-dev:20190405202123j:plain" title="f:id:bascule-dev:20190405202123j:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&#10;いい例がなかったのでフリー素材からですが（つまりレアケースとも言えそうですが）、被写体がかばんを持っていてその上に文字が重なると不具合のように見えます。&lt;/p&gt;&#10;&lt;p&gt;Detectron（正しくはデータセットのcoco）ではハンドバッグも対象クラスになっているので、「被写体と見なした人のセグメントと交差するハンドバッグのセグメントはマスクに含む」と判定をすることで対応しました。&lt;/p&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20190405/20190405202149.jpg" alt="f:id:bascule-dev:20190405202149j:plain" title="f:id:bascule-dev:20190405202149j:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;p&gt;今回の場合は事前に手荷物を預かるようなオペレーションもできましたが、一応かばんを持っていても抜けるように対応しました。&lt;/p&gt;&#10;&lt;h3&gt;Tips 3. 雨が降って傘を持っている可能性がある。傘も抜きたい。&lt;/h3&gt;&#10;&lt;p&gt;今回は屋外で撮影が行われるため、当日に雨が降り傘をさしている可能性があります。傘もかばんと同様に人と同じレイヤーに当たるので抜き出します。&lt;/p&gt;&#10;&lt;p&gt;幸い、傘もDetectronの対象クラスなので検出されます。&lt;/p&gt;&#10;&lt;p&gt;ただ、&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;傘は人をくり抜いたような形のセグメントになり、ときどき交差しないことがある&lt;/li&gt;&#10;&lt;li&gt;後ろの人の傘が被写体の人と交差することがある&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;ということでかばんと同様の交差判定では対応できませんでした。&lt;/p&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20190405/20190405202254.png" alt="f:id:bascule-dev:20190405202254p:plain" title="f:id:bascule-dev:20190405202254p:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;p&gt;そこで、基本的には傘は頭を覆うように写るため、「バウンディングボックス内に被写体の上端を含む傘」を対象とするようにしました。&lt;/p&gt;&#10;&lt;p&gt;ヒューリスティックであまりイケてないですが、雨天はイレギュラーということでお茶を濁すことにしました。&lt;/p&gt;&#10;&lt;h3&gt;その他&lt;/h3&gt;&#10;&lt;h4&gt;文字組みについて&lt;/h4&gt;&#10;&lt;p&gt;デザイナーと相談ながら調整した結果&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;文字の30%が人に重なっているくらいがちょうどよい&lt;/li&gt;&#10;&lt;li&gt;文字のサイズは基本は画像の横幅いっぱいに大きく表示するのが良い&lt;/li&gt;&#10;&lt;li&gt;1〜2文字の場合は画面幅合わせだと大きすぎるので調整&lt;/li&gt;&#10;&lt;li&gt;画として美しい文字数は3 or 4文字。6文字までがきれいに見える限界。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;という結論になりました。&lt;/p&gt;&#10;&lt;h2&gt;結果&lt;/h2&gt;&#10;&lt;p&gt;マスクを人力で用意するのはしんどいので定量的な評価はできておらず、目視での評価になりますが、概ね違和感なくおしゃ文字することができました。&lt;/p&gt;&#10;&lt;p&gt;ただ、いくつか課題が出ました。&lt;/p&gt;&#10;&lt;h4&gt;&lt;strong&gt;2列に並んで撮影するとTips 1で行った処理で後列が除外される&lt;/strong&gt;&lt;/h4&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20190405/20190405202327.jpg" alt="f:id:bascule-dev:20190405202327j:plain" title="f:id:bascule-dev:20190405202327j:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20190405/20190405202548.jpg" alt="f:id:bascule-dev:20190405202548j:plain" title="f:id:bascule-dev:20190405202548j:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;p&gt;足まで写っていないときにセグメントが上半身のみになってしまい、除外されてしまいました。2列に並ぶのは記念写真ではあるあるだと思うのでもうちょっと工夫が必要そうです。&lt;/p&gt;&#10;&lt;h4&gt;&lt;strong&gt;幼児を抱いているとTips 1で行った処理で幼児が除外される&lt;/strong&gt;&lt;/h4&gt;&#10;&lt;p&gt;予想通り、幼児はTips 1の処理で除外されてしまいました。&#10;予想外に幼児を連れた方が多く参加されてて申し訳ない気持ちになりました。&lt;/p&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20190405/20190405202609.png" alt="f:id:bascule-dev:20190405202609p:plain" title="f:id:bascule-dev:20190405202609p:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&#10;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20190405/20190405202630.png" alt="f:id:bascule-dev:20190405202630p:plain" title="f:id:bascule-dev:20190405202630p:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;p&gt;また、これは想定外だったのですが、幼児を前で抱えると誤検出が増えることがわかりました。&lt;/p&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20190405/20190405202651.png" alt="f:id:bascule-dev:20190405202651p:plain" title="f:id:bascule-dev:20190405202651p:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;p&gt;大人の足が幼児と同一人物として検出されています。他にもこういった例が見られました。cocoの学習データにこういったサンプルが少なさそうなのでこういった例を拾うのはむずかしそうです。&lt;/p&gt;&#10;&lt;p&gt;幼児対策として&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;幼児は横に抱いてもらう&lt;/li&gt;&#10;&lt;li&gt;大人は上半身のみで撮影する&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;とカットで逃げることで対処してもらいました。&lt;/p&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20190408/20190408161515.png" alt="f:id:bascule-dev:20190408161515p:plain" title="f:id:bascule-dev:20190408161515p:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;h4&gt;写真集&lt;/h4&gt;&#10;&lt;p&gt;以下、写真集です。&lt;/p&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20190405/20190405202732.jpg" alt="f:id:bascule-dev:20190405202732j:plain" title="f:id:bascule-dev:20190405202732j:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20190405/20190405202753.jpg" alt="f:id:bascule-dev:20190405202753j:plain" title="f:id:bascule-dev:20190405202753j:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20190405/20190405203104.jpg" alt="f:id:bascule-dev:20190405203104j:plain" title="f:id:bascule-dev:20190405203104j:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;p&gt;暗くても抜ける（すごい）&lt;/p&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20190405/20190405203125.jpg" alt="f:id:bascule-dev:20190405203125j:plain" title="f:id:bascule-dev:20190405203125j:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;h2&gt;おわりに&lt;/h2&gt;&#10;&lt;p&gt;テクノロジーの無駄遣いという感もありますが、AI的なものをエンターテイメント活用できて面白かったです。記念撮影という形で試せたことも、&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;写真のコントロールがこちらでできる&lt;/li&gt;&#10;&lt;li&gt;枚数も少ない（GPUインスタンスいっぱい立てるとお金がかかる）&lt;/li&gt;&#10;&lt;li&gt;1組に複数回の撮影ができるため、失敗してる写真があってもおもしろくらいでごまかせる&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;といった点でよかったです。&lt;/p&gt;&#10;&lt;p&gt;まだ人力でPhotoshopなどで抜くのに及ばない課題はいろいろありますが、撮ったら待たずにすぐに見れるのは体験として良いし連写に対応できるのも長所かなと思います。&lt;/p&gt;&#10;&lt;p&gt;そのうちプリクラに載るかもしれないですね。&lt;/p&gt;&#10;&lt;p&gt;今回ご協力いただいた写真家の五十嵐絢也さんにはむずかしい要求にも快く応えていただき良いおしゃ文字写真をつくることができました。&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://github.com/bascule-dev/oshamoji-notebook"&gt;jupyter notebook&lt;/a&gt; にコードのサンプルもアップしたのでよかったら見てみてください。&lt;/p&gt;&#10;</content><link rel="enclosure" href="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20190405/20190405202732.jpg" type="image/jpeg" length="0"/><author><name>bascule-dev</name></author></entry><entry><title>AfterEffectsをサーバーサイドで使って動的に動画を生成する</title><link href="https://blog.bascule.co.jp/entry/2017/09/12/100000"/><id>hatenablog://entry/8599973812296991155</id><published>2017-09-12T10:00:00+09:00</published><updated>2017-09-12T01:00:00+09:00</updated><summary type="html">エンジニアの丸山（@maruware）です。 7月末に開催されたWRCフィンランド戦でToyota Gazoo RacingさんとEchoCamというコンテンツをやっていました。 そのときに、AfterEffectsによる動的動画生成をしていたのですが、あんまり日本語の記事がなさそうなので多少参考になるかなということで 今回はそちらを紹介したいと思います。 EchoCamとは ↑の紹介動画の通り、 ユーザー（観戦者）が撮影した写真の時刻とドライブレコーダーの時刻を同期して、動画を生成、ユーザーに届ける というものです。 図で書くとこんな感じ。 その中の動画生成にAfterEffectsを使って…</summary><content type="html">&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20170911/20170911153626.jpg" alt="f:id:bascule-dev:20170911153626j:plain" title="f:id:bascule-dev:20170911153626j:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;p&gt;エンジニアの丸山（@maruware）です。&lt;br/&gt;&#10;7月末に開催されたWRCフィンランド戦でToyota Gazoo RacingさんとEchoCamというコンテンツをやっていました。&lt;br/&gt;&#10;そのときに、AfterEffectsによる動的動画生成をしていたのですが、あんまり日本語の記事がなさそうなので多少参考になるかなということで&#10;今回はそちらを紹介したいと思います。&lt;/p&gt;&#10;&lt;h1&gt;EchoCamとは&lt;/h1&gt;&#10;&lt;iframe src="https://www.facebook.com/plugins/video.php?href=https%3A%2F%2Fwww.facebook.com%2FTOYOTAGAZOORacingWRC%2Fvideos%2F394905150911676%2F&amp;show_text=0&amp;width=560" width="560" height="315" style="border:none;overflow:hidden" scrolling="no" frameborder="0" allowTransparency="true" allowFullScreen="true"&gt;&lt;/iframe&gt;&#10;&lt;p&gt;↑の紹介動画の通り、&lt;br/&gt;&#10;ユーザー（観戦者）が撮影した写真の時刻とドライブレコーダーの時刻を同期して、動画を生成、ユーザーに届ける&lt;br/&gt;&#10;というものです。&lt;/p&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20170911/20170911141618.jpg" alt="f:id:bascule-dev:20170911141618j:plain" title="f:id:bascule-dev:20170911141618j:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;p&gt;図で書くとこんな感じ。&lt;br/&gt;&#10;その中の動画生成にAfterEffectsを使っています。紹介動画の40秒あたりからが生成される動画です。&lt;/p&gt;&#10;&lt;h1&gt;AfterEffectsを使った動的動画生成&lt;/h1&gt;&#10;&lt;h2&gt;①aerenderでAfterEffects自動化&lt;/h2&gt;&#10;&lt;p&gt;AfterEffectsにはCLIから実行できるようにaerenderという実行ファイルが含まれています。&lt;br/&gt;&#10;詳しくは↓のAdobe公式ガイドをご覧ください。&lt;/p&gt;&#10;&lt;p&gt;&lt;iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fhelpx.adobe.com%2Fjp%2Fafter-effects%2Fusing%2Fautomated-rendering-network-rendering.html" title="After Effects での自動レンダリングとネットワークレンダリング" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://helpx.adobe.com/jp/after-effects/using/automated-rendering-network-rendering.html"&gt;helpx.adobe.com&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;&lt;h2&gt;②AfterEffects内のアセットの動的差し替え&lt;/h2&gt;&#10;&lt;p&gt;ユーザーの投稿それぞれに動画を生成したいので、画像・動画・テキストなどを差し替えます。&lt;br/&gt;&#10;以下の記事が英語ですが参考になるかと思います。&lt;/p&gt;&#10;&lt;p&gt;&lt;iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.themarketingtechnologist.co%2Fcreating-dynamic-videos-using-javascript-and-after-effects-the-basics%2F" title="Creating dynamic videos using JavaScript and After Effects: the basics" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://www.themarketingtechnologist.co/creating-dynamic-videos-using-javascript-and-after-effects-the-basics/"&gt;www.themarketingtechnologist.co&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;&lt;p&gt;基本的には&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;画像や動画はフッテージとして配置してファイルを差し替えればOK&lt;/li&gt;&#10;&lt;li&gt;テキストはJavaScriptのファイルを参照してExpressionで処理すればOK&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;です。&lt;br/&gt;&#10;外部ファイルは&lt;code&gt;$.evalFile(path)&lt;/code&gt;としてevalするしかないようなので&lt;br/&gt;&#10;以下のようなjsファイルを実装しておくような形になります。&lt;/p&gt;&#10;&lt;pre class="code lang-javascript" data-lang="javascript" data-unlink&gt;&lt;span class="synIdentifier"&gt;var&lt;/span&gt; model = &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synConstant"&gt;&amp;quot;baseTime&amp;quot;&lt;/span&gt;: 1496984724659,&#10; &lt;span class="synConstant"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;: 100,&#10; &lt;span class="synConstant"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;: &lt;span class="synConstant"&gt;&amp;quot;Taro&amp;quot;&lt;/span&gt;,&#10; &lt;span class="synConstant"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;: 3740,&#10; &lt;span class="synConstant"&gt;&amp;quot;ssName&amp;quot;&lt;/span&gt;: &lt;span class="synConstant"&gt;&amp;quot;ss2&amp;quot;&lt;/span&gt;,&#10; &lt;span class="synConstant"&gt;&amp;quot;top&amp;quot;&lt;/span&gt;: 320,&#10; &lt;span class="synConstant"&gt;&amp;quot;window&amp;quot;&lt;/span&gt;: &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synConstant"&gt;&amp;quot;h&amp;quot;&lt;/span&gt;: 1080,&#10; &lt;span class="synConstant"&gt;&amp;quot;w&amp;quot;&lt;/span&gt;: 1920&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&lt;span class="synIdentifier"&gt;}&lt;/span&gt;;&#10;&lt;/pre&gt;&#10;&lt;p&gt;ここで1つ面倒なことがあって、&lt;code&gt;$.evalFile(path)&lt;/code&gt;で参照できるパスは絶対パスじゃないといけません。&lt;br/&gt;&#10;開発時にgitで管理するときのポータビリティ的にも運用時の配置的にも使いづらいです。&lt;/p&gt;&#10;&lt;h2&gt;③nexrenderでポータブル化&lt;/h2&gt;&#10;&lt;p&gt;nexrenderというありがたいライブラリがあります。&lt;br/&gt;&#10;&lt;iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2FInlife%2Fnexrender" title="Inlife/nexrender" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://github.com/Inlife/nexrender"&gt;github.com&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;&lt;p&gt;Node.js製です。&lt;br/&gt;&#10;今回は使っていませんがサーバーとして起動する機能もあるようで多機能です。&lt;br/&gt;&#10;（ちょろっとContributeしたりもしています）&lt;/p&gt;&#10;&lt;p&gt;nexrenderを使うことで、&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;AEのプロジェクトファイルとそのアセットをセットにして宣言的に定義できる（フォーマット化されている）&lt;/li&gt;&#10;&lt;li&gt;アセットのソースをリモートのURLまたはローカルのパスで指定できる&lt;/li&gt;&#10;&lt;li&gt;②で問題だったjsファイルの絶対パス問題が解決できる&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;というよさがあります。&lt;br/&gt;&#10;AEのプロジェクトファイルはaepx（XMLファイル形式）にする必要があります。&lt;/p&gt;&#10;&lt;p&gt;jsファイルの絶対パス問題はけっこうな力技で解決していて、&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;aepx内にバイナリで保存されているExpressionをBufferでデコード&lt;/li&gt;&#10;&lt;li&gt;正規表現でパス文字列を検索して置換する&lt;/li&gt;&#10;&lt;li&gt;再度バイナリ化してaepx内に設定&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;ということをしています。&lt;br/&gt;&#10;このあたりの処理が少しゆるいようで&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;通常のjsの式がパスとして解釈されることがある（カッコの位置がけっこう影響する）&lt;/li&gt;&#10;&lt;li&gt;Expression内に日本語などマルチバイト文字を含んでいると壊れる&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;という不具合があり、注意が必要です。&lt;br/&gt;&#10;せっかくバグ踏んだので時間があるときにパッチかこうかなと思います。&lt;/p&gt;&#10;&lt;h2&gt;④いろいろ動かす&lt;/h2&gt;&#10;&lt;p&gt;Expression内で動的な値を読み込むことができるので、&lt;br/&gt;&#10;Expressionでできることは動的にすることができます。&lt;/p&gt;&#10;&lt;p&gt;例えば、EchoCamでは"位置"のExpressionに&lt;/p&gt;&#10;&lt;pre class="code lang-javascript" data-lang="javascript" data-unlink&gt;$.evalFile(&lt;span class="synConstant"&gt;'/your-project-path/model.js'&lt;/span&gt;);&#10;&lt;span class="synIdentifier"&gt;var&lt;/span&gt; dx = (model.left + (model.&lt;span class="synStatement"&gt;window&lt;/span&gt;.w / 2.0));&#10;&lt;span class="synIdentifier"&gt;var&lt;/span&gt; dy = (model.&lt;span class="synStatement"&gt;top&lt;/span&gt; + (model.&lt;span class="synStatement"&gt;window&lt;/span&gt;.h / 2.0));&#10;&#10;easeIn(time, 9.0, 10.0, &lt;span class="synIdentifier"&gt;[&lt;/span&gt;dx / 2, dy / 2&lt;span class="synIdentifier"&gt;]&lt;/span&gt;, &lt;span class="synIdentifier"&gt;[&lt;/span&gt;960, 540&lt;span class="synIdentifier"&gt;]&lt;/span&gt;)&#10;&lt;/pre&gt;&#10;&lt;p&gt;と設定、またスケールも対応したExpressionを設定して&lt;br/&gt;&#10;「特定の位置に向かってズーム」をしています。&lt;/p&gt;&#10;&lt;p&gt;また、時刻のカウンタを&lt;/p&gt;&#10;&lt;pre class="code lang-javascript" data-lang="javascript" data-unlink&gt;$.evalFile(&lt;span class="synConstant"&gt;'/your-project-path/model.js'&lt;/span&gt;);&#10;&lt;span class="synIdentifier"&gt;var&lt;/span&gt; baseTime = model.baseTime;&#10;&lt;span class="synIdentifier"&gt;var&lt;/span&gt; frameCount = timeToFrames();&#10;&lt;span class="synIdentifier"&gt;var&lt;/span&gt; msec = frameCount * 10 * 100 / 99;&#10;&#10;&lt;span class="synIdentifier"&gt;var&lt;/span&gt; curr = &lt;span class="synStatement"&gt;new&lt;/span&gt; &lt;span class="synType"&gt;Date&lt;/span&gt;(baseTime + msec);&#10;&#10;&lt;span class="synIdentifier"&gt;var&lt;/span&gt; h = ((curr.getUTCHours() + 3) % 24).toString();&#10;&lt;span class="synIdentifier"&gt;var&lt;/span&gt; m = (&lt;span class="synConstant"&gt;'00'&lt;/span&gt; + curr.getMinutes()).slice(-2);&#10;&lt;span class="synIdentifier"&gt;var&lt;/span&gt; s = (&lt;span class="synConstant"&gt;'00'&lt;/span&gt; + curr.getSeconds()).slice(-2);&#10;&lt;span class="synIdentifier"&gt;var&lt;/span&gt; ms = (&lt;span class="synConstant"&gt;'000'&lt;/span&gt; + curr.getMilliseconds()).slice(-3);&#10;&#10;h + &lt;span class="synConstant"&gt;':'&lt;/span&gt; + m + &lt;span class="synConstant"&gt;&amp;quot;'&amp;quot;&lt;/span&gt; + s + &lt;span class="synConstant"&gt;'&amp;quot;'&lt;/span&gt; + ms;&#10;&lt;/pre&gt;&#10;&lt;p&gt;のようにして回したりしています。&lt;/p&gt;&#10;&lt;h1&gt;おわりに&lt;/h1&gt;&#10;&lt;p&gt;動的動画生成にはよくffmpeg+ImageMagickを使いますが、そちらと比較して&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;凝った演出がやりやすい&lt;/li&gt;&#10;&lt;li&gt;デザイナーや映像クリエイターと分業しやすい&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;といったメリットがあります。&lt;br/&gt;&#10;一方、デメリットして&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;処理時間がかかる（GPUそこそこのマシンで30秒の動画生成に2分くらいかかったりする）&lt;/li&gt;&#10;&lt;li&gt;AEのライセンスが必要&lt;/li&gt;&#10;&lt;li&gt;WindowsまたはMacをレンダリングサーバーとして用意する必要&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;といったあたりがあります。&lt;/p&gt;&#10;&lt;p&gt;EchoCamのようにユーザー数が限られていたり、札束をぶちこめるようなときには選択肢の1つとして検討してみてはいかがでしょうか。&lt;/p&gt;&#10;</content><link rel="enclosure" href="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20170911/20170911153626.jpg" type="image/jpeg" length="0"/><author><name>bascule-dev</name></author></entry><entry><title>GearVR用Android端末の初期設定を自動化した話</title><link href="https://blog.bascule.co.jp/entry/2017/07/03/android-setup"/><id>hatenablog://entry/8599973812276305299</id><published>2017-07-03T11:20:14+09:00</published><updated>2017-07-03T02:48:19+09:00</updated><summary type="html">Androidのセットアップ、実は自動化できます。 VRエンジニアの桟(@katapad )です。 以前、GearVRを使った案件で、数十台の端末セットアップを自動化しました。Googleアカウントなどのパスワードをちまちま入力するの、めんどくさいですよね。iPhoneのようにバックアップからリストアできたらよかったのですが、うまくいかずにガリッとやっちゃいました。 ↑自動でGoogleアカウントを設定している様子 詳しい実装を書くとめちゃくちゃに長くなるので、ここでは要点だけを書きますね。 Android SDKのadb shell機能を使うと、わりとやりたい放題できる shellが用意され…</summary><content type="html">&lt;h2&gt;Androidのセットアップ、実は自動化できます。&lt;/h2&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20170703/20170703113913.gif" alt="f:id:bascule-dev:20170703113913g:plain" title="f:id:bascule-dev:20170703113913g:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;p&gt;VRエンジニアの桟(&lt;a href="https://twitter.com/katapad"&gt;@katapad&lt;/a&gt; )です。&lt;br/&gt;&#10;以前、GearVRを使った案件で、数十台の端末セットアップを自動化しました。Googleアカウントなどのパスワードをちまちま入力するの、めんどくさいですよね。iPhoneのようにバックアップからリストアできたらよかったのですが、うまくいかずにガリッとやっちゃいました。&lt;/p&gt;&#10;&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/YExdm36sZ1c" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&#10;&lt;p&gt;&#10;↑自動でGoogleアカウントを設定している様子&lt;/p&gt;&#10;&lt;p&gt;詳しい実装を書くとめちゃくちゃに長くなるので、ここでは要点だけを書きますね。&lt;/p&gt;&#10;&lt;h2&gt;Android SDKのadb shell機能を使うと、わりとやりたい放題できる&lt;/h2&gt;&#10;&lt;p&gt;shellが用意されており、PC側からそれなりに操作できます。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;キーボードイベントを発生させる&lt;/li&gt;&#10;&lt;li&gt;タップ・スワイプイベントを発生させる&#10;&lt;ul&gt;&#10;&lt;li&gt;ロングタップもOK&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;各種設定画面を開く&lt;/li&gt;&#10;&lt;li&gt;画面のキャプチャを撮る&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;これを「設定画面を開いて」-「チェックマークあたりをタップして」という具合に、チマチマと設定していきます。&lt;/p&gt;&#10;&lt;p&gt;さらにAndroid NDK (Native Development Kit)を入れていじくると、Androidの画面をストリームで取得したりができるようです。&lt;/p&gt;&#10;&lt;h2&gt;できたこと: アカウント・パスワード入力自動化、設定自動化&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Googleアカウントの設定、wifiパスワード入力などのテキスト入力&lt;/li&gt;&#10;&lt;li&gt;設定画面での設定を自動化&#10;&lt;ul&gt;&#10;&lt;li&gt;ディスプレイの自動オフを10分にする&lt;/li&gt;&#10;&lt;li&gt;GearVRサービスで開発者モードをオンにする&lt;/li&gt;&#10;&lt;li&gt;など&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;上部ドロワーメニュー内の設定のOn/Off操作&lt;br/&gt;&#10;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20170703/20170703111625.gif" alt="f:id:bascule-dev:20170703111625g:plain" title="f:id:bascule-dev:20170703111625g:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&#10;↑ Androidのスクショ撮ってPCに渡してOpenCVでテンプレートマッチング→どこタップするか判定→OnにしたりOffにしたりする&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3&gt;Googleアカウント設定の例&lt;/h3&gt;&#10;&lt;p&gt;けっこう泥臭くやります。&lt;/p&gt;&#10;&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/pZefWN6N2n8" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&#10;&lt;pre class="code" data-lang="" data-unlink&gt;#!/usr/bin/env zsh&#10;# キーが数字だとよくわからんので変数にいれる&#10;KEY_HOME=3&#10;KEY_TAB=61&#10;KEY_ENTER=66&#10;KEY_DOWN=20&#10;KEY_EVENT=&amp;#34;shell input keyevent &amp;#34;&#10;PRESS_KEY_ENTER=$KEY_EVENT$KEY_ENTER&#10;PRESS_KEY_TAB=$KEY_EVENT$KEY_TAB&#10;PRESS_KEY_DOWN=$KEY_EVENT$KEY_DOWN&#10;&#10;# USBで繋がってる端末ID&#10;DEVICE=$1&#10;S=&amp;#34;-s $DEVICE&amp;#34;&#10;&#10;ID=&amp;#34;your_google_account&amp;#34;&#10;PW=&amp;#34;your_password&amp;#34;&#10;&#10;&#10;# Google アカウント設定画面開く&#10;adb $S shell am start -a android.settings.SYNC_SETTINGS&#10;sleep 1.5&#10;&#10;## [アカウント追加] - [追加]を選択&#10;adb $S $KEY_EVENT $KEY_DOWN $KEY_DOWN $KEY_ENTER&#10;sleep 1.5&#10;&#10;echo &amp;#34;アカウント追加 選択するもの画面&amp;#34;&#10;# 6回下キーを押してエンターを押す&#10;_TMP_KEY=&amp;#34; &amp;#34;&#10;for i in `seq 0 5`&#10;do&#10; _TMP_KEY=&amp;#34;$_TMP_KEY $KEY_DOWN&amp;#34;&#10;done&#10;echo &amp;#34;$KEY_EVENT $_TMP_KEY $KEY_ENTER&amp;#34;&#10;adb $S $KEY_EVENT $_TMP_KEY $KEY_ENTER&#10;sleep 3&#10;&#10;echo &amp;#34;アカウント入力画面&amp;#34;&#10;_TMP_KEY=&amp;#34; &amp;#34;&#10;# タブを二回をして入力フォームを選択&#10;for i in `seq 0 2`&#10;do&#10; _TMP_KEY=&amp;#34;$_TMP_KEY $PRESS_KEY_TAB&amp;#34;&#10;done&#10;adb $S $KEY_EVENT $_TMP_KEY&#10;sleep 0.3&#10;&#10;# Googleアカウント名を入力&#10;adb $S shell input text $ID&#10;sleep 0.3&#10;adb $S $PRESS_KEY_ENTER&#10;adb $S $PRESS_KEY_ENTER&#10;&#10;sleep 3&#10;&#10;## パスワード入力画面&#10;adb $S shell input text $PW&#10;adb $S $PRESS_KEY_ENTER&#10;&#10;sleep 1&#10;&#10;echo &amp;#34;終了&amp;#34;&lt;/pre&gt;&#10;&lt;h2&gt;できなかった&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Bluetoothのオフ&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html?hl=ja"&gt;https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html?hl=ja&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;BluetoothはOnにしかできない&lt;/li&gt;&#10;&lt;li&gt;ならばAndroidアプリを作ってjava側からオフにしようとしたが、そもそもオフにするAPIがなかった。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2&gt;最終的にこれだけ自動化できた&lt;/h2&gt;&#10;&lt;p&gt;最初に開発者モードにするところだけ手動で設定する&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;開発者モードON&lt;/li&gt;&#10;&lt;li&gt;USBデバッグON&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;以下は自動化できた。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;アカウント設定&#10;&lt;ul&gt;&#10;&lt;li&gt;Googleアカウント設定&lt;/li&gt;&#10;&lt;li&gt;Oculusアカウント設定&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;もろもろ端末設定&#10;&lt;ul&gt;&#10;&lt;li&gt;wifi設定&lt;/li&gt;&#10;&lt;li&gt;いろいろオフ&#10;&lt;ul&gt;&#10;&lt;li&gt;Bluetooth オフ&lt;/li&gt;&#10;&lt;li&gt;端末同期 オフ&lt;/li&gt;&#10;&lt;li&gt;NFC オフ&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;通知をミュートモードにする&lt;/li&gt;&#10;&lt;li&gt;開発者モード&lt;/li&gt;&#10;&lt;li&gt;画面 スリープにするまで10分&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;インストール&#10;&lt;ul&gt;&#10;&lt;li&gt;つくったGearVRアプリのapk&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;GearVR 設定&#10;&lt;ul&gt;&#10;&lt;li&gt;GearVR開発者モードにする&#10;&lt;ul&gt;&#10;&lt;li&gt;[アプリケーション管理] - [GearVR Service] - [ストレージを管理] - [VR Service Ver]を7回タップ&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2&gt;最終的には費用対効果を考えよう&lt;/h2&gt;&#10;&lt;p&gt;Androidを2-3台しか設定しないのであれば手動設定のほうが早いですよね。&lt;/p&gt;&#10;&lt;p&gt;今回は数十台だったので、手作業ではミスが発生しがちなのと、自分のモチベーションが底をつくため自動化を選びました。&lt;/p&gt;&#10;&lt;h2&gt;参考&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="http://qiita.com/wf9a5m75/items/b05ed63654540a0bb791"&gt;ADB Shell メモ - Qiita&lt;/a&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;できることがザッと書いてある&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://developer.android.com/studio/command-line/adb.html#shellcommands"&gt;Android Debug Bridge | Android Studio&lt;/a&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;公式doc&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="http://andmem.blogspot.jp/2014/04/androidcommand.html"&gt;パソコンからAndroid端末を操作する時に使えるコマンド(ADBなど) | AndMem&lt;/a&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;コマンド解説&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="http://android-smart.com/2014/11/adb_input.html"&gt;【デバッグ接続】タッチやキー入力などの操作を自動化する「input」コマンド | あんどろいどスマート&lt;/a&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;具体的なinputコマンドの書きかた&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://techbooster.org/android/environment/694/"&gt;Android adbを使いこなす | TechBooster&lt;/a&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;PCと接続している端末の取得のしかた&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</content><link rel="enclosure" href="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20170703/20170703114705.jpg" type="image/jpeg" length="0"/><author><name>bascule-dev</name></author></entry><entry><title>流星放送局の流星演出と映像合成</title><link href="https://blog.bascule.co.jp/entry/2017/05/15/100000"/><id>hatenablog://entry/10328749687244202824</id><published>2017-05-15T10:00:00+09:00</published><updated>2017-05-15T01:00:22+09:00</updated><summary type="html">こんにちは、エンジニアの岩渕（@buccchi）です。 去年12月のふたご座流星群ピークの夜に放送された「流星放送局」では流星を感知できるシステム“Meteor Broadcaster”からの通知を受けて生放送画面にみんなの願い事をリアルタイムに合成して降らせるということをやってました。今回はこの演出部分について説明します。 構成 番組中はドラマとライブ（アーティストライブ、トーク）がありました。それぞれに対応するため2種類の構成を用意しました。 ドラマ時の機材構成 ドラマは5分程度のものが5話ありました。 事前に画角とカットが切り替わるタイミングがわかっているので、各カットに応じたパースや大…</summary><content type="html">&lt;p&gt;&#10;こんにちは、エンジニアの岩渕（&lt;a href="https://twitter.com/buccchi"&gt;@buccchi&lt;/a&gt;）です。&lt;/p&gt;&#10;&lt;p&gt;去年12月の&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%D5%A4%BF%A4%B4%BA%C2%CE%AE%C0%B1%B7%B2"&gt;ふたご座流星群&lt;/a&gt;ピークの夜に放送された「&lt;a href="https://www.bascule.co.jp/work/ryusei-tv/"&gt;流星放送局&lt;/a&gt;」では流星を感知できるシステム“Meteor Broadcaster”からの通知を受けて生放送画面にみんなの願い事をリアルタイムに合成して降らせるということをやってました。今回はこの演出部分について説明します。&lt;/p&gt;&#10;&lt;h1&gt;構成&lt;/h1&gt;&#10;&lt;p&gt;番組中はドラマとライブ（アーティストライブ、&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF"&gt;トーク&lt;/a&gt;）がありました。それぞれに対応するため2種類の構成を用意しました。&lt;/p&gt;&#10;&lt;h2&gt;ドラマ時の機材構成&lt;/h2&gt;&#10;&lt;p&gt;ドラマは5分程度のものが5話ありました。&lt;br/&gt;&#10;事前に画角とカットが切り替わるタイミングがわかっているので、各カットに応じたパースや大きさで流星を降らせることができる様に、1話ごとに決めカットを3つと、汎用的に使えるヒキ・ヨリ・ミドルのカットを3つ、それと非表示用のカットを1つの計7カットを用意してこれをドラマのタイムコードに合わせて切り替えていました。&lt;br/&gt;&#10;最終的なカット数は530超。&lt;br/&gt;&#10;また、参加者数や流星感知時のテロップ表示はAnimateで実装し、&lt;a href="http://nixus.jp/qumo2/"&gt;QUMO2&lt;/a&gt;というシステムで合成していました。&lt;/p&gt;&#10;&lt;h5&gt;&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/BS%A5%B8%A5%E3%A5%D1%A5%F3"&gt;BSジャパン&lt;/a&gt;（六本木）機材構成&lt;/h5&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20170509/20170509165230.png" alt="f:id:bascule-dev:20170509165230p:plain" title="f:id:bascule-dev:20170509165230p:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;h2&gt;ライブ時の機材構成&lt;/h2&gt;&#10;&lt;p&gt;生放送用のカメラ3台にUnityマシン3台を割り当てていました。&lt;/p&gt;&#10;&lt;h5&gt;&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%B2%CF%B8%FD%B8%D0"&gt;河口湖&lt;/a&gt;中継車機材構成&lt;/h5&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20170509/20170509165256.png" alt="f:id:bascule-dev:20170509165256p:plain" title="f:id:bascule-dev:20170509165256p:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;h1&gt;合成方法&lt;/h1&gt;&#10;&lt;p&gt;流星演出はUnityで実装。ブルーム発光しているのでこれを加算合成をすれば綺麗に表示できると思っていたのですが、加算合成はTV放送ではなかなか難しいらしく断念、代わりにフィルキー合成という方法で行いました。&lt;br/&gt;&#10;Unityの画面を単純にフィル映像として使うとどうしても半透明部分が黒ずんでしまうので、いくつかのパターンを試して、フィル映像は白ベタでキー映像をUnityで生成するという方法を選択しました。&lt;/p&gt;&#10;&lt;h4&gt;Unityの映像をBlackmagicへ送る&lt;/h4&gt;&#10;&lt;p&gt;Unityの映像は &lt;a href="https://github.com/Syphon/Unity3D/"&gt;SyphonUnityPlugin&lt;/a&gt;というのと&lt;a href="http://vdmx.vidvox.net/blog/black-syphon"&gt;Black Syphon&lt;/a&gt;というのを使ってBlackmagicへ送っていました。&lt;br/&gt;&#10;ただし&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;描画タイミングの同期が取れていないのかちょっとカクつく。&lt;/li&gt;&#10;&lt;li&gt;Unity側でカメラを切り替えると設定が外れる。&lt;/li&gt;&#10;&lt;li&gt;再設定後に同期がとれるまでノイズが流れる。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;という問題もあったので今後同じ様なことをやる場合には別の方法を探る必要があそうです。&lt;/p&gt;&#10;&lt;h1&gt;流星演出&lt;/h1&gt;&#10;&lt;p&gt;ライブのカメラ位置や画角が確定するのは放送当日で、そこから最終調整をしなければいけない状況だったため、落下範囲や速度をすばやく設定できる様にエディタ拡張を用意しておきました。&lt;/p&gt;&#10;&lt;h4&gt;流星落下アニメーション&lt;/h4&gt;&#10;&lt;p&gt;文字はDOTweenで1文字ずつ&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%E9"&gt;バラ&lt;/a&gt;&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%E9"&gt;バラ&lt;/a&gt;に制御。&lt;br/&gt;&#10;消滅時のパーティクルは、文字の最終ノードにエミッターを設定。&lt;br/&gt;&#10;地面を照らす光はライトだと重いので板ポリで表現。&lt;br/&gt;&#10;文字間を繋ぐ線はLineだと細い場合に潰れてしまうので、板ポリに左右に余白をもたせたテクスチャで。&lt;/p&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20170509/20170509165312.gif" alt="f:id:bascule-dev:20170509165312g:plain" title="f:id:bascule-dev:20170509165312g:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;h4&gt;流星の落下範囲やもろもろの設定&lt;/h4&gt;&#10;&lt;p&gt;以下のパラメータをエディタ上で変更できるようにしていました。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;落下エリア&lt;/li&gt;&#10;&lt;li&gt;文字が発生する距離&lt;/li&gt;&#10;&lt;li&gt;文字が静止する距離（画面内のいい感じの高さで静止させて文字が読めるように）&lt;/li&gt;&#10;&lt;li&gt;文字が静止後に滑る距離（静止後もゆっくりスクロールし続けることで長文も読めるように）&lt;/li&gt;&#10;&lt;li&gt;文字が静止後に滑る時間&lt;/li&gt;&#10;&lt;li&gt;進入角度&lt;/li&gt;&#10;&lt;li&gt;願いの文字サイズ&lt;/li&gt;&#10;&lt;li&gt;着地時の光のサイズ&lt;/li&gt;&#10;&lt;li&gt;衝突時のエフェクトの有無（落下地点が画面外の場合に処理を省略するため）&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;落下地点を完全なランダムにしてしまうと流星が重なりすぎて読めなくなったりするので Random.InitState(int seed) を適当なタイミングで叩いて各カットの初回の落下位置を固定していました。&lt;br/&gt;&#10;落下範囲を極端に狭めることで「ダルマの頭に落とす」みたいなことも可能になってます。&lt;/p&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20170509/20170509165338.png" alt="f:id:bascule-dev:20170509165338p:plain" title="f:id:bascule-dev:20170509165338p:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&#10;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20170509/20170509165327.png" alt="f:id:bascule-dev:20170509165327p:plain" title="f:id:bascule-dev:20170509165327p:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;h1&gt;パースマッチング&lt;/h1&gt;&#10;&lt;p&gt;パースは&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/Blender"&gt;Blender&lt;/a&gt;上で&lt;a href="https://github.com/stuffmatic/blam"&gt;BLAM&lt;/a&gt;というアドオンを使って合わせていました。&lt;br/&gt;&#10;ただし&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/Blender"&gt;Blender&lt;/a&gt;は右手座標系で水平画角、Unityは左手座標系で垂直画角という違いがあるのでこれを変換する必要があります。&lt;/p&gt;&#10;&lt;h4&gt;&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/Blender"&gt;Blender&lt;/a&gt;画角（水平）をUnity画角（垂直）に変換&lt;/h4&gt;&#10;&lt;pre class="code" data-lang="" data-unlink&gt;public Camera cam;&#10;public float blenderFov = 62.4f; // Blender側の視野角&#10;&#10;private void ConvertFovFromBlender(){&#10; float fov = 2f * Mathf.Atan(Mathf.Tan(blenderFov * Mathf.Deg2Rad / 2f) / cam.aspect);&#10; cam.fieldOfView = fov * Mathf.Rad2Deg;&#10;}&lt;/pre&gt;&#10;&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20170515/20170515234728.gif" alt="f:id:bascule-dev:20170515234728g:plain" title="f:id:bascule-dev:20170515234728g:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;h1&gt;ドラマ決めカット一覧&lt;/h1&gt;&#10;&lt;p&gt;ドラマ用に最終的に用意した決めカットは15種類。これ以外のカットは汎用カットからシーンに合ったものを割り当てていました。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%DE%A5%DB"&gt;スマホ&lt;/a&gt;&lt;br/&gt;&#10;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20170509/20170509165518.png" alt="f:id:bascule-dev:20170509165518p:plain" title="f:id:bascule-dev:20170509165518p:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/li&gt;&#10;&lt;li&gt;ミュージシャンの妹&lt;br/&gt;&#10;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20170509/20170509165527.png" alt="f:id:bascule-dev:20170509165527p:plain" title="f:id:bascule-dev:20170509165527p:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/li&gt;&#10;&lt;li&gt;オウム返し&lt;br/&gt;&#10;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20170509/20170509165534.png" alt="f:id:bascule-dev:20170509165534p:plain" title="f:id:bascule-dev:20170509165534p:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/li&gt;&#10;&lt;li&gt;あれから&lt;br/&gt;&#10;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20170509/20170509165542.png" alt="f:id:bascule-dev:20170509165542p:plain" title="f:id:bascule-dev:20170509165542p:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/li&gt;&#10;&lt;li&gt;風景が変わる&lt;br/&gt;&#10;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20170509/20170509165604.png" alt="f:id:bascule-dev:20170509165604p:plain" title="f:id:bascule-dev:20170509165604p:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h1&gt;おわりに&lt;/h1&gt;&#10;&lt;p&gt;流星という自然現象をキーにしているため、これだけ用意しても使われることのないカットが多くを占めてしまうのですが、&#10;それでも新しい体験を作るためにやる価値は十分あったと思ってます。&lt;/p&gt;&#10;</content><author><name>bascule-dev</name></author></entry><entry><title>Locustを利用したMQTT over WebSocketの性能テスト</title><link href="https://blog.bascule.co.jp/entry/2017/05/09/135920"/><id>hatenablog://entry/10328749687234961111</id><published>2017-05-09T13:59:20+09:00</published><updated>2017-05-09T04:59:20+09:00</updated><summary type="html">はじめまして。 バックエンドエンジニアの会津（@ido）です。 2016年に携わった案件「流星放送局」ではサイトを訪問している大量ユーザーにプッシュ通知を行う必要がありました。 Websocketサーバ実装といえばSocket.IOなどがありますが、昨今の流行りに乗って自前で開発はせずサーバレスでフルマネージメントなAWS IoTのMQTT over WebSocketを利用して実装しました。 疎通まではコンソールからAWS IoTの設定するだけの簡単なお仕事なのですが、大量のWebsocket接続へのプッシュ通知がどの程度遅延するかを計測するというなかなか骨の折れそうな性能テストをしなければ…</summary><content type="html">&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20170509/20170509135655.jpg" alt="f:id:bascule-dev:20170509135655j:plain" title="f:id:bascule-dev:20170509135655j:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;p&gt;はじめまして。&#10;バックエンドエンジニアの&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%B2%F1%C4%C5"&gt;会津&lt;/a&gt;（&lt;a href="https://twitter.com/ido?lang=ja"&gt;@ido&lt;/a&gt;）です。&lt;/p&gt;&#10;&lt;p&gt;2016年に携わった案件「&lt;a href="https://www.bascule.co.jp/work/ryusei-tv/"&gt;流星放送局&lt;/a&gt;」ではサイトを訪問している大量ユーザーにプッシュ通知を行う必要がありました。&lt;/p&gt;&#10;&lt;p&gt;Websocketサーバ実装といえば&lt;a href="https://socket.io/"&gt;Socket.IO&lt;/a&gt;などがありますが、昨今の流行りに乗って自前で開発はせずサーバレスでフルマネージメントな&lt;a href="https://aws.amazon.com/jp/iot-platform/how-it-works/"&gt;AWS IoT&lt;/a&gt;のMQTT over WebSocketを利用して実装しました。&lt;/p&gt;&#10;&lt;p&gt;疎通まではコンソールから&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS"&gt;AWS&lt;/a&gt; IoTの設定するだけの簡単なお仕事なのですが、大量のWebsocket接続へのプッシュ通知がどの程度遅延するかを計測するというなかなか骨の折れそうな性能テストをしなければなりません。&#10;ただ流石にこんなニッチな性能テストができるサービスは存在していなかったので、&lt;a href="http://locust.io/"&gt;Locust&lt;/a&gt;を利用してテストを行いました。&lt;/p&gt;&#10;&lt;h3&gt;なぜLocustなのか&lt;/h3&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;普段&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/Python"&gt;Python&lt;/a&gt;ばかり書いているのでテストシナリオは&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/Python"&gt;Python&lt;/a&gt;で書きたい、間違っても&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/XML"&gt;XML&lt;/a&gt;なんて書きたくない。&lt;/li&gt;&#10;&lt;li&gt;接続するWebsocketクライアント数を手軽に変更したい、スケーラビリティ大事。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;以上の2点からLocustを選びました。&lt;/p&gt;&#10;&lt;p&gt;ちなみにLocustは日本語訳でイナゴです。&#10;イナゴを大量発生させて操れるなんて気分は&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E2%A1%BC%A5%BB"&gt;モーセ&lt;/a&gt;ですね。&lt;/p&gt;&#10;&lt;h3&gt;テストを行う&lt;/h3&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;a href="https://www.ansible.com/"&gt;ANSIBLE&lt;/a&gt; + &lt;a href="https://www.packer.io/"&gt;Packer&lt;/a&gt;を使ってLocustのマスター・スレーブ2種類のAMI作成&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.terraform.io/"&gt;Terraform&lt;/a&gt;でLocust&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%B9%A5%BF"&gt;クラスタ&lt;/a&gt;ーを構築（今回スレーブは最大200&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9"&gt;インスタンス&lt;/a&gt;起動）&lt;/li&gt;&#10;&lt;li&gt;マスターのWebGUIから接続数を設定して実行&lt;/li&gt;&#10;&lt;li&gt;ローカルPCまたは&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS"&gt;AWS&lt;/a&gt; IoTコンソールからMQTT BrokerにPublish&lt;/li&gt;&#10;&lt;li&gt;マスターのWebGUIで性能確認&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;大まかに書くと以上の手順でテストを行いますが、構築などの詳細を書くと膨大になるため割愛。（リクエストあれば書くかも）&#10;テストシナリオとテストPublishを行うコードは以下のとおりです。&lt;/p&gt;&#10;&lt;h4&gt;locustfile.py&lt;/h4&gt;&#10;&lt;pre class="code lang-python" data-lang="python" data-unlink&gt;&lt;span class="synComment"&gt;# -*- coding: utf-8 -*-&lt;/span&gt;&#10;&lt;span class="synPreProc"&gt;from&lt;/span&gt; __future__ &lt;span class="synPreProc"&gt;import&lt;/span&gt; division, print_function, absolute_import&#10;&#10;&lt;span class="synPreProc"&gt;import&lt;/span&gt; json&#10;&lt;span class="synPreProc"&gt;import&lt;/span&gt; time&#10;&lt;span class="synPreProc"&gt;import&lt;/span&gt; uuid&#10;&#10;&lt;span class="synPreProc"&gt;from&lt;/span&gt; locust &lt;span class="synPreProc"&gt;import&lt;/span&gt; TaskSet, task, Locust&#10;&lt;span class="synPreProc"&gt;from&lt;/span&gt; locust.events &lt;span class="synPreProc"&gt;import&lt;/span&gt; request_success&#10;&#10;&lt;span class="synPreProc"&gt;from&lt;/span&gt; AWSIoTPythonSDK.MQTTLib &lt;span class="synPreProc"&gt;import&lt;/span&gt; AWSIoTMQTTClient&#10;&#10;CA_FILE_PATH = &lt;span class="synConstant"&gt;'./rootCA.pem'&lt;/span&gt;&#10;QoS = &lt;span class="synConstant"&gt;1&lt;/span&gt;&#10;AWS_ACCESS_KEY_ID = &lt;span class="synConstant"&gt;'XXXXXXXXX'&lt;/span&gt;&#10;AWS_SECRET_ACCESS_KEY = &lt;span class="synConstant"&gt;'XXXXXXXXX'&lt;/span&gt;&#10;ENDPOINT = &lt;span class="synConstant"&gt;'XXXXXXXXXX.iot.ap-northeast-1.amazonaws.com'&lt;/span&gt;&#10;TOPIC = &lt;span class="synConstant"&gt;'/ryusei/notification'&lt;/span&gt;&#10;&#10;&#10;&lt;span class="synStatement"&gt;def&lt;/span&gt; &lt;span class="synIdentifier"&gt;on_recive&lt;/span&gt;(client, userdata, message):&#10; payload = json.loads(message.payload)&#10; elapsed = time.time() - payload[&lt;span class="synConstant"&gt;'started_at'&lt;/span&gt;]&#10; request_success.fire(&#10; request_type=&lt;span class="synConstant"&gt;'Recive Notification'&lt;/span&gt;,&#10; name=message.topic,&#10; response_time=elapsed,&#10; response_length=&lt;span class="synIdentifier"&gt;len&lt;/span&gt;(message.payload),&#10; )&#10;&#10;&#10;&lt;span class="synStatement"&gt;def&lt;/span&gt; &lt;span class="synIdentifier"&gt;get_client&lt;/span&gt;():&#10; client = AWSIoTMQTTClient(clientID=uuid.uudi4().&lt;span class="synIdentifier"&gt;hex&lt;/span&gt;,&#10; useWebsocket=&lt;span class="synIdentifier"&gt;True&lt;/span&gt;)&#10; client.configureIAMCredentials(AWSAccessKeyID=AWS_ACCESS_KEY_ID,&#10; AWSSecretAccessKey=AWS_SECRET_ACCESS_KEY)&#10; client.configureCredentials(CAFilePath=CA_FILE_PATH)&#10; client.configureEndpoint(hostName=ENDPOINT,&#10; portNumber=&lt;span class="synConstant"&gt;443&lt;/span&gt;)&#10; client.configureOfflinePublishQueueing(-&lt;span class="synConstant"&gt;1&lt;/span&gt;)&#10; client.configureDrainingFrequency(&lt;span class="synConstant"&gt;2&lt;/span&gt;)&#10; client.configureConnectDisconnectTimeout(&lt;span class="synConstant"&gt;10&lt;/span&gt;)&#10; client.configureMQTTOperationTimeout(&lt;span class="synConstant"&gt;60&lt;/span&gt;)&#10; &lt;span class="synStatement"&gt;return&lt;/span&gt; client&#10;&#10;&#10;&lt;span class="synStatement"&gt;class&lt;/span&gt; &lt;span class="synIdentifier"&gt;AWSIoTTaskSet&lt;/span&gt;(TaskSet):&#10; bufsize = &lt;span class="synConstant"&gt;5000&lt;/span&gt;&#10;&#10; &lt;span class="synStatement"&gt;def&lt;/span&gt; &lt;span class="synIdentifier"&gt;on_start&lt;/span&gt;(self):&#10; client = get_client()&#10; client.connect()&#10; client.subscribe(TOPIC, QoS, on_recive)&#10;&#10; &lt;span class="synStatement"&gt;while&lt;/span&gt; &lt;span class="synIdentifier"&gt;True&lt;/span&gt;:&#10; time.sleep(&lt;span class="synConstant"&gt;1&lt;/span&gt;)&#10;&#10;&#10;&lt;span class="synStatement"&gt;class&lt;/span&gt; &lt;span class="synIdentifier"&gt;AWSIoTUser&lt;/span&gt;(Locust):&#10; task_set = AWSIoTTaskSet&#10; min_wait = &lt;span class="synConstant"&gt;100&lt;/span&gt;&#10; max_wait = &lt;span class="synConstant"&gt;100&lt;/span&gt;&#10;&lt;/pre&gt;&#10;&lt;h4&gt;publish.py&lt;/h4&gt;&#10;&lt;pre class="code lang-python" data-lang="python" data-unlink&gt;&lt;span class="synComment"&gt;# -*- coding: utf-8 -*-&lt;/span&gt;&#10;&lt;span class="synPreProc"&gt;from&lt;/span&gt; __future__ &lt;span class="synPreProc"&gt;import&lt;/span&gt; division, print_function, absolute_import&#10;&#10;&lt;span class="synPreProc"&gt;import&lt;/span&gt; json&#10;&lt;span class="synPreProc"&gt;import&lt;/span&gt; time&#10;&lt;span class="synPreProc"&gt;import&lt;/span&gt; uuid&#10;&#10;&lt;span class="synPreProc"&gt;from&lt;/span&gt; AWSIoTPythonSDK.MQTTLib &lt;span class="synPreProc"&gt;import&lt;/span&gt; AWSIoTMQTTClient&#10;&#10;CA_FILE_PATH = &lt;span class="synConstant"&gt;'./rootCA.pem'&lt;/span&gt;&#10;QoS = &lt;span class="synConstant"&gt;1&lt;/span&gt;&#10;AWS_ACCESS_KEY_ID = &lt;span class="synConstant"&gt;'XXXXXXXXX'&lt;/span&gt;&#10;AWS_SECRET_ACCESS_KEY = &lt;span class="synConstant"&gt;'XXXXXXXXX'&lt;/span&gt;&#10;ENDPOINT = &lt;span class="synConstant"&gt;'XXXXXXXXXX.iot.ap-northeast-1.amazonaws.com'&lt;/span&gt;&#10;TOPIC = &lt;span class="synConstant"&gt;'/ryusei/notification'&lt;/span&gt;&#10;&#10;&#10;&#10;&lt;span class="synStatement"&gt;def&lt;/span&gt; &lt;span class="synIdentifier"&gt;get_client&lt;/span&gt;():&#10; client = AWSIoTMQTTClient(clientID=uuid.uudi4().&lt;span class="synIdentifier"&gt;hex&lt;/span&gt;,&#10; useWebsocket=&lt;span class="synIdentifier"&gt;True&lt;/span&gt;)&#10; client.configureIAMCredentials(AWSAccessKeyID=AWS_ACCESS_KEY_ID,&#10; AWSSecretAccessKey=AWS_SECRET_ACCESS_KEY)&#10; client.configureCredentials(CAFilePath=CA_FILE_PATH)&#10; client.configureEndpoint(hostName=ENDPOINT,&#10; portNumber=&lt;span class="synConstant"&gt;443&lt;/span&gt;)&#10; client.configureOfflinePublishQueueing(-&lt;span class="synConstant"&gt;1&lt;/span&gt;)&#10; client.configureDrainingFrequency(&lt;span class="synConstant"&gt;2&lt;/span&gt;)&#10; client.configureConnectDisconnectTimeout(&lt;span class="synConstant"&gt;10&lt;/span&gt;)&#10; client.configureMQTTOperationTimeout(&lt;span class="synConstant"&gt;60&lt;/span&gt;)&#10; &lt;span class="synStatement"&gt;return&lt;/span&gt; client&#10;&#10;&lt;span class="synStatement"&gt;def&lt;/span&gt; &lt;span class="synIdentifier"&gt;main&lt;/span&gt;():&#10; client = get_client()&#10; client.connect()&#10; client.publish(TOPIC, json.dumps({&lt;span class="synConstant"&gt;'started_at'&lt;/span&gt;: time.time()}), &lt;span class="synConstant"&gt;1&lt;/span&gt;)&#10;&#10;&lt;span class="synStatement"&gt;if&lt;/span&gt; __name__ == &lt;span class="synConstant"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;:&#10; main()&#10;&lt;/pre&gt;&#10;&lt;h3&gt;おわりに&lt;/h3&gt;&#10;&lt;p&gt;Locustでの性能テストを繰り返すことで&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS"&gt;AWS&lt;/a&gt; IoTの特性を知ることができ、TVの放送本番までには満足できる結果を得られるまでチューニングを行いました。&#10;みなさんもLocustを使って強力かつ楽チンな性能テストライフをはじめてはいかがでしょうか。&lt;/p&gt;&#10;</content><link rel="enclosure" href="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20170509/20170509135655.jpg" type="image/jpeg" length="0"/><author><name>bascule-dev</name></author></entry><entry><title>マッチムーVJ</title><link href="https://blog.bascule.co.jp/entry/2017/04/26/195513"/><id>hatenablog://entry/10328749687240041747</id><published>2017-04-26T19:55:13+09:00</published><updated>2017-04-26T10:55:13+09:00</updated><summary type="html">こんにちわ。エンジニアのビジュアル担当、渡邊(@_nabe)です。 今回はバスキュールとは全く関係ないんですが、趣味で北千住デザインという名義でthreejsを使ってブラウザVJをやっていまして、今回はそのVJネタのひとつを説明します。 やったこと ARみたく、実写と動的なCGを組み合わせてVJをしたいと考えた（こういう合成をマッチムーブというらしい）。 ただARは現状では精度が低かったり、リアルタイムが前提の表現なので、映像作品とみたときイマイチな場合がある。そこで、実写映像をあらかじめ撮影して別のソフトで3Dトラッキングし、それにWebGLで描画された音に反応するアニメーションをオーバーレ…</summary><content type="html">&lt;p&gt;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20170426/20170426195330.png" alt="f:id:bascule-dev:20170426195330p:plain" title="f:id:bascule-dev:20170426195330p:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&#10;こんにちわ。エンジニアのビジュアル担当、渡邊(&lt;a href="https://twitter.com/_nabe"&gt;@_nabe&lt;/a&gt;)です。&lt;/p&gt;&#10;&lt;p&gt;今回はバスキュールとは全く関係ないんですが、趣味で&lt;a href="https://kitasenjudesign.com/"&gt;北千住デザイン&lt;/a&gt;という名義でthreejsを使ってブラウザVJをやっていまして、今回はそのVJネタのひとつを説明します。&lt;/p&gt;&#10;&lt;h2&gt;やったこと&lt;/h2&gt;&#10;&lt;p&gt;ARみたく、実写と動的なCGを組み合わせてVJをしたいと考えた（こういう合成を&lt;strong&gt;マッチムーブ&lt;/strong&gt;というらしい）。&#10;ただARは現状では精度が低かったり、リアルタイムが前提の表現なので、映像作品とみたときイマイチな場合がある。そこで、実写映像をあらかじめ撮影して別のソフトで3Dト&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%C3%A5%AD%A5%F3%A5%B0"&gt;ラッキング&lt;/a&gt;し、それに&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/WebGL"&gt;WebGL&lt;/a&gt;で描画された音に反応するアニメーションをオーバーレイさせるという方法をとってみた。静的な映像と動的な映像のハイブリットといった感じ。&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="http://68.media.tumblr.com/0cb993c0ab2b570c1d14072fc2c90e6e/tumblr_oe2lrhUVYP1t1xi2oo1_250.gif" alt="alt" /&gt;&lt;img src="http://68.media.tumblr.com/7a0426a20cc9286ad4ccd061839aa374/tumblr_oe1wwph1Iz1t1xi2oo1_250.gif" alt="alt" /&gt;&lt;img src="https://qiita-image-store.s3.amazonaws.com/0/17747/6026b508-f3fa-0d1b-74c0-5fcfcd326a90.gif" alt="ver6.gif" /&gt;&lt;/p&gt;&#10;&lt;p&gt;こんな感じで、背景はふつうのリニアな映像、手前の動いている物体は、音に合わせてリアルタイムに変形している。&lt;/p&gt;&#10;&lt;p&gt;どなたかが録画してくれていた動画↓&lt;/p&gt;&#10;&lt;blockquote class="twitter-tweet" data-lang="ja"&gt;&lt;p lang="ja" dir="ltr"&gt;キマイラ &lt;a href="https://twitter.com/hashtag/BRDG7?src=hash"&gt;#BRDG7&lt;/a&gt; @ 渋谷WWW X&lt;br&gt;&lt;br&gt;&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/DE%20DE%20MOUSE"&gt;DE DE MOUSE&lt;/a&gt;🐭 x HEXPIEXLS with 北千住デザイン✨&lt;br&gt;&lt;br&gt;これヤバすぎて🐼顔が凄くにやけた☺️☺️☺️ &lt;a href="https://t.co/775uMeOcBh"&gt;pic.twitter.com/775uMeOcBh&lt;/a&gt;&lt;/p&gt;&amp;mdash; KG (@keiji_307) &lt;a href="https://twitter.com/keiji_307/status/777558184818987008"&gt;2016年9月18日&lt;/a&gt;&lt;/blockquote&gt;&#10;&lt;h2&gt;ト&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%C3%A5%AD%A5%F3%A5%B0"&gt;ラッキング&lt;/a&gt;のやり方&lt;/h2&gt;&#10;&lt;p&gt;実写のト&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%C3%A5%AD%A5%F3%A5%B0"&gt;ラッキング&lt;/a&gt;にはCinema4d Studioを使った（会社にあったから）。やり方は&lt;a href="http://www.maxonjapan.jp/archives/297"&gt;この辺を見ればわかる&lt;/a&gt; 。ト&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%C3%A5%AD%A5%F3%A5%B0"&gt;ラッキング&lt;/a&gt;ができたら、&lt;a href="http://qiita.com/_nabe/items/9c1ff9ad47be7476571f"&gt;Cinema4DはPythonを使える機能がある&lt;/a&gt;ので、それを使い、毎フレーム、カメラの座標・回転（&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%A9%A1%BC%A5%BF%A5%CB%A5%AA%A5%F3"&gt;クォータニオン&lt;/a&gt;）・FOVを&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/json"&gt;json&lt;/a&gt;に書き出す。ただcinema4dとthreejsは座標系が違う（左手・右手）ので書き出す際注意が必要。&lt;/p&gt;&#10;&lt;p&gt;Cinema4dの&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/Python"&gt;Python&lt;/a&gt;のコードはこんな感じ&#10;&lt;a href="https://gist.github.com/kitasenjudesign/d739c6123e0b55bc77445d2db6f608d1"&gt;https://gist.github.com/kitasenjudesign/d739c6123e0b55bc77445d2db6f608d1&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;書き出した&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/JSON"&gt;JSON&lt;/a&gt;はこんな感じ&lt;/p&gt;&#10;&lt;pre class="code" data-lang="" data-unlink&gt; &amp;#34;frames&amp;#34;: [&#10; {&#10; &amp;#34;fov&amp;#34;: 32.70611613573759, &#10; &amp;#34;frame&amp;#34;: 0, &#10; &amp;#34;q&amp;#34;: [&#10; 0.01175325347189373, &#10; 0.9667209890999054, &#10; 0.04514531549377907, &#10; -0.25154381478281923&#10; ], &#10; &amp;#34;x&amp;#34;: -141.4472256273544, &#10; &amp;#34;y&amp;#34;: 68.24724022421263, &#10; &amp;#34;z&amp;#34;: -198.63611330979802&#10; },...&lt;/pre&gt;&#10;&lt;p&gt;これをthreejsに持っていき、映像の再生時間と合わせて、毎フレーム、PerspectiveCameraのパラメータを更新すれば良い。&lt;/p&gt;&#10;&lt;p&gt;※AfterEffectsでも同様のことができるようです。他の3Dソフトでもできると思う。&#10;&lt;a href="https://medium.com/@Jam3/mtchmv-a54624f6232#.jwdrxnan1"&gt;https://medium.com/@Jam3/mtchmv-a54624f6232#.jwdrxnan1&lt;/a&gt;&lt;/p&gt;&#10;&lt;h2&gt;Video再生&lt;/h2&gt;&#10;&lt;p&gt;&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/WebGL"&gt;WebGL&lt;/a&gt;のキャンバスの背景を透明にして下にVideoタグを敷くという方法もあるが、今回はポストプロセスもかけたかったのでVideoを&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/Canvas"&gt;Canvas&lt;/a&gt;にキャプチャしてthreejsの中の板ポリに貼り付けた。（やり方はググれば出てくる）&#10;その際、板ポリはカメラに関係なくずっと正面を向いていてほしい。毎フレーム、板ポリをLookAtするという手段もあるが、めんどいので、以下のようにシェーダーを書いた。&lt;/p&gt;&#10;&lt;pre class="code" data-lang="" data-unlink&gt;geo = new THREE.PlaneBufferGeometry(2, 2, 1, 1);&#10;mat = new THREE.ShaderMaterial({&#10; vertexShader: _vertexShader,&#10; fragmentShader: _fragmentShader,&#10; uniforms: {&#10; texture: { type: &amp;#39;t&amp;#39;, value: _texture } &#10; }&#10;});&#10;mesh = new THREE.Mesh(geo,mat);&#10;&lt;/pre&gt;&#10;&lt;pre class="code" data-lang="" data-unlink&gt;varying vec2 vUv;&#10;void main()&#10;{&#10; vUv = uv;&#10; vec4 hoge = vec4(position, 1.0);//マトリックス計算しない&#10; hoge.z = 1.0;//最背面のz=1に。&#10; gl_Position = hoge;&#10;} &lt;/pre&gt;&#10;&lt;pre class="code" data-lang="" data-unlink&gt;uniform sampler2D texture;&#10;varying vec2 vUv; &#10;void main()&#10;{&#10; //テクスチャを書き出してるだけ&#10; gl_FragColor = texture2D(texture, vUv);&#10;} &lt;/pre&gt;&#10;&lt;h2&gt;影&lt;/h2&gt;&#10;&lt;p&gt;影もつけたい。ShadowMaterialっていうのを使うと透明なPlaneに影だけ落としてくれる。&#10;&lt;a href="http://stackoverflow.com/questions/35710130/shadow-catcher-in-three-js-shadow-on-transparent-material"&gt;http://stackoverflow.com/questions/35710130/shadow-catcher-in-three-js-shadow-on-transparent-material&lt;/a&gt;&lt;/p&gt;&#10;&lt;h2&gt;&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%EA%A5%C3%A5%D4%A5%F3%A5%B0"&gt;クリッピング&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;地面から飛び出してくるような演出もしたい。3Dオブジェクトを&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%EA%A5%C3%A5%D4%A5%F3%A5%B0"&gt;クリッピング&lt;/a&gt;せねばならない。シェーダーを自分で書いても良い（たとえばy&amp;lt;0ならdiscardするとか）。けれど、めんどうなので、これもthreejsの&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%EA%A5%C3%A5%D4%A5%F3%A5%B0"&gt;クリッピング&lt;/a&gt;機能を使う。&#10;&lt;a href="https://threejs.org/examples/?q=clipp#webgl_clipping"&gt;https://threejs.org/examples/?q=clipp#webgl_clipping&lt;/a&gt;&lt;/p&gt;&#10;&lt;h2&gt;おわりに&lt;/h2&gt;&#10;&lt;p&gt;世にある大概のマッチムーブは実写とCGの合成が違和感ないように、なじむ方向に作るのだけれど、今回のように実写としょぼいCGを組み合わせてみたら、それはそれで違和感が生まれて面白い。3Dト&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%C3%A5%AD%A5%F3%A5%B0"&gt;ラッキング&lt;/a&gt;の精度はそれなりに必要だと思うけど。&lt;/p&gt;&#10;&lt;p&gt;今後もよいネタあれば、発信していきます。&lt;/p&gt;&#10;&lt;p&gt;あと今エンジニアを募集してますーー。&#10;&lt;iframe src="//hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.bascule.co.jp%2Fjoin-us%2F" title="JOIN US - Bascule Inc." class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"&gt;&lt;/iframe&gt;&lt;cite class="hatena-citation"&gt;&lt;a href="https://www.bascule.co.jp/join-us/"&gt;www.bascule.co.jp&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&#10;</content><link rel="enclosure" href="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20170426/20170426195330.png" type="image/png" length="0"/><author><name>bascule-dev</name></author></entry><entry><title>インスタレーションを支えるバックエンドの話</title><link href="https://blog.bascule.co.jp/entry/installation-backend"/><id>hatenablog://entry/10328749687234170866</id><published>2017-04-11T10:00:00+09:00</published><updated>2017-04-11T01:00:22+09:00</updated><summary type="html">こんにちは、丸山です。 12月に開催されていたCOFFEE MOMENT ENSEMBLE（以下、CME）や願いの森というインスタレーションイベントでバックエンドを担当しました。 CME動画 願いの森動画 この記事では『インスタレーションを支えるバックエンドの話』というテーマでCMEを具体例としてお話します。 構成 全体の構成は以下のようになっています。 スマホアプリ→Nestle内のAppサーバー→CME Appサーバー→店舗内機器 という流れで"コーヒーを淹れた"というイベントが伝播されます。 CME Appサーバー以降がバスキュールの担当範囲になります。 Appサーバー Appサーバーは…</summary><content type="html">&lt;p&gt;こんにちは、丸山です。&lt;br/&gt;&#10;12月に開催されていた&lt;a href="https://www.bascule.co.jp/work/nescafe-cme/"&gt;COFFEE MOMENT ENSEMBLE&lt;/a&gt;（以下、CME）や&lt;a href="https://www.bascule.co.jp/work/negainomori/"&gt;願いの森&lt;/a&gt;という&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%EC%A1%BC%A5%B7%A5%E7%A5%F3"&gt;インスタレーション&lt;/a&gt;イベントでバックエンドを担当しました。&lt;/p&gt;&#10;&lt;p&gt;CME動画&lt;/p&gt;&#10;&lt;iframe src="https://player.vimeo.com/video/202144696" width="640" height="360" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen&gt;&lt;/iframe&gt;&#10;&lt;p&gt;願いの森動画&lt;/p&gt;&#10;&lt;iframe src="https://player.vimeo.com/video/212235610" width="640" height="360" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen&gt;&lt;/iframe&gt;&#10;&lt;p&gt;この記事では『&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%EC%A1%BC%A5%B7%A5%E7%A5%F3"&gt;インスタレーション&lt;/a&gt;を支えるバックエンドの話』というテーマでCMEを具体例としてお話します。&lt;/p&gt;&#10;&lt;h1&gt;構成&lt;/h1&gt;&#10;&lt;p&gt;全体の構成は以下のようになっています。&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://blog.bascule.co.jp/images/bascule-dev.github.io/blog-assets/cme/cme-arch.svg" alt="cme arch"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%DE%A5%DB"&gt;スマホ&lt;/a&gt;アプリ→&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/Nestle"&gt;Nestle&lt;/a&gt;内のAppサーバー→CME Appサーバー→店舗内機器&lt;br/&gt;&#10;という流れで"コーヒーを淹れた"というイベントが伝播されます。&lt;br/&gt;&#10;CME Appサーバー以降がバスキュールの担当範囲になります。&lt;/p&gt;&#10;&lt;h1&gt;Appサーバー&lt;/h1&gt;&#10;&lt;p&gt;Appサーバーは主にWeb &lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/API"&gt;API&lt;/a&gt;とWebSocketを実装しています。&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://blog.bascule.co.jp/images/bascule-dev.github.io/blog-assets/cme/cme-arch-app.svg"&gt;&lt;/p&gt;&#10;&lt;p&gt;&amp;ldquo;コーヒーを淹れた"というアクションに対してイベント発火とメインビジョン用の統計処理を行っています。&#10;統計は&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%C5%D4%C6%BB"&gt;都道&lt;/a&gt;府県別ランキング、時間帯別の小計、人気のメニューなどがあります。&lt;/p&gt;&#10;&lt;p&gt;&#10;&lt;span itemscope itemtype="http://schema.org/Photograph"&gt;&lt;img src="https://blog.bascule.co.jp/images/cdn-ak.f.st-hatena.com/images/fotolife/b/bascule-dev/20170407/20170407192702.jpg" alt="f:id:bascule-dev:20170407192702j:plain" title="f:id:bascule-dev:20170407192702j:plain" class="hatena-fotolife" itemprop="image"&gt;&lt;/span&gt;&lt;/p&gt;&#10;&lt;p&gt;インフラは素朴にALB, EC2, RDS, ElastiCache(Redis),S3という構成で、&lt;br/&gt;&#10;Webアプリは主に&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails"&gt;Rails&lt;/a&gt;を、WebSocketまわりでNode.jsを使っています。&lt;/p&gt;&#10;&lt;h3&gt;クリエイティブ系における&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails"&gt;Rails&lt;/a&gt;最高&lt;/h3&gt;&#10;&lt;p&gt;私は負荷、外部要因などを気にしなくていい案件ではデフォルトで&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails"&gt;Rails&lt;/a&gt;を使っています。&lt;br/&gt;&#10;&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails"&gt;Rails&lt;/a&gt;というと&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/Web%A5%B5%A1%BC%A5%D3%A5%B9"&gt;Webサービス&lt;/a&gt;で使われているイメージがありますが、&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%EC%A1%BC%A5%B7%A5%E7%A5%F3"&gt;インスタレーション&lt;/a&gt;を含めクリエイティブ系において&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails"&gt;Rails&lt;/a&gt;（と周辺gem）は最高に使いやすくて、&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;クリエイティブ系は基本的にアプリを使い捨てていくのでセットアップ機会が多い。&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails"&gt;Rails&lt;/a&gt;はセットアップ時間が短くて最高。&lt;/li&gt;&#10;&lt;li&gt;画像や動画などのメディアを扱うことが多く、基本的にS3にあげる。&lt;a href="https://github.com/carrierwaveuploader/carrierwave"&gt;carrierwave&lt;/a&gt;最高。&lt;/li&gt;&#10;&lt;li&gt;ユーザー用に画像や動画の動的生成をよくするのでジョブキューをよく使う。&lt;a href="https://github.com/mperham/sidekiq"&gt;sidekiq&lt;/a&gt;よくできてて最高。&lt;/li&gt;&#10;&lt;li&gt;管理画面が基本的に必要。これも使い捨てなので手間をかけたくない。&lt;a href="https://github.com/activeadmin/activeadmin"&gt;ActiveAdmin&lt;/a&gt;楽さとカスタマイズ性のバランスが最高。&lt;/li&gt;&#10;&lt;li&gt;いつも&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/JSON"&gt;JSON&lt;/a&gt; &lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/API"&gt;API&lt;/a&gt;を組むがフロントエンドのメンバー用にドキュメントが必要。これも使い捨てなので手間をかけたくない。&lt;a href="https://github.com/ruby-grape/grape-swagger"&gt;grape-swagger&lt;/a&gt;&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/API"&gt;API&lt;/a&gt;実装がそのままドキュメントにつながって楽だしドキュメント更新忘れがち問題も起きにくくて最高。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;ということで、最高です。&lt;/p&gt;&#10;&lt;p&gt;クリエイティブ系は常に新しいことをやっていきたいという意識なのでそこに集中したくて &lt;br/&gt;&#10;それ以外はとにかく楽をしたい、というところで&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails"&gt;Rails&lt;/a&gt;と周辺gemは熟れてるし強いなと感じます。&lt;/p&gt;&#10;&lt;h1&gt;現場ハブ&lt;/h1&gt;&#10;&lt;p&gt;CME Appサーバーと店舗内の各機器をつなぐ、中継であり中央指令的なPCを現場ハブと呼んでいます。&#10;現場ハブはNode.jsで実装しています。&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://blog.bascule.co.jp/images/bascule-dev.github.io/blog-assets/cme/cme-arch-genbahub.svg"&gt;&lt;/p&gt;&#10;&lt;p&gt;Appサーバーに対してはWebSocketのクライアントとして、店舗内の各機器に対しては適した&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%C8%A5%B3%A5%EB"&gt;プロトコル&lt;/a&gt;に応じてWebSocket、OSC、&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/UDP"&gt;UDP&lt;/a&gt;で通信しています。&lt;br/&gt;&#10;現場ハブの特徴としてFSMをベースに実装している点があります。&lt;br/&gt;&#10;イベント系ではよくFSMを使っているのですが、割とおすすめなのでここではFSMに絞って紹介します。&lt;/p&gt;&#10;&lt;h3&gt;状態遷移とイベント発火の多い&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%EC%A1%BC%A5%B7%A5%E7%A5%F3"&gt;インスタレーション&lt;/a&gt;にFSM&lt;/h3&gt;&#10;&lt;p&gt;FSMで&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B0%A5%B0%A4%EB"&gt;ググる&lt;/a&gt;と&lt;a href="https://ja.wikipedia.org/wiki/%E7%A9%BA%E9%A3%9B%E3%81%B6%E3%82%B9%E3%83%91%E3%82%B2%E3%83%83%E3%83%86%E3%82%A3%E3%83%BB%E3%83%A2%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%BC%E6%95%99"&gt;空飛ぶスパゲッティ・モンスター教&lt;/a&gt;がトップに出てきますが、そちらではなく&lt;a href="https://ja.wikipedia.org/wiki/%E6%9C%89%E9%99%90%E3%82%AA%E3%83%BC%E3%83%88%E3%83%9E%E3%83%88%E3%83%B3"&gt;有限オートマトン&lt;/a&gt; の方です。&#10;&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/wikipedia"&gt;wikipedia&lt;/a&gt;にはいろいろ小難しいことが書いてありますがコードを見た方が早いかと思います。&lt;br/&gt;&#10;&lt;a href="https://www.npmjs.com/search?q=fsm"&gt;Node.jsにもいろいろFSMの実装&lt;/a&gt;がありますが&lt;a href="http://machina-js.org/"&gt;Machina.js&lt;/a&gt;が個人的には好みでよく使っています。&lt;br/&gt;&#10;サイトに載ってる車の例のように、FSMを使うと状態遷移と各状態において操作をしたときの処理をキレイに分けることができます。&lt;/p&gt;&#10;&lt;p&gt;今回の案件では&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;default - 定常状態&lt;/li&gt;&#10;&lt;li&gt;dispStats - 統計表示状態&lt;/li&gt;&#10;&lt;li&gt;performing - 演奏状態&lt;/li&gt;&#10;&lt;li&gt;suspend - 緊急時の中断状態&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;という4つの状態を定義しました。&lt;br/&gt;&#10;CMEでは例えば&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;演奏開始命令がサーバーから飛んできたときに&#10;&lt;ul&gt;&#10;&lt;li&gt;定常状態なら演奏を開始する&lt;/li&gt;&#10;&lt;li&gt;統計表示状態なら表示が終わるまで待機して終わってから演奏開始&lt;/li&gt;&#10;&lt;li&gt;演奏状態なら何かがおかしいのでエラーログに記録する（演奏は&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%C2%D4%A4%C1%B9%D4%CE%F3"&gt;待ち行列&lt;/a&gt;で管理しているのでキュー処理が間違っているなど）&lt;/li&gt;&#10;&lt;li&gt;中断状態なら中断が解除されるまで待機&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D4%A5%BF%A5%B4%A5%E9%C1%F5%C3%D6"&gt;ピタゴラ装置&lt;/a&gt;から演奏完了イベントが飛んできたときに&#10;&lt;ul&gt;&#10;&lt;li&gt;定常状態なら何かがおかしいのでエラーログに記録する（演奏を&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BF%A5%A4%A5%E0%A5%A2%A5%A6%A5%C8"&gt;タイムアウト&lt;/a&gt;して定常状態に強制的に戻したあとに飛んできたなど）&lt;/li&gt;&#10;&lt;li&gt;統計表示状態も同上&lt;/li&gt;&#10;&lt;li&gt;演奏状態なら状態を定常状態に戻す&lt;/li&gt;&#10;&lt;li&gt;中断状態なら無視する&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;といったようなことをしていて、これをif文で書いていくと死ねます。（↑では2つの処理を例にしているけど、これが5〜20個くらいある）&lt;br/&gt;&#10;&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%EC%A1%BC%A5%B7%A5%E7%A5%F3"&gt;インスタレーション&lt;/a&gt;系だとだいたいこんな感じになるんじゃないかと思います。&lt;/p&gt;&#10;&lt;p&gt;FSMでこれを記述するとif文で書くときと構造が逆になる（状態をインターフェース、各状態をその実装クラス、各処理がメソッドになって&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DD%A5%EA%A5%E2%A1%BC%A5%D5%A5%A3%A5%BA%A5%E0"&gt;ポリモーフィズム&lt;/a&gt;するような感じ）ため、すっきり書けるし分岐し忘れのようなことも防ぐことができます。&lt;/p&gt;&#10;&lt;p&gt;分岐し忘れについてはMachina.jsだと&lt;/p&gt;&#10;&lt;pre class="code lang-javascript" data-lang="javascript" data-unlink&gt;&lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synComment"&gt;//...&lt;/span&gt;&#10; performing: &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; &lt;span class="synConstant"&gt;'*'&lt;/span&gt;: &lt;span class="synIdentifier"&gt;function&lt;/span&gt; (eventName) &lt;span class="synIdentifier"&gt;{&lt;/span&gt;&#10; logger.error(&lt;span class="synConstant"&gt;'nohandler'&lt;/span&gt;, eventName, &lt;span class="synIdentifier"&gt;this&lt;/span&gt;.state);&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10; &lt;span class="synIdentifier"&gt;}&lt;/span&gt;,&#10; &lt;span class="synComment"&gt;//...&lt;/span&gt;&#10;&lt;span class="synIdentifier"&gt;}&lt;/span&gt;&#10;&lt;/pre&gt;&#10;&lt;p&gt;のように&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EF%A5%A4%A5%EB%A5%C9%A5%AB%A1%BC%A5%C9"&gt;ワイルドカード&lt;/a&gt;で処理を定義しておくと未実装の場合のハンドリングをすることもできます。&lt;/p&gt;&#10;&lt;h1&gt;まとめ&lt;/h1&gt;&#10;&lt;p&gt;&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%EC%A1%BC%A5%B7%A5%E7%A5%F3"&gt;インスタレーション&lt;/a&gt;を支えるバックエンドでは、新しい体験づくりを支えるスピード感と安定感を心がけています。&lt;br/&gt;&#10;バスキュールでは&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%EC%A1%BC%A5%B7%A5%E7%A5%F3"&gt;インスタレーション&lt;/a&gt;イベントに力を入れており、フロントエンド、バックエンド、デ&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%B9"&gt;バイス&lt;/a&gt;など問わず&lt;a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%EC%A1%BC%A5%B7%A5%E7%A5%F3"&gt;インスタレーション&lt;/a&gt;したい仲間を募集しています。&lt;/p&gt;&#10;</content><author><name>bascule-dev</name></author></entry></feed>