マーターポートSDKの2021年のチュートリアル

# Matterport SDK 2021 Tutorial ([English](https://qiita.com/loic-meister-guild/items/8cf015969d9fbddc2160))
まず、Matterport アプリのディレクトリを作成します：

```
mkdir matterport-test
cd matterport-test
```
次に、簡単なWebサーバーを設定することから始めましょう　．．．
## ウェブサーバー
__Node.js__で__Express__ウェブサーバーを使用してページを提供します。
### Expressのインストール
まず、空のプロジェクトの「package.json」ファイルを初期化しましょう：

```
npm init -y
```
次に、Express の最新の安定版をインストールします：

```
npm install express
```
### ウェブサーバーの作成
まず、ウェブサーバー用のJavaScriptファイルを作成します：

```
touch server.js
```
次に、ファイルでウェブサーバーを設定します：

```JavaScript:server.js
const express = require('express');
const app = express();
```
HTML、CSS、JavaScript、画像などの静的アセットを`public`ディレクトリから提供するように__Express__を設定します：

```JavaScript:server.js
const express = require('express');
const path = require('path');
const app = express();
app.use(express.static(path.join(__dirname, 'public')))
```
`public`ディレクトリを作成します：

```
mkdir public
cd public
```
__Matterport__アプリケーションのHTML、CSS、JavaScriptを追加する空のファイルを作成します：

```
touch app.js
touch index.html
touch styles.css
cd ..
```
アプリのHTMLファイルを提供するルートを作成します：

```JavaScript:server.js
// ...
app.get('/', (req, res) => {
    res.sendFile(`${__dirname}/public/index.html`);
});
```
最後に、ポート「8000」にリスナーを設定して__Express__ウェブサーバーを起動しましょう：

```JavaScript:server.js
// ...
app.listen(8000, () => {
    console.log('Application listening on port 8000!');
});
```
### ウェブサーバーの起動
ウェブサーバーを起動するには、次のコマンドを実行します：

