Three.js-坐标

矩阵

空了看下Object3D中关于矩阵相关的源码,把原理给弄清楚。

本地矩阵-matrix

对应WebGL在3D空间的旋转、平移、缩放概念。

一个对象的本地矩阵.materix包含了该对象的旋转、平移和缩放变换,本地矩阵是平移矩阵、缩放矩阵和旋转矩阵的乘积。

世界矩阵-matrixWorld

了解Three.js中层级模型的概念,Threejs如何通过Group来创建一个父子关系层级模型。

一个对象的世界矩阵.matrixWorld是该对象本地矩阵及其所有所有祖宗对象本地矩阵的乘积,或者每一个对象的世界矩阵是对象本地矩阵和父对象的世界矩阵的乘积。

可以看到物体的世界矩阵就是其父元素的世界矩阵乘以自己的本地矩阵,这是一个递归调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
updateMatrixWorld( force ) {
if ( this.matrixAutoUpdate ) this.updateMatrix();
if ( this.matrixWorldNeedsUpdate || force ) {
if ( this.parent === null ) {
this.matrixWorld.copy( this.matrix );
} else {
// 物体的世界矩阵就是其父元素的世界矩阵乘以自己的本地矩阵
this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix );
}

this.matrixWorldNeedsUpdate = false;
force = true;
}

// 递归调用,update children
const children = this.children;
for ( let i = 0, l = children.length; i < l; i ++ ) {
children[ i ].updateMatrixWorld( force );
}
}

源码信息

Object3D

Object3D.js也是位于core下面的,这个设计在可视化领域已经是一个约定俗成的原则了。

里面引入了math目录下的四元数、欧拉角、三维矩阵、四维矩阵等。

Matrix4.js中,有个计算三种矩阵乘积的方法:

1
compose( position, quaternion, scale )

几个关键的API

1
2
3
4
var worldPosition = new THREE.Vector3();
mesh.getWorldPosition(worldPosition)
console.log('世界坐标',worldPosition);
console.log('本地坐标',mesh.position);

Vector3.setFromMatrixPosition(Object3D.matrixWorld)

将物体坐标转换为画布坐标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* 将3D物体的坐标转换为2D的画布坐标
* 注意,这个似乎很耗费性能,我笔记本发热严重,待验证
* @param obj - Object3D对象
* @param camera - 相机对象
* @param element - 画布父元素的DOM对象
* @returns
*/
export function get2DCoordinateOfObject3D(
obj: THREE.Object3D,
camera: THREE.Camera,
element: HTMLElement
) {
// 创建一个3D向量存储坐标信息
const vector = new THREE.Vector3();
// 注意:必须要主动触发世界矩阵的更新,否则物体执行了平移、缩放、旋转后,最终算出来的位置还是操作之前的位置
obj.updateMatrixWorld();
// obj.matrixWorld是已经计算了obj坐标后的Matrix4,这样才能递归计算所有物体的世界坐标
vector.setFromMatrixPosition(obj.matrixWorld);
// 因为相机已经对屏幕坐标做了矩阵计算,所以这里只需要对相机做个project就可以了
vector.project(camera);

const width = element.clientWidth;
const height = element.clientHeight;

const halfWidth = width / TWO;
const halfHeight = height / TWO;

// TODO:这里涉及一个知识点:屏幕坐标系方向(左下角)和笛卡尔坐标系(左上角)不一样
return {
x: Math.round(vector.x * halfWidth + halfWidth),
y: Math.round(-vector.y * halfHeight + halfHeight),
};
}

Vector3中的project方法:

1
2
3
project( camera ) {
return this.applyMatrix4( camera.matrixWorldInverse ).applyMatrix4( camera.projectionMatrix );
}

Vector3中的setFromMatrixPosition方法:

1
2
3
4
5
6
7
8
9
setFromMatrixPosition( m ) {
const e = m.elements;

this.x = e[ 12 ];
this.y = e[ 13 ];
this.z = e[ 14 ];

return this;
}

12、13、14是矩阵的最后一行,即存储的坐标信息。

参考资料

【精】本地矩阵与世界矩阵:

http://www.yanhuangxueyuan.com/doc/Three.js/matrixWorld.html