概要
Three.jsでOrbitControlsのようなカメラの移動や回転を加味してクリックイベントなどの任意のタイミングで画面の真正面にオブジェクトを配置するのに楽だった方法を備忘録として記します。(three.js r149)
方法
- カメラを中心とした透明の球を定義する
- 任意のタイミングでレイキャスター(Raycaster)と球体の交点座標を取得する
- 取得した交点座標にオブジェクトを移動する
サンプル
// シーン
const scene = new THREE.Scene()
// カメラ
const camera = new THREE.PerspectiveCamera( 45, width / height, 1, 1000 );
scene.add(camera)
// 仮の任意のオブジェクト
const object = new THREE.Object3D()
scene.add(object)
// 1. カメラを中心とした透明の球を定義する
const radius = 100 // 結果的にカメラとオブジェクトの距離になる
const panoramaGeometry = new THREE.SphereGeometry(radius, 32, 32)
// Raycasterが反応するように急の反転を表裏を反転する(THREE.DoubileSideでも良い)
panoramaGeometry.scale(-1, 1, 1)
this.customWorld = new THREE.Mesh(
panoramaGeometry,
new THREE.MeshLambertMaterial({
transparent: true, // 透過する
opacity: 0.0,
})
)
this.customWorld.userData = { type: 'customWorld' }
...
// 2. 任意のタイミング(クリックイベントとか)でレイキャスターと交点の球体の交点座標を取得する
const raycaster = new THREE.Raycaster()
raycaster.setFromCamera({ x: 0, y: 0 }, camera)
const intersects = raycaster.intersectObject(scene)
// 事前に定義している球とカメラの視線の交点座標(THREE.Vector3)を取得する
const intersectsCustomWorldPosition = intersects.find(
(obj) => obj.object.userData.type === 'customWorld')?.point
if (!intersectsCustomWorldPosition) throw new Error()
// 3. 取得した交点座標にオブジェクトを移動する
object.position.copy(intersectsCustomWorldPosition)
レイキャスターで交点を取得するのに気にするとよかったこと
RaycasterはMeshに対してはデフォルトでは表側の面しか検出できないことに注意してください。今回の場合は、以下のようにジオメトリの裏表を反転させることで事足りますが、一般的にはマテリアルに対してTHREE.DoubleSide
を適用してください。
panoramaGeometry.scale(-1, 1, 1)
また、Raycaster#intersectObjectでレイキャスターと交差するオブジェクト全てが取得できるのですが、intersectObject
の返り値のオブジェクトが複数あり対象を見つけるのが難しいことがあるかと思います。今回の場合は、透過している球にあたるものですね。そういう場合は思い切ってメッシュに対して(userData)[https://threejs.org/docs/#api/en/core/Object3D.userData]などに自前の設定してしまうと以下のように取得が楽になりました。(もっと良い方法があるかもしれない)
this.customWorld.userData = { type: 'customWorld' }
// 事前に定義している球とカメラの視線の交点座標(THREE.Vector3)を取得する
const intersectsCustomWorldPosition = intersects.find(
(obj) => obj.object.userData.type === 'customWorld')?.point