ワールド座標をローカル座標に変換するためにworldToLocal
1を利用したところ、意図しない結果になってハマりました。Three.jsのバージョンはr119です。
解決法
オブジェクトのシーンへの追加をattach
により行います。グループを利用する場合については後述します。
const scene = new THREE.Scene();
const cube = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshStandardMaterial({
color: 0xFFD046
})
);
cube.position.set(2, 3, 2);
scene.attach(cube); // これが重要!
// ワールド座標からローカル座標へ変換
const dir = cube.worldToLocal(
new THREE.Vector3(-4, 0, 2)
);
attach
とは
Three.jsのドキュメント(Object3D#attach)によれば
.attach
( object : Object3D ) : this
Adds object as a child of this, while maintaining the object's world transform.
とあり、ワールドでの変換を保持したまま子オブジェクトとして追加するものです。
オブジェクトのシーンへの追加はadd
が広く用いられているようですが、attach
では内部的にadd
を呼び出している2ためadd
をする必要はありません。
attach
の代わりにadd
するとどうなるか
worldToLocal
はオブジェクトのシーンへの追加方法によっては次の画像のように全く意図しない結果になります。
attach した場合(意図した結果)
|
add した場合
|
---|---|
画像は黄色の箱(cube
)から青色の箱(cube2
)に向かう矢印を黄色の箱のローカル座標に描画したものです。意図した結果である左の画像に対して、右の画像では全く別の方向を指しています。コードは次に示す通りです。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Three.js test</title>
<style>
html, body{ margin: 0; padding: 0; }
canvas{ display: block; }
</style>
</head>
<body>
<script src="./main.js"></script>
</body>
</html>
import * as THREE from "three";
import {OrbitControls} from "three/examples/jsm/controls/OrbitControls";
// コンテナ
const container = document.createElement("div");
document.body.appendChild(container);
// レンダラー
const renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild(renderer.domElement);
// ワールド
const scene = new THREE.Scene();
scene.add(new THREE.AxesHelper(5));
scene.add(new THREE.GridHelper(10, 10));
// カメラ
const camera = new THREE.PerspectiveCamera(
50, window.innerWidth / window.innerHeight
);
camera.position.set(-8, 6, 8);
// コントロール
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
// ライト
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
const pointLight = new THREE.PointLight(0xffffff);
pointLight.position.set(0, 5, 0);
scene.add(ambientLight, pointLight);
// オブジェクト
function generateCube(colorHex: THREE.Color){
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({
color: colorHex
});
const cube = new THREE.Mesh(geometry, material);
cube.add(new THREE.AxesHelper(2));
return cube;
}
const cube = generateCube(new THREE.Color(0xFFD046));
cube.position.set(2, 3, 2);
cube.rotateX(THREE.MathUtils.degToRad(60));
cube.rotateY(THREE.MathUtils.degToRad(90));
scene.attach(cube); // これが重要!
const cube2 = generateCube(new THREE.Color(0x003F91));
cube2.position.set(-4, 0, -2);
scene.add(cube2);
// 座標変換
const dir = cube.worldToLocal(
cube2.position.clone()
);
const length = dir.length();
dir.normalize();
const arrowLocal = new THREE.ArrowHelper(
dir,
new THREE.Vector3(0, 0, 0), // origin
length,
0xff0000
);
cube.add(arrowLocal);
function animate(){
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(() => animate());
}
animate();
いろいろ試したところ、cube
を回転させず移動のみの場合はadd
でも矢印は青い箱に刺さるように表示されました。
グループを利用する場合
黄色い箱をグループに入れるような場合はグループのattach
も必要になります。画像は先の例と同様に黄色の箱から青色の箱に向かう矢印を描画したものです。グループにはAxesHelperと黄色の箱を追加しています。
attach した場合(意図した結果)
|
add した場合
|
---|---|
先の例では黄色い箱はシーンにattach
していましたが、今回はグループが親になるため、グループへのattach
を行います。座標変換はグローバル→グループ→オブジェクトの順に行います。
...
// オブジェクト
function generateCube(colorHex: THREE.Color){
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({
color: colorHex
});
const cube = new THREE.Mesh(geometry, material);
cube.add(new THREE.AxesHelper(2));
return cube;
}
const cube = generateCube(new THREE.Color(0xFFD046));
cube.position.set(2, 3, 2);
cube.rotateX(THREE.MathUtils.degToRad(60));
cube.rotateY(THREE.MathUtils.degToRad(90));
// scene.attach(cube); シーンにはattachしません
// グループ
const group = new THREE.Group();
group.position.set(1, 1, 1);
group.rotateX(THREE.MathUtils.degToRad(60));
group.rotateY(THREE.MathUtils.degToRad(45));
group.add(new THREE.AxesHelper(2));
group.attach(cube); // グループ <- cube
scene.attach(group); // シーン <- グループ
const cube2 = ...
...
// 座標変換
const dir = cube.worldToLocal(
group.worldToLocal(cube2.position.clone())
);
...
参考
- javascript - Translate a vector from global space to local vector in three.js - Stack Overflow
- Object3D - three.js docs
- Object3D: added attach() method by WestLangley · Pull Request #16528 · mrdoob/three.js · GitHub