看过《三体》《eva》《星际穿越》等的朋友一定都对高维物体运动感兴趣,今天我们就来将它实现一下。我们选用threejs来做,如果要做更高要求的,运用相同原理重写一下就行啦。下面开始。
第一篇我们来讲讲基本概念:
1.体的边界(包括二维图形,三维体,四维及更多维的超体)
我们称一个东西为有限体,即该体有边界。那么这体的外部和内部怎么区分呢?用集合论的定义。通俗说,在一个空间中,有一个点集为a,如果剩下的点可以分为集合b和c,其中b和c满足:(从b中任意一点到c中任意一点都不存在这样的路径d:(d不包含a中的任何点)),那么a可称为该空间中某体e的边界。进一步,如果b中任意两点距离存在最大值,c中任意两点距离无最大值,则称b为体的内部,c为体的外部。简单上图:
2.四维超体
因为我们活在三维世界(因为我们最多只能找到三个不同向量,他们满足两两之间夹角相等。说到这儿,其实我们是可以像画三维坐标在二维平面上的投影一样画四维坐标在二维平面的投影。关于投影的概念,下面会细述),所以我们并不好想象四维空间。但我们可以用万能的向量来描述。四维空间就是由有四个分量的向量构成的空间,不妨设为(x,y,z,w)。四维超体,我们就先理解为有限体的的边界加内部点的集合就行。
3.投影
点a在某体(包括线,面,体,超体等)上的投影,几何上讲即从a点做直线垂直于该体,并与该体交于点b,点b即所求投影。其实就是求体上的某点b,使向量ab与体中的任意向量内积为0。我们先来考虑投影在坐标轴构成的线,面,体等等上的情况。很显然,点(x,y,z,w)投影在x轴上就是y,z,w分量为0,投影在xy平面上就是z,w分量位0,投影在xyz空间上就是w分量为0。那么投影在非坐标轴构成空间上呢?比如投影在平面上,使用叉乘公式:
det
其中a,b为平面上不在一条直线上的两向量,i,j,k为x,y,z方向上的三个基向量。同理,其他更高维的空间也是一样的,比如四维空间中点投影在某三维空间中:
det
其中a,b,c为所给三维空间中不在一个平面上的向量(即一组基向量)
4,交集
四维物体可能有一部分点在我们所处空间中,即所有分量w为0的点我们都能观察到。进一步推广,求某四维体m在三维空间,如线性空间ax+by+cz+dw=e(其中a,b,c,d,e为常量)中的点,通过联立一下方程可求。
5,旋转
讲到旋转,就是动态的一个东西了,这是为了让我们更好地观察高维物体不得不引入的概念,它涉及到了速度的概念。在此,我们简单定义一下:
点a在绕体b做旋转运动,指的是点a当前速度方向与该体垂直,且点a的当前速度方向也垂直于点a到该体的距离向量。四维体绕某平面旋转,即四维体中每个点都在绕该平面旋转。这里我们先说简单的,比如四维空间中u点绕zw平面旋转(即在某平行于xy平面(垂直于zw平面)的平面旋转),根据旋转公式推广得(x1,y1,z1,w1)=(x0,y0,z0,w0)M,
其中M=,(x0,y0,z0,w0)为u点原位置,(x1,y1,z1,w1)为u点旋转a角度后位置。围绕其他一般平面的,我们先写个思路,可以通过联立方程求出投影点(联立方程:1,满足投影点在该平面上;2,满足u到其投影点构成的向量垂直于平面上的基向量。此方法亦可用与四维点在三维空间中的投影以及更高维情况),然后求出运动方向单位向量v,再将旋转夹角进行正交分解,得axy,axz...即得Mxy,Mxz,Mxw,Myz...它们的成积就是所求M
说了那么多,主要是想表明对于高维空间,我们必须多使用集合,矩阵,向量等通用概念和方法去描述,这样我们才好理解和推理。如果还有什么疏漏没说,下篇博客再说吧
######################################################################################
附:四维超体在三维空间的投影
几个关键点:
1、泛正方体
二维中为方形:
-a<x<a , -a<y<a
三维中为正方体
-a<x<a , -a<y<a, -a<z<a
四维中为超立方体
-a<x<a,-a<y<a,-a<z<a,-a<m<a
可理解为我们的世界为m为0点处的四维世界,由于上述超立方体中x,y,z取值范围与m无关,当该超体与我们世界相交,即m取值在-a到a之中时,其他三维取值范围不变,在我们世界中始终未一个立方体。现通过正交变换对立方体进行旋转变换:
x=1,0,0,0
y=0,1,0,0
z=0,0,1,0
m=0,0,0,1
v1 = a1x+b1y+c1z+d1m
v2 = a2x+b2y+c2z+d2m
v3 = a3x+b3y+c3z+d3m
v4 = a4x+b4y+c4z+d4m
v1,v2,v3,v4两两正交
可得其中一解为
1,1,1,1
1,1,-1,-1
1,-1,1,-1
1,-1,-1,1
得表达式
-a<x+y+z+m<a ,-a< x+y-z-m<a, -a< x-y+z-m<a, -a< x-y-z+m<a
m取值在-a到a之中时,其他三维取值发生改变
以下为源代码:
var container;
var renderer;
var scene;
var camera;
var group;
var i = 0;
var t = -10;
var swi_tch = 1;
var daz = 0.2;
function circle(){
scene.remove(group);
requestAnimationFrame(circle);
camera.lookAt(scene.position);
persSuperCube(t);
if(swi_tch==1){
t = t + 0.5;
if(t>30){
swi_tch=0;
}
}
else{
t = t - 0.5;
if(t<-30){
swi_tch=1;
}
}
}
function onKeyDown(e){
//alert(e.keyCode);
if(e.keyCode==32){
circle();
}
}
function persSuperCube(t){
group = new THREE.Group();
var meshsss = [];
for(var i=0;i<24;i++)
for(var j=0;j<24;j++)
for(var k=0;k<24;k++)
{
if(meshsss[i]==null){
meshsss[i]=[];
}
if(meshsss[i][j]==null){
meshsss[i][j]=[];
}
var x=i-12;
var y=j-12;
var z=k-12;
var m=t-12;
if(x+y+z+m>-12 && x+y+z+m<12 && x+y-z-m>-12 && x+y-z-m<12 && x-y+z-m>-12 && x-y+z-m<12 && x-y-z+m>-12 && x-y-z+m<12){
meshsss[i][j][k]=new THREE.Mesh(
new THREE.BoxGeometry(2, 2, 2 ),
new THREE.MeshNormalMaterial( { overdraw: 0.5 } )
);
meshsss[i][j][k].position.x = 2*x;
meshsss[i][j][k].position.y = 2*y;
meshsss[i][j][k].position.z = 2*z;
meshsss[i][j][k].matrixAutoUpdate = false;
meshsss[i][j][k].updateMatrix();
group.add(meshsss[i][j][k]);
}
}
scene.add(group);
renderer.render(scene,camera);
}
function init(){
//document.addEventListener( 'keydown', onKeyDown, false );
container = document.createElement( 'div' );
document.body.appendChild( container );
group = new THREE.Group();
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.x = 140*Math.sin(Math.PI/4);
camera.position.y = 140*Math.cos(Math.PI/4);
camera.position.z = 150;
camera.rotation.x = -Math.atan(camera.position.x/camera.position.z);
camera.rotation.y = Math.atan(camera.position.y/camera.position.z);
camera.rotation.x = -0.25 * Math.PI;
camera.rotation.y = 0.25 * Math.PI;
scene = new THREE.Scene();
scene.add(camera);
var x, y,z=0;
var meshsss = [];
for(var i=0;i<40;i++)
for(var j=0;j<40;j++)
for(var k=0;k<40;k++){
if(meshsss[i]==null){
meshsss[i]=[];
}
if(meshsss[i][j]==null){
meshsss[i][j]=[];
}
x=i-20;
y=j-20;
z=k-20;
if(x+y+z>-15 && x+y+z<15 && x-y+z>-15 && x-y+z<15 && -x+y+z>-15 && -x+y+z<15 && -x-y+z>-15 && -x-y+z<15){
meshsss[i][j][k]=new THREE.Mesh(
new THREE.BoxGeometry(2, 2, 2 ),
new THREE.MeshNormalMaterial( { overdraw: 0.5 } )
);
meshsss[i][j][k].position.x = 2*x;
meshsss[i][j][k].position.y = 2*y;
meshsss[i][j][k].position.z = 2*z;
meshsss[i][j][k].matrixAutoUpdate = false;
meshsss[i][j][k].updateMatrix();
group.add(meshsss[i][j][k]);
}
}
scene.add(group);
var meshx = new THREE.Mesh(
new THREE.BoxGeometry(200, 1, 1 ),
new THREE.MeshNormalMaterial( { overdraw: 0.5 } )
);
meshx.position.x = 100;
//meshx.rotation.z = 0.25 * Math.PI;
meshx.matrixAutoUpdate = false;
meshx.updateMatrix();
scene.add(meshx);
var meshy = new THREE.Mesh(
new THREE.BoxGeometry(1, 200, 1 ),
new THREE.MeshNormalMaterial( { overdraw: 0.5 } )
);
meshy.position.y = 100;
//meshy.rotation.z = 0.25 * Math.PI;
meshy.matrixAutoUpdate = false;
meshy.updateMatrix();
scene.add(meshy);
var meshz = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 200 ),
new THREE.MeshNormalMaterial( { overdraw: 0.5 } )
);
meshz.position.z = 100;
//meshz.rotation.x = 0.25 * Math.PI;
meshz.matrixAutoUpdate = false;
meshz.updateMatrix();
scene.add(meshz);
renderer = new THREE.CanvasRenderer();
renderer.setClearColor( 0xffffff );
//renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
renderer.render(scene,camera);
circle();
}