【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