【A-Frame】Raycastで任意の地点の距離を測定して表示する
A-FrameのRaycastで任意の距離感を測定したいなあと思ったのですが、始点はカメラ位置にしかできないみたいなので(そりゃそうか、VR用途だから。)、Three.js側のRaycastを使うことで対処できる。
デモ
画面中央の丸いポチをブロックに移動したりしてみてください。
ソースコード
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Raycasterサンプル</title>
<meta name="description" content="Raycaster - A-Frame">
<!-- <script src="../../../dist/aframe-master.js"></script> -->
<script src="https://aframe.io/releases/1.0.0/aframe.min.js"></script>
<script src="https://unpkg.com/aframe-button-controls@^1.1.0/aframe-button-controls.js"></script>
</head>
<body>
<script>
AFRAME.registerComponent('raycaster-listen', {
init: function () {
this.el.addEventListener('raycaster-intersected', evt => {
// 箱の色を変える
evt.target.setAttribute('material', 'color', '#7f7');
// 交差情報を変数にセット
this.raycaster = evt.detail.el;
// 線を表示
document.getElementById("cylinder").setAttribute('visible', true)
});
// インターセクトから外れた
this.el.addEventListener('raycaster-intersected-cleared', evt => {
// 箱の色を戻す
evt.target.setAttribute('material', 'color', '#f77');
// 交差情報を空にする
this.raycaster = null;
// 線を消す
document.getElementById("cylinder").setAttribute('visible', false)
// テキストを消す
document.getElementById("text1").setAttribute('value', '')
// コメントを消す
document.getElementById("text_comment").setAttribute('visible', false)
});
},
// 毎フレーム処理する内容を書く
tick: function () {
if (!this.raycaster) { return; } // 見つからない
var intersection = this.raycaster.components.raycaster.getIntersection(this.el);
//document.getElementById("ball").setAttribute('position', intersection.point)
document.getElementById("cylinder").setAttribute('position', intersection.point);
//////////////////////
// 任意の交差判定を作る
// https://threejs.org/docs/#api/en/core/Raycaster
var origin = intersection.point; //new THREE.Vector3(0, 0, -10);
var direction = intersection.face.normal; //new THREE.Vector3(1, 0, 0);
var near = 0.01;
var far = 100;
var ray = new THREE.Raycaster(origin, direction, 1);
// 検索対象は自力で作ってみる
var targetEls = this.el.sceneEl.querySelectorAll('.clickable');
this.targets = [];
for (var i = 0; i < targetEls.length; i++) {
this.targets.push(targetEls[i].object3D);
}
// 交差判定を実行
var intersection2 = ray.intersectObjects(this.targets, true);
if (intersection2.length > 0) {
console.log("あった!");
document.getElementById("cylinder").setAttribute('visible', true)
// 円柱の位置と長さを求めて更新する
var center = new THREE.Vector3(
(origin.x + intersection2[0].point.x) / 2.0,
(origin.y + intersection2[0].point.y) / 2.0,
(origin.z + intersection2[0].point.z) / 2.0);
document.getElementById("cylinder").setAttribute('position', center);
document.getElementById("cylinder").setAttribute('height', intersection2[0].distance);
// テキスト表示のサンプル
document.getElementById("text1").setAttribute('visible', true)
document.getElementById("text1").setAttribute('value', String(intersection2[0].distance));
var text_center = center;
text_center.y += 0.6;
document.getElementById("text1").setAttribute('position', text_center);
// コメント表示
document.getElementById("text_comment").setAttribute('visible', true)
var comment_center = center;
comment_center.y -= 1;
document.getElementById("text_comment").setAttribute('position', comment_center);
} else {
// 線を消す
document.getElementById("cylinder").setAttribute('visible', false)
// テキストを消す
document.getElementById("text1").setAttribute('value', '')
// コメントを消す
document.getElementById("text_comment").setAttribute('visible', false)
}
}
});
AFRAME.registerComponent('clickevent', {
init: function () {
this.el.addEventListener('click', function (evt) {
var state = document.getElementById("text_comment").getAttribute('value')
if (state.length== 0) {
var text = prompt('コメントを入力ください');
document.getElementById("text_comment").setAttribute('value', text);
}
});
}
});
</script>
<!--https://aframe.io/docs/1.2.0/components/raycaster.html#sidebar-->
<!--
テキスト表示、コメントのサンプル
-->
<a-scene>
<a-entity camera look-controls wasd-controls></a-entity>
<a-entity raycaster="objects: .clickable" cursor></a-entity>
<a-entity id="raycaster" raycaster></a-entity>
<a-entity geometry material raycaster-listen></a-entity>
<a-entity id="box1" class="clickable" position="-5 0 -10" scale="2 4 3" geometry="primitive: box" material="color: #f77" raycaster-listen></a-entity>
<a-entity id="box2" class="clickable" position="5 0 -10" scale="3 4 2" geometry="primitive: box" material="color: #f77" raycaster-listen></a-entity>
<a-entity id="box3" class="clickable" position="15 5 -11" scale="5 6 4" geometry="primitive: box" material="color: #f77" raycaster-listen></a-entity>
<a-entity id="box4" class="clickable" position="-20 5 -12" scale="6 4 3" geometry="primitive: box" material="color: #f77" raycaster-listen></a-entity>
<a-entity position="0 0 0">
<a-cylinder id="cylinder" position="0 0 0" rotation="90 0 90" height="5" radius="0.1" color="#f00" clickevent></a-cylinder>
</a-entity>
<!-- テキスト表示のサンプル -->
<a-text id="text1" position="0 0 0" value="" scale="3 3 3" color="black" visible="false"></a-text>
<a-text id="text_comment" position="0 0 0" value="" scale="3 3 3" color="blue" visible=""></a-text>
<!-- next -->
<!-- camera -->
<a-camera position="0 0 0" collider-check>
<!-- カーソル表示。画面中央のポッチ -->
<a-cursor></a-cursor>
</a-camera>
</a-scene>
</body>
</html>
説明
レイキャストを2回やっています。
1回目のレイキャスト(this.raycaster.components.raycaster.getIntersection)はA-Frameコンポーネントを普通に使って、自分の視点から見つかった地点の座標を取得できます。
このとき、座標(intersection.point)と、面の法線(intersection.face.normal)を覚えておきましょう。
2回目のレイキャスト(new THREE.Raycaster(origin, direction, 1);)はTHREE.jsを使っています。先程見つけた座標から、法線方向にレイを飛ばせ という指示をします。
var intersection2 = ray.intersectObjects(this.targets, true)のところで、ぶつかる面を探しています。
任意の地点間の距離はintersection2[0].distanceです。
あとはこの視点~終点にたいして、可視化するためにCylinderを表現したりします。
参考サイト
・https://qiita.com/mkawamo/items/a0e6d962d1c716c75eae
・https://www.sejuku.net/blog/95030
