チュートリアル - 3D空間を使った簡単webサイト 3/5

[前回](https://qiita.com/sutobu000/items/7905429e0bf4222e7c80)

#3Dオブジェクトでマウスのイベントを起こす
3Dオブジェクトとマウスカーソルとの関係性を作ります。

ヒエラルキーからchara->Modelを選択。
ADD COMPONENTからScriptを選択し、hotspotのscriptを登録します。
このModelを一つずつ選択する動作は面倒ですが、複数選択して設定を行うこともできます。

![スクリーンショット 2019-07-31 12.05.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109168/ec2181e8-e167-fee4-0e17-3c779c8d1d2b.png)


hotspotの中身をコードエディターで開きます。

配置された3Dオブジェクトとマウスカーソルとの関係を持たせるにはレイキャストと呼ばれるものを使用します。
レイキャストとは、特定の地点から特定の方向へ直線の線を引き、その線上のどこかでぶつかるものがあるか検知するものです。
ここでのレイキャストは、「マウスがクリックされた時、カメラから見て、3Dオブジェクトとそのマウスカーソルが重なっているか」を取得するような処理を作ります。

ここから以下の処理を追加していきます

- 3Dオブジェクトにレイキャストの線が当たるヒットエリアを作成
- 使用するカメラのEntityとヒットエリアの大きさをAttributesでEditorから設定
- ヒットエリアにマウスホバーしてイベントが取れる

以下のコードをhotspot.jsに書き換えます。

```js
var Hotspot = pc.createScript('hotspot');

Hotspot.attributes.add("cameraEntity", {type: "entity", title: "Camera Entity"}); // カメラのEntityを取得
Hotspot.attributes.add("radius", {type: "number", title: "Radius"}); // Entityのヒットエリアの範囲を指定

Hotspot.prototype.initialize = function() { // init
    this.hitArea = new pc.BoundingSphere(this.entity.getPosition(), this.radius); // ヒットエリアを作成。BoundingSphereがentityの境界エリアを作成(Photoshopでいうバウンディングボックス的な)
    this.ray = new pc.Ray(); // cameraからentityへ直進する線のデータを作成。（Rayは光線の意でstart pointからentityまでの距離を測ったりすることが可能）
    
    this.directionToCamera = new pc.Vec3(); // Vector座標の型を取得

    this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseHover, this); // マウスカーソルがホバーした時
    
};

Hotspot.prototype.onMouseHover = function(screenPosition) { // マウスホバー時
    this.cameraEntity.camera.screenToWorld(screenPosition.x, screenPosition.y, this.cameraEntity.camera.farClip, this.ray.direction); // ポジションを2Dスクリーンから3D空間へ変換
    this.ray.origin.copy(this.cameraEntity.getPosition()); // レイのオリジナルのポジションにカメラのポジションをコピー
    this.ray.direction.sub(this.ray.origin).normalize(); // 3次元ベクトルを他の場所から減算し、単位ベクトルに変換

    if (this.hitArea.intersectsRay(this.ray)) { // ヒットエリアとレイが交差した場合
        console.log("hover");
    }
};
```

新しくattributesというのが言葉がありますが、これは属性設定ができ、PlayCanvasのEditorからデータを変更することができるようになります。
ここではカメラのEntityの選択と、対象の3Dオブジェクトのヒットエリアの大きさを指定できます。

```js
Hotspot.attributes.add("cameraEntity", {type: "entity", title: "Camera Entity"}); // カメラのEntityを取得
Hotspot.attributes.add("radius", {type: "number", title: "Radius"}); // Entityのヒットエリアの範囲を指定
```

以下画像のように入力エリアが表示されていない場合は、Editアイコン横のParseで更新され表示されます。

![スクリーンショット 2019-06-20 12.00.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109168/8e7e56ab-52cf-99a2-69ca-6f322e07da37.png)

initializeではヒットエリア、レイキャストのRayの設定、マウス移動のイベントを取得しています。

```js
Hotspot.prototype.initialize = function() {
    this.hitArea = new pc.BoundingSphere(this.entity.getPosition(), this.radius); // ヒットエリアを作成。BoundingSphereがentityの境界エリアを作成(Photoshopでいうバウンディングボックス的な)
    this.ray = new pc.Ray(); // 直進する線のデータを作成。（Rayは光線の意でstart pointからentityまでの距離を測ったりすることが可能）

    this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseHover, this); // マウスカーソルがホバーした時
};
```

マウス移動のイベントで3Dオブジェクトとカメラから見てホバーしているかonMouseHoverで取得しています。

```js
Hotspot.prototype.onMouseHover = function(screenPosition) { // マウスホバー時
    this.cameraEntity.camera.screenToWorld(screenPosition.x, screenPosition.y, this.cameraEntity.camera.farClip, this.ray.direction); // ポジションを2Dスクリーンから3D空間へ変換
    this.ray.origin.copy(this.cameraEntity.getPosition()); // レイのオリジナルのポジションにカメラのポジションをコピー
    this.ray.direction.sub(this.ray.origin).normalize(); // 3次元ベクトルを他の場所から減算し、単位ベクトルに変換

    if (this.hitArea.intersectsRay(this.ray)) { // ヒットエリアとレイが交差した場合
        console.log("hover");
    }
};
```

マウス移動のイベントで3Dオブジェクトとカメラから見てホバーしているかonMouseHoverで取得しています。

 

これでマウスオーバー（ホバー）のイベントを取得できました。
次はクリックのイベントです。

initializeに以下を追加

```js
    this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this); // クリックが押された時
```

クリックイベントの左クリックを取得

```js
Hotspot.prototype.onMouseDown = function(event) { // クリックが押されている時
    if (event.button == pc.MOUSEBUTTON_LEFT) { // 左クリックが押された時
        this.doRayCast(event); // レイキャストを呼ぶ
    }
};
```

クリックしたらレイキャストを走らせる

```jsonnet
Hotspot.prototype.doRayCast = function (screenPosition) { // レイキャスト処理（ある地点から特定方向に直線で線を引いて、その線上で物体があるか検知する処理）
    if (this.hitArea.intersectsRay(this.ray)) { // ヒットエリアとレイが交差した場合
        console.log("click!!");
    }
};
```

Launch画面をリロードしてコンソールから確認ができます。

ここまでのコードは以下になります。（hotspot.js）

```js
var Hotspot = pc.createScript('hotspot');

// canvasのclickやhoverなどの処理を行う

Hotspot.attributes.add("cameraEntity", {type: "entity", title: "Camera Entity"}); // カメラのentityを取得
Hotspot.attributes.add("radius", {type: "number", title: "Radius"}); // entityのヒットエリアの範囲を指定

Hotspot.prototype.initialize = function() { // init
    this.hitArea = new pc.BoundingSphere(this.entity.getPosition(), this.radius); // ヒットエリアを作成。BoundingSphereがentityの境界エリアを作成(Photoshopでいうバウンディングボックス的な)
    this.ray = new pc.Ray(); // cameraからentityへ直進する線のデータを作成。（Rayは光線の意でstart pointからentityまでの距離を測ったりすることが可能）
    
    this.directionToCamera = new pc.Vec3(); // Vector座標の型を取得
    
    this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseHover, this); // マウスカーソルがホバーした時
    this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this); // クリックが押された時
};


Hotspot.prototype.doRayCast = function (screenPosition) { // レイキャスト処理（ある地点から特定方向に直線で線を引いて、その線上で物体があるか検知する処理）
    if (this.hitArea.intersectsRay(this.ray)) { // ヒットエリアとレイが交差した場合
        console.log("click!!");
    }
};


Hotspot.prototype.onMouseHover = function(screenPosition) { // マウスホバー時
    this.cameraEntity.camera.screenToWorld(screenPosition.x, screenPosition.y, this.cameraEntity.camera.farClip, this.ray.direction); // ポジションを2Dスクリーンから3D空間へ変換
    this.ray.origin.copy(this.cameraEntity.getPosition()); // レイのオリジナルのポジションにカメラのポジションをコピー
    this.ray.direction.sub(this.ray.origin).normalize(); // 3次元ベクトルを他の場所から減算し、単位ベクトルに変換

    if (this.hitArea.intersectsRay(this.ray)) { // ヒットエリアとレイが交差した場合
        console.log("hover");
    }
};

Hotspot.prototype.onMouseDown = function(event) { // クリックが押されている時
    if (event.button == pc.MOUSEBUTTON_LEFT) { // 左クリックが押された時
        this.doRayCast(event); // レイキャストを呼ぶ
    }
};
``` 

次は3Dオブジェクトに応じてHTMLのページを表示させるようにします。


[次回](https://qiita.com/sutobu000/items/8380dfa812e60b15cea4)