```
node server.js
```
これで「 http://localhost:8000 」のブラウザーでアプリにアクセスできます。
## [Matterport SDK for Embeds](https://matterport.github.io/showcase-sdk/sdk_home.html)
__Matterport__のウェブサイトには、実際に補完的な[2つのSDK](https://support.matterport.com/hc/en-us/articles/360061477754)がリストされています： 

- [Matterport SDK for Embeds](https://matterport.github.io/showcase-sdk/sdk_home.html)：基本的な__マーターポートの3D Showcase SDK__を使用すると、Web アプリケーションは、エンド・ユーザーの位置と__3Dショーケース__での表示内容を変更するアクションを実行し、ユーザーの位置とユーザーが表示または実行していることに関するイベントをリッスンし、適切に反応できます。
- [Matterport SDK Bundle](https://matterport.github.io/showcase-sdk/sdk_home.html)：組み込み用の__SDK for embeds__の拡張で、マーターポート・モデルとのサードパーティとの緊密な統合と、3Dエンジン、レンダラー、シーン グラフなどへの直接アクセスのためのフレームワークを提供します。

第1のアプリを作成するには、（無料の）サンドボックス・アクセスで埋め込み用の__SDK for Embeds__を使用します　．．．
### 無料利用枠
無料のサブスクリプション プランでは、サンドボックス モードの開発者ツールに制限付きでアクセスできます：

- SDKのキーは`localhost`でのみ使用できます。他のドメインでは使用できません。
- APIのトークンはデモ・モデルに対してのみ使用でき、ユーザーが作成したモデルに対しては使用できません。

それでは、第1のアプリを作成しましょう。
### 第1のアプリ：基礎
まず、基本的な__Matterport__アプリを作成しましょう　．．．
##### HTML
まず、アプリの最小限のページを作成しましょう：

```public/index.html
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0, shrink-to-fit=no">
        <meta name="mobile-web-app-capable" content="yes">
        <meta name="apple-mobile-web-app-capable" content="yes" />
        <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
        <title>Matterport Test</title>
        <script src="/app.js" defer></script>
        <link rel="stylesheet" href="/styles.css">
    </head>
    <body>
    </body>
</html>
```
次に、`<head>`タグにJavascriptライブラリを含めて、__3D Showcase SDK__をWebアプリケーションに追加します：

```public/index.html
<html lang="en">
    <head>
        ...
        <script src='https://static.matterport.com/showcase-sdk/2.0.1-0-g64e7e88/sdk.js'></script>
        <script src="/app.js" defer></script>
        <link rel="stylesheet" href="/styles.css">
    </head>
    ...
```
最後に、`<iframe>`タグを追加して、ウェブページにマーターポート・スペースを埋め込みます：

```public/index.html
...
    <body>
        <iframe id="showcase" width="960" height="480" title="test" frameBorder="0" allowFullScreen allow="vr"></iframe>
    </body>
</html>
```
`<iframe>`の属性を見てみましょう：

- `frameBorder="0"`：`<iframe>`の周囲に境界線が描画されないようにします。
- `allowFullScreen`：__フルスクリーンモード__ボタンを表示します。
- `allow="vr"`：いくつかのコード・サンプルで見つかりましたが、何もしていないようです。
- `allow="xr-spatial-tracking"`：[__xr immersive mode__](https://matterport.github.io/showcase-sdk/sdkbundle_tutorials_using_xr.html)を有効にします（SDK Bundleだけ）。

##### JavaScript
次は、アプリケーションのJavaScriptコードで：

- `DOMContentLoaded`のイベントまで待ちます。
- `<iframe>`のマーターポート・スペースのソースを設定します。
- マーターポート・スペースを含む`<iframe>`が読み込まれるまで待ちます。
- SDKをマーターポート・スペースに接続します。

```public/app.js
"use strict";

const sdkKey = "aaaaXaaaXaaaXaaaaXaaaXaaa"
const modelSid = "SxQL3iGyoDo";
const params = `m=${modelSid}&hr=0&play=1&qs=1`;

var iframe = document.getElementById('showcase');

document.addEventListener("DOMContentLoaded", () => {
    iframe.setAttribute('src', `https://my.matterport.com/show/?${params}`);
    iframe.addEventListener('load', () => showcaseLoader(iframe));
});

function showcaseLoader(iframe){
    try{
        window.MP_SDK.connect(iframe, sdkKey, '3.10')
            .then(loadedShowcaseHandler)
            .catch(console.error);
    } catch(e){
        console.error(e);
    }
}

function loadedShowcaseHandler(mpSdk){
    // ...
}
```
`params`内のURLパラメータを見てみましょう：

- `applicationKey=aaaaXaaaXaaaXaaaaXaaaXaaa`：[マーターポート・アカウント → 設定 → 開発者ツール、SDKのキー管理](https://my.matterport.com/settings/account/devtools)で取得したアプリケーションのSDKのキー。
- `hr=0`：画面の下部に__highlight reel__を表示する__open highlights__ボタンを非表示にします。
- `log=0`：ロギングを無効にします？
- `m=SxQL3iGyoDo`：表示するモデルのSID。
- `play=1`：1に設定すると、ページの読み込み時に`<iframe>`が自動的に読み込まれます。
- `qs=1`：？

### アクション
3Dショーケースで実行するアクションを宣言するには、次のように宣言します：

- アクションが成功した場合に何かを行うためのコールバック：

```JavaScript
function successCallback(message) { console.log(message); }
```
- アクションが失敗した場合に何かを行うためのコールバック：

```JavaScript
function errorCallback(error) { console.error(error); }
```
- 必要な引数を指定してアクションを呼び出します：

```JavaScript
mpSdk.<moduleName>.<actionName>(<actionArguments>).then(successCallback)
.catch(errorCallback);
```
__アクション__は各モジュールの[リファレンス。ドキュメント](https://matterport.github.io/showcase-sdk/sdk_reference_summary)の__関数__セクションにリストされています。
##### 例：モデルへのMattertagの追加
ラベル、説明、ステム、および画像を使用して[Mattertagを作成する](https://matterport.github.io/showcase-sdk/sdk_creating_mattertags.html)には、次のオブジェクトを宣言します：

```JavaScript
var myMattertag = {
    label: 'A Tool',
    description: 'A cute little vacuum cleaner!',
    media: {
        type: mpSdk.Mattertag.MediaType.PHOTO,
        src: 'https://media.4rgos.it/i/Argos/8086071_R_Z001A',
    },
    anchorPosition: { x: -3.156, y: 0.363, z: 3.082 },
    stemVector: { x: -0.822, y: -0.041, z: 0.568 }
};
```
⚠️ 画像や動画のメディア ソースとしてローカル URL を使用することはできません！
※ [`Mattertag`](https://matterport.github.io/showcase-sdk/docs/sdk/reference/current/modules/mattertag.html)のアンカー位置、stemVector、floorId を見つけるには、[Mattertagを作成する](https://matterport.github.io/showcase-sdk/sdk_creating_mattertags.html)では[Intersection Inspector Tool](https://matterport.github.io/showcase-sdk/sdk_intersection_inspector)を使用することをお勧めしますが、[__Transient Tags Editor__](https://matterport.github.io/showcase-sdk/sdk_transient_tags_editor.html)の方がはるかに優れた使いやすいツールです！
※ 画像またはビデオ メディアを表示するには、__Matterport__は[embed.ly](https://embed.ly)を使用します。メディア ソースとして設定する前に、[embedly exploreの表示ツール](https://embed.ly/docs/explore/display/resize)で画像またはビデオの URL を確認することをお勧めします。

次に、[`Mattertag`](https://matterport.github.io/showcase-sdk/docs/sdk/reference/current/modules/mattertag.html)モジュールから[`add`](https://matterport.github.io/showcase-sdk/docs/sdk/reference/current/modules/mattertag.html#add)関数を呼び出します；

```JavaScript
mpSdk.Mattertag.add(myMattertag)
    .then(function(mattertagId) {
        console.log(mattertagId);
        // ...
    })
    .catch(function(error) {
        console.error(error);
        // ...
    });
```
### イベント
3Dショーケースからのイベントをリッスンするには、グローバル[`on`](https://matterport.github.io/showcase-sdk/docs/sdk/reference/current/index.html#on)関数を使用します。

```JavaScript
showcase.on(<eventName>, function (<stateArguments>) {
    // what to do when this event happens
});
```
※ 3Dショーケースからのイベントのリッスンを停止するには、グローバル[`off`](https://matterport.github.io/showcase-sdk/docs/sdk/reference/current/index.html#off)関数を使用します。

__イベント__は各モジュールの[リファレンス。ドキュメント](https://matterport.github.io/showcase-sdk/sdk_reference_summary)の__列挙__セクションにリストされています。
##### 例：Mattertagでのclickイベントの検出
[`mpSdk.Mattertag.Event.CLICK`](https://matterport.github.io/showcase-sdk/docs/sdk/reference/current/enums/mattertag.event.html#click)引数を指定してグローバル[`on`](https://matterport.github.io/showcase-sdk/docs/sdk/reference/current/index.html#on)関数を呼び出し、イベント ハンドラー コールバックの引数を確認します：

```JavaScript
mpSdk.on(mpSdk.Mattertag.Event.CLICK,
    function (tagSid) {
        console.log('Mattertag ' + tagSid + ' was selected');
    }
);
```
## カスタマイズ
このセクションでは、__マーターポートSDK__のインタラクティブ性を強化する方法について説明します。
### iframeでclickイベントの傍受
ユーザーが`<iframe>`をクリックすると、現在のウィンドウで`blur`イベントをトリガーする __フォカス__ が取得されます。

⚠️ 最初の`click`イベントの後、フォーカスを`window`に戻す必要があることに注意してください。そうしないと、2番目の`click`イベントが`blur`イベントをトリガーしないことに注意してください！

⚠️ 左クリック、中クリック、右クリックの区別がつきません！

次に、クリックがマーターオートの`<iframe>`内で発生したことを確認する必要があります　．．．
##### 方法1：mouseoverのイベント
`click`イベントがマッターポートの`<iframe>`で発生したかどうかを知るには、それを`container`に埋め込みます：

```HTML
    ...
    <body>
        <div id="container">
            <iframe id="showcase" ...></iframe>
        </div>
    </body>
</html>
```
そして、`mouseover`イベントと`mouseout`イベントをリッスンして、状態オブジェクト内のマウス ポインターの位置を追跡します：

```JavaScript
var stateObject = {
    isMouseOverIframe : false
}
window.addEventListener('blur',function(){
    if(stateObject.isMouseOverIframe){
        console.log('Yes! The click happened inside the iframe!');
        window.focus()
    }
});
document.getElementById('container').addEventListener('mouseover',function(){
    stateObject.isMouseOverIframe = true;
});
document.getElementById('container').addEventListener('mouseout',function(){
    stateObject.isMouseOverIframe = false;
});
```
⚠️ 
モバイルの場合、`touchstart`や`touchend`などの[タッチ・イベント](https://developer.mozilla.org/en-US/docs/Web/API/Touch_events)を処理する必要があります。
##### 方法2：document.activeElement
マーターポートの`<iframe>`で`click`イベントが発生したかどうかを知るには`document.activeElement`が`iframe`変数によって参照される要素と同じであることを確認します：

```JavaScript
window.addEventListener('blur',function(){	
    window.setTimeout(function () {	
        if (document.activeElement === iframe) {
            console.log('Yes! The click happened inside the iframe!');
            window.focus()
        }
    }, 0);
});
```
※ __Firefox__で機能させるには、0秒の`setTimeout`を使用します。
### モデルにマッタータグを視覚的に挿入
__JSFiddle__で公的にホストされている[Intersection Inspector](https://matterport.github.io/showcase-sdk/sdk_intersection_inspector.html)の[ソースコード](http://jsfiddle.net/guillermo_matterport/pftnhkuc)とは異なり、[Transient Tags Editor](https://matterport.github.io/showcase-sdk/sdk_transient_tags_editor.html)の[アプリ](https://rmauldin.github.io/matterport/transient-tag-editor/app/)のソースコードは__github__で非公開でホストされています（Firefoxでは完全に動作しないため？）。

しかし、[Transient Tags Editor](https://matterport.github.io/showcase-sdk/sdk_transient_tags_editor.html)の使いやすさに興味をそそられ、マーターポートSDKが提供する2つのツールの違いを理解して、マータータグ座標を見つけたいと思いました。
##### Intersection Inspector の仕組み
[Intersection Inspector](https://matterport.github.io/showcase-sdk/sdk_intersection_inspector.html)は、__タイマー__を使用して、ユーザーが 1 秒以上[`Pointer`](https://matterport.github.io/showcase-sdk/docs/sdkbundle/reference/current/modules/camera.html)を動かさないと、ポインターの位置にボタンを表示します。ユーザーはボタンをクリックして__Mattertag__座標を表示し、手動でコピーすることができます　．．．

これを実現するには、カメラの[`pose`](https://matterport.github.io/showcase-sdk/docs/sdkbundle/reference/current/modules/camera.html#pose-1)プロパティを観察して取得する現在の[`Camera`](https://matterport.github.io/showcase-sdk/docs/sdkbundle/reference/current/modules/camera.html)位置が必要です：

```JavaScript
    var poseCache;
    mpSdk.Camera.pose.subscribe(function(pose) {
        poseCache = pose;
    });
```
また、ポインターの[`intersection`](https://matterport.github.io/showcase-sdk/docs/sdkbundle/reference/current/modules/pointer.html#intersection-1)交差プロパティを観察して取得する現在の[`Pointer`](https://matterport.github.io/showcase-sdk/docs/sdkbundle/reference/current/modules/camera.html)位置も必要です：


```JavaScript
    var intersectionCache;
    mpSdk.Pointer.intersection.subscribe(function(intersection) {
        intersectionCache = intersection;
        intersectionCache.time = new Date().getTime();
        button.style.display = 'none';
        buttonDisplayed = false;
    });
```
※ ユーザーがポインターを移動すると`intersection`イベントがトリガーされるため、1 秒の遅延が経過する前にボタンが表示されないようにボタンを非表示にします。

次に、[`setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval)を使用して__タイマー__を設定し、適切な時間にボタンを表示します：

```JavaScript
    setInterval(() => {
        // ...
    }, 16);
```

タイマー コールバックでは、ボタンを表示するためのすべての条件が満たされているかどうかを確認します　．．．

- まず、必要な情報があることを確認します：

```JavaScript
    setInterval(() => {
        if (!intersectionCache || !poseCache) {
            return;
        }
        // ...
    }, 16);
```
- 次に、最後の`intersection`イベントが受信されてから1秒が経過したかどうかを確認するか、次のティックを待って再度確認します；

```JavaScript
    var delayBeforeShow = 1000;
    setInterval(() => {
        if (!intersectionCache || !poseCache) {
            return;
        }
        const nextShow = intersectionCache.time + delayBeforeShow;
        if (new Date().getTime() > nextShow) {
            // ...
        }
    }, 16);
```
- 最後に、ボタンがまだ表示されていないことを確認します：

```JavaScript
    var delayBeforeShow = 1000;
    var buttonDisplayed = false;
    setInterval(() => {
        if (!intersectionCache || !poseCache) {
            return;
        }
        const nextShow = intersectionCache.time + delayBeforeShow;
        if (new Date().getTime() > nextShow) {
            if (buttonDisplayed) {
                return;
            }
            // ...
        }
    }, 16);
```
条件が満たされたら、[`Conversion.worldToScreen()`](https://matterport.github.io/showcase-sdk/docs/sdkbundle/reference/current/modules/conversion.html#worldtoscreen)を使用してボタンを表示し、ポインターの画面座標を取得できます：

```JavaScript
    // ...
    setInterval(() => {
        // ...
        if (new Date().getTime() > nextShow) {
            // ...
            var size = {
                w: iframe.clientWidth,
                h: iframe.clientHeight,
            };
            var coord = mpSdk.Conversion.worldToScreen(intersectionCache.position, poseCache, size);
            button.style.left = `${coord.x - 25}px`;
            button.style.top = `${coord.y - 22}px`;
            button.style.display = 'block';
            buttonDisplayed = true;
        }
    }, 16);
```
このボタンは、デフォルトでは`display: none;`を使用して非表示にされ、`position: absolute;`を使用して本文に対して相対的に配置される単純な HTML ボタンです。

ユーザーがボタンをクリックすると、ポインタの現在の座標が`<iframe>`の上の`<div>`タグに表示され、ボタンが非表示になります：

```JavaScript
    button.addEventListener('click', function() {
        text.innerHTML = `position: ${pointToString(intersectionCache.position)}\nnormal: ${pointToString(intersectionCache.normal)}\nfloorId: ${intersectionCache.floorId}`;
        button.style.display = 'none';
        iframe.focus();
    });
```
座標は、次の関数を使用してフォーマットされます：

```JavaScript
function pointToString(point) {
  var x = point.x.toFixed(3);
  var y = point.y.toFixed(3);
  var z = point.z.toFixed(3);

  return `{ x: ${x}, y: ${y}, z: ${z} }`;
}
```
では、より使いやすく、使いやすい[Transient Tags Editor](https://matterport.github.io/showcase-sdk/sdk_transient_tags_editor.html)のインターフェイスがどのように機能するかを見てみましょう　．．．
##### Transient Tag Editor の仕組み
アプリケーションのいくつかのモデルに永続的に設定する__マッタータグ__がいくつかある場合は、[Intersection Inspector](https://matterport.github.io/showcase-sdk/sdk_intersection_inspector.html)で十分です。ただし、ユーザーがモデルにインタラクティブにタグを設定する必要がある場合は、[Transient Tags Editor](https://matterport.github.io/showcase-sdk/sdk_transient_tags_editor.html)のGUIのようなものが開始点として適しています。

[Transient Tags Editor](https://matterport.github.io/showcase-sdk/sdk_transient_tags_editor.html)を使用する主な利点は、`Mattertag`がどのように表示されるかを作成前に確認できることです。こうすることで、試行錯誤することなく正確にタグを配置することができます　．．．

タグを追加するには、「新しいタグを配置」ボタンをクリックして「タグの追加」モードに入る必要があります。その後、必要な場所に新しいタグを1つ配置できます。

エディターのその側面にのみ焦点を当て、タグを追加するだけの簡単なサンプルを作成します：

- 「タグの追加」モードでユーザーがポインターに沿ってタグを移動すると、最初のステップは新しいタグを作成して配置することです。[`Mattertag.add()`](https://matterport.github.io/showcase-sdk/docs/sdkbundle/reference/current/modules/mattertag.html#add)を使用して、そのための関数を作成しましょう：

```JavaScript
    function addTag() {
        if(!tag){
            mpSdk.Mattertag.add([{
                label: "Matterport Tag",
                description: "",
                anchorPosition: {x: 0, y: 0, z: 0},
                stemVector: {x:0, y: 0, z: 0},
                color: {r: 1, g: 0, b: 0},
            }])
            .then((sid) => {
                tag = sid[0];
            })
            .catch( (e) => {
                console.error(e);
            })
        }
    }
```
- 次に、ポインターの近くにタグを配置し、ユーザーがポインターを移動するとその位置を更新する必要があるため、[`Mattertag.editPosition()`](https://matterport.github.io/showcase-sdk/docs/sdkbundle/reference/current/modules/mattertag.html#editposition)を使用してそのための関数を作成しましょう：

```JavaScript
    function updateTagPos(newPos, newNorm=undefined, scale=undefined){
        if(!newPos) return;
        if(!scale) scale = .33;
        if(!newNorm) newNorm = {x: 0, y: 1, z: 0};

        mpSdk.Mattertag.editPosition(tag, {
            anchorPosition: newPos,
            stemVector: {
                x: scale * newNorm.x,
                y: scale * newNorm.y,
                z: scale * newNorm.z,
            }
        })
        .catch(e =>{
            console.error(e);
            tag = null;
        });
    }
```
ご覧のとおり`updateTagPos()`関数は 3 つのパラメーターを受け取ります：

1. __newPos__：`Mattertag`の新しいアンカー位置。
2. __newNorm__：`Mattertag`の新しいステム・ベクトルのオプション。
3. __scale__：`Mattertag`のステム用の新しいスケールのオプション。

- ユーザーがポインターを移動したときにタグの位置を更新するには、ポインターの[`intersection`](https://matterport.github.io/showcase-sdk/docs/sdkbundle/reference/current/modules/pointer.html#intersection-1)交差プロパティを観察して`updateTagPos()`を呼び出します：

```JavaScript
    mpSdk.Pointer.intersection.subscribe(intersectionData => {
        if(tag){
            if(intersectionData.object === 'intersectedobject.model' || intersectionData.object === 'intersectedobject.sweep'){
                updateTagPos(intersectionData.position, intersectionData.normal);
            }
        }
    });
```
- 新しいタグを配置するには、ユーザーがマウス ボタンをクリックするだけです。[Transient Tags Editor](https://matterport.github.io/showcase-sdk/sdk_transient_tags_editor.html)は、`<iframe>`でのクリックをインターセプトする独自のバージョンの`document.activeElement`方法を提供します_（ただし、__Firefox__では機能しないため、エディターは非常に複雑な回避策）_：

```JavaScript
    function focusDetect(){
        const eventListener = window.addEventListener('blur', function() {
            if (document.activeElement === iframe) {
                placeTag(); //function you want to call on click
                setTimeout(function(){ window.focus(); }, 0);
            }
            window.removeEventListener('blur', eventListener );
        });
    }
```
ただし、__Firefox__でより適切に動作するバージョンを使用します _（ただし、何らかの理由で__Firefox__で最初にクリックした後は動作を停止します）_：

```JavaScript
    window.addEventListener('blur',function(){  
        window.setTimeout(function () { 
            if (document.activeElement === iframe) {
                placeTag(); //function you want to call on click
                window.focus()
                addTag();
            }
        }, 0);
    });
```
- 最後に、[`Mattertag.navigateToTag()`](https://matterport.github.io/showcase-sdk/docs/sdkbundle/reference/current/modules/mattertag.html#navigatetotag)を使用して、新しいタグに移動してその__billboard（看板）を開く__関数を作成しましょう：

```JavaScript
    function placeTag(){
        if(tag) mpSdk.Mattertag.navigateToTag(tag, mpSdk.Mattertag.Transition.INSTANT);
        tag = null;
    }
```

##### 単純なエディターのサンプル
まず、全てのJavaScriptソースコード：

```JavaScript
"use strict";

const sdkKey = "aaaaXaaaXaaaXaaaXaaaXaaa"
const modelSid = "iL4RdJqi2yK";

let iframe;
let tag;

document.addEventListener("DOMContentLoaded", () => {
    iframe = document.querySelector('.showcase');
    iframe.setAttribute('src', `https://my.matterport.com/show/?m=${modelSid}&help=0&play=1&qs=1&gt=0&hr=0`);
    iframe.addEventListener('load', () => showcaseLoader(iframe));
});

function showcaseLoader(iframe){
    try{
        window.MP_SDK.connect(iframe, sdkKey, '3.10')
        .then(loadedShowcaseHandler)
        .catch(console.error);
    } catch(e){
        console.error(e);
    }
}

function loadedShowcaseHandler(mpSdk){
    addTag()

    function placeTag(){
        if(tag) mpSdk.Mattertag.navigateToTag(tag, mpSdk.Mattertag.Transition.INSTANT);
        tag = null;
    }

    window.addEventListener('blur',function(){  
        window.setTimeout(function () { 
            if (document.activeElement === iframe) {
                placeTag(); //function you want to call on click
                window.focus()
                addTag();
            }
        }, 0);
    });
    function updateTagPos(newPos, newNorm=undefined, scale=undefined){
        if(!newPos) return;
        if(!scale) scale = .33;
        if(!newNorm) newNorm = {x: 0, y: 1, z: 0};

        mpSdk.Mattertag.editPosition(tag, {
            anchorPosition: newPos,
            stemVector: {
                x: scale * newNorm.x,
                y: scale * newNorm.y,
                z: scale * newNorm.z,
            }
        })
        .catch(e =>{
            console.error(e);
            tag = null;
        });
    }

    mpSdk.Pointer.intersection.subscribe(intersectionData => {
        if(tag){
            if(intersectionData.object === 'intersectedobject.model' || intersectionData.object === 'intersectedobject.sweep'){
                updateTagPos(intersectionData.position, intersectionData.normal);
            }
        }
    });

    function addTag() {
        if(!tag){
            mpSdk.Mattertag.add([{
                label: "Matterport Tag",
                description: "",
                anchorPosition: {x: 0, y: 0, z: 0},
                stemVector: {x:0, y: 0, z: 0},
                color: {r: 1, g: 0, b: 0},
            }])
            .then((sid) => {
                tag = sid[0];
            })
            .catch( (e) => {
                console.error(e);
            })
        }
    }
} // loadedShowcaseHandler
```
HTMLのソースコード：

```HTML
<!DOCTYPE html>
<html>
    <head>
        <title>Transient Tag Editor</title>
        <style>
            #showcase {
                width: 100%;
                height: 100vh;
            }
        </style>
        <script src="https://static.matterport.com/showcase-sdk/2.0.1-0-g64e7e88/sdk.js"></script>
        <script src="/js/app-editor.js" type="text/javascript" defer></script>
    </head>
    <body>
        <iframe id="showcase" frameborder="0"  allowFullScreen allow="xr-spatial-tracking"></iframe>
    </body>
</html>
```
やった！
## [Matterport SDK Bundle](https://matterport.github.io/showcase-sdk/sdkbundle_home.html)
第2のアプリを作成するために、今回は__SDK Bundle__を使用しますが、まだ（無料の）サンドボックス・アクセスです　．．

まず、最新の[SDK Bundleのパケージ](https://static.matterport.com/showcase-sdk/bundle/3.1.41.5-19-g5133fdd3e/showcase-bundle.zip)をダウンロードします。

次に、第2のアプリケーションの2つ目のルートを処理するようにサーバーを更新しましょう　．．．
### ウェブサーバー
バンドル アプリのルートを追加しましょう：

```JavaScript:server.js
// ...
app.get('/bundle', (req, res) => {
    res.sendFile(`${__dirname}/public/index-bundle.html`);
});
app.listen(8000, () => {
    console.log('Application listening on port 8000!');
});
```
その後、ウェブサーバーを再起動して　．．．
### 第2のアプリ：基礎
最初のものと似ているので、別の基本的な__Matterport__アプリを作成しましょう。最初のアプリ ファイルをコピーして名前を変更できます　．．．
##### HTML
まず、`index.html`ファイルをコピーして、`index-bundle.html`に名前を変更します：

JavaScriptのファイル名を`app-bundle.js`に変更します：

```public/index-bundle.html
...
        <script src="/app-bundle.js" defer></script>
...
```
##### JavaScript
それでは、`app.js`ファイルをコピーして、`app-bundle.js`に名前を変更しましょう。

そこで、iframeのsourceURLを`/bundle/showcase.html?${params}`に変更します：

```public/app-bundle.js
// ...
document.addEventListener("DOMContentLoaded", () => {
    iframe.setAttribute('src', `/bundle/showcase.html?${params}`);
    iframe.addEventListener('load', () => showcaseLoader(iframe));
});
// ...
```
そして、`connect`ステートメントを次のように変更します`iframe.contentWindow.MP_SDK.connect`：

```public/app-bundle.js
// ...
function showcaseLoader(iframe){
    try{
        iframe.contentWindow.MP_SDK.connect(iframe, sdkKey, '3.10')
            .then(loadedShowcaseHandler)
            .catch(console.error);
    } catch(e){
        console.error(e);
    }
}
// ...
```
### シーン
__SDK Bundle__は、標準のSDKに追加のモジュールである`Scene`モジュールを提供します。
##### ライティング
シーンにはライトが必要です。ライトがない場合、モデルは黒で表示されます。

__Matterport__は、このために`mp.lights`と呼ばれる基本的なライト コンポーネントを提供します。[`Scene.createNode()`](https://matterport.github.io/showcase-sdk/docs/sdkbundle/reference/current/modules/scene.html#createnode)を使用してシーンにノードを作成し、[`INode.addComponent()`](https://matterport.github.io/showcase-sdk/docs/sdkbundle/reference/current/interfaces/scene.inode.html#addcomponent)を使用してコンポーネントをそれにアタッチしましょう：

```JavaScript:loadedShowcaseHandler(mpSdk)
    const lights = await mpSdk.Scene.createNode();
    lights.addComponent('mp.lights');
    lights.start();
```
※ 
コンポーネントで [`INode.start()`](https://matterport.github.io/showcase-sdk/docs/sdkbundle/reference/current/interfaces/scene.inode.html#start)を呼び出して、動作を開始します。
### モデル
__SDK Bundle__は、次のシーンおよびモデル アセット タイプのロードをサポートしています：

- __dae__、`mpSdk.Scene.Component.DAE_LOADER`で
- __fbx__、`mpSdk.Scene.Component.FBX_LOADER `で
- __obj__、`mpSdk.Scene.Component.OBJ_LOADER `で
- __gltf__、`mpSdk.Scene.Component.GLTF_LOADER `で

モデルは、照明コンポーネントのようなコンポーネントとしてシーンに追加されます：

```JavaScript:loadedShowcaseHandler(mpSdk)
    // ...
    const modelNode = await mpSdk.Scene.createNode();
    const fbxComponent = modelNode.addComponent(mpSdk.Scene.Component.DAE_LOADER, {
        url: 'https://gitcdn.link/repo/mrdoob/three.js/dev/examples/models/collada/stormtrooper/stormtrooper.dae',
    });
```
※ __SDK Bundle__ドキュメントのチュートリアル[__Working With Models__](https://matterport.github.io/showcase-sdk/sdkbundle_tutorials_models.html#select-a-model)（モデルの操作）にいくつかのモデルがリストされています。
##### 拡大縮小
一部のモデルは実世界の単位に調整されていないため、モデルをスケーリングしてより現実的なサイズに調整する必要がある場合があります：

```JavaScript:loadedShowcaseHandler(mpSdk)
    // ...
    fbxComponent.inputs.localScale = {
        x: 0.3,
        y: 0.3,
        z: 0.3
    };
```
##### ポジショニング、按排
デフォルトの位置は(0, 0, 0)ですが、ストームトルーパーを空中に浮かせたくない場合は、それを下げる必要があります：

```JavaScript:loadedShowcaseHandler(mpSdk)
    // ...
    modelNode.obj3D.position.set(0,-1.65,0);
```
##### 回転
ストームトルーパーのモデルは、y軸に沿った角度が0の場合はカメラの方を向き、角度が`𝜋`の場合はカメラの方を向きます：

```JavaScript:loadedShowcaseHandler(mpSdk)
    // ...
    modelNode.obj3D.rotation.y = Math.PI
```
※ __Matterport__は、__Three.js__のように、角度を指定するためにラジアンを使用していました。

##### アクティベート
照明コンポーネントと同様に、[`INode.start()`](https://matterport.github.io/showcase-sdk/docs/sdkbundle/reference/current/interfaces/scene.inode.html#start)を使用してモデルをアクティブ化する必要があります：

```JavaScript:loadedShowcaseHandler(mpSdk)
    // ...
    modelNode.start();
```
### 第2のアプリ: まとめ
2番目のアプリを完成させましょう　．．．
##### 
アニメーション
モデルをアニメーション化するには、単に[`requestAnimationFrame()`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame)関数を使用します。

ストームトルーパーを前後に動かしながらゆっくり回転させましょう：

```JavaScript:loadedShowcaseHandler(mpSdk)
    // ...
    let step = -0.1
    let zPos = 0
    const tick = function() {
        requestAnimationFrame(tick);
        if (zPos>= 1.8) {
            step = -0.02
        } else if (zPos <= -0.4) {
            step = 0.02
        }
        zPos += step
        modelNode.obj3D.rotation.y += 0.02;
        modelNode.obj3D.position.set(0,-1.65,zPos);
    }
    tick();
```
##### JavaScriptコードのまとめ
すべてを`app-bundle.js`ファイルにまとめましょう：

```JavaScript:app-bundle.js
"use strict";

const sdkKey = "aaaaXaaaXaaaXaaaaXaaaXaaa"
const modelSid = "22Ub5eknCVx";
const params = `m=${modelSid}&play=1&qs=1&log=0&applicationKey=${sdkKey}`;

var iframe = document.getElementById('showcase');

document.addEventListener("DOMContentLoaded", () => {
    iframe.setAttribute('src', `/bundle/showcase.html?${params}`);
    iframe.addEventListener('load', () => showcaseLoader(iframe));
});

function showcaseLoader(iframe){
    try{
        iframe.contentWindow.MP_SDK.connect(iframe, sdkKey, '3.10')
        .then(loadedShowcaseHandler)
        .catch(console.error);
    } catch(e){
        console.error(e);
    }
}

async function loadedShowcaseHandler(mpSdk){
  const lights = await mpSdk.Scene.createNode();
  lights.addComponent('mp.lights');
  lights.start();

  const modelNode = await mpSdk.Scene.createNode();
  const fbxComponent = modelNode.addComponent(mpSdk.Scene.Component.DAE_LOADER, {
    url: 'https://gitcdn.link/repo/mrdoob/three.js/dev/examples/models/collada/stormtrooper/stormtrooper.dae',
  });

  fbxComponent.inputs.localScale = {
    x: 0.3,
    y: 0.3,
    z: 0.3
  };

  modelNode.obj3D.position.set(0,-1.65,0);

  modelNode.obj3D.rotation.y = Math.PI

  modelNode.start();

  let step = -0.1
  let zPos = 0
  const tick = function() {
    requestAnimationFrame(tick);
    if (zPos>= 1.8) {
      step = -0.02
    } else if (zPos <= -0.4) {
      step = 0.02
    }
    zPos += step
    modelNode.obj3D.rotation.y += 0.02;
    modelNode.obj3D.position.set(0,-1.65,zPos);
  }
  tick();
}
```
## [Matterport Model API](https://support.matterport.com/hc/en-us/articles/360041976053)
このチュートリアルで検討した 2 つの SDK に加えて、__Matterport__は__API Token__を使用する[__Model API__と呼ばれる__GraphQL API__](https://support.matterport.com/hc/en-us/articles/360041976053)も提供します。SDKとの違いは[__FAQ__](https://support.matterport.com/hc/en-us/articles/360041976053#faqs-0-5)で説明されています。

管理者権限でMatterport Cloud（my.matterport.com）にログインしているユーザーは定的に__GraphQL API__を試すことができる__インタラクティブ・コンソール__が[__reference documentation__](https://api.matterport.com/docs/reference)とともに利用できます。
## 全てのコード
すべてのサンプルのコードは、__github__で入手できます：
[github.com/loic-meister-guild/pj-matterport-sdk-2021-tutorial](https://github.com/loic-meister-guild/pj-matterport-sdk-2021-tutorial)
## 参照
- [Node.js + Express Tutorial for 2021](https://catalins.tech/nodejs-express-tutorial-for-2021-getting-started-with-the-javascript-web-server-framework)
- [How to detect a click event on a cross domain iframe](https://gist.github.com/jaydson/1780598)