三维三体
显示所有步骤
之前的两体,三体,N体都是在同一平面空间里的,而我们真实的宇宙是三维空间的, 天体在空间中的定位有三个坐标。现在我们来要看看在三维宇宙空间里万有引力作用下的三体, 先看看运行效果, 可以触摸(鼠标左键)滑动,
绘制三维空间效果比较复杂,浏览器提供的API函数,不能简单实现,所以用了个第三方库来做基础:
首先我们对万有引力在二维平面空间的实现代码做了升级- 在二维坐标x, y的基础上增加了代表第三维的z坐标, 原来相应的函数,计算代码只需做了简单的扩展:
function distance3(A, B){
var dX = A.x - B.x;
var dY = A.y - B.y;
var dZ = A.z - B.z;
return Math.sqrt(dX*dX + dY*dY + dZ*dZ);
}
function force3(A, B, d){
if ( d === undefined ) d = distance3(A,B);
if ( !d ) return 0;
return G * A.m * B.m / d / d;
}
function direction3(A, B, normalize, dist){
if (normalize){
if (!dist) dist = distance3(A,B);
if(!dist)
return {x: 0, y: 0, z: 0};
else
return { x: (B.x-A.x)/dist, y: (B.y-A.y)/dist, z: (B.z-A.z)/dist};
}
else{
return {x: B.x - A.x, y: B.y - A.y, z: B.z - A.z};
}
}
function updateObject3(obj, scalarF, normDirect, deltaT){
obj.forced.x = scalarF * normDirect.x;
obj.forced.y = scalarF * normDirect.y;
obj.forced.z = scalarF * normDirect.z;
obj.acc.x = obj.forced.x / obj.m;
obj.acc.y = obj.forced.y / obj.m;
obj.acc.z = obj.forced.z / obj.m;
obj.dv.x = obj.acc.x * deltaT;
obj.dv.y = obj.acc.y * deltaT;
obj.dv.z = obj.acc.z * deltaT;
obj.x = obj.x + (obj.v.x + obj.dv.x/2) * deltaT;
obj.y = obj.y + (obj.v.y + obj.dv.y/2) * deltaT;
obj.z = obj.z + (obj.v.z + obj.dv.z/2) * deltaT;
obj.v.x = obj.v.x + obj.dv.x;
obj.v.y = obj.v.y + obj.dv.y;
obj.v.z = obj.v.z + obj.dv.z;
}
function calcUpdate2Object3(A, B, deltaT){
var d = distance3(A, B);
var f = force3(A, B, d);
var fAB = direction3(A, B, true, d);
var fBA = {x: -fAB.x, y: -fAB.y, z: -fAB.z};
updateObject3(A, f, fAB, deltaT);
updateObject3(B, f, fBA, deltaT);
}
function makeObject3(name, m, r, x, y, z, v, c){
return {
name: name,
m: m,
r: r,
x: x || 0,
y: y || 0,
z: z || 0,
v: v || {x: 0, y: 0, z: 0},
color: c || 'red',
forced: {x: 0, y: 0, z: 0},
acc: {x: 0, y: 0, z: 0},
dv: {x: 0, y: 0, z: 0},
path: [x, y, z],
};
}
然后增加了些三维空间图形显示相关的几个函数:
function createNewLine(startingPoint, lineMat, scene){
var geometry = new THREE.Geometry();
for (var i=0; i< max_len; i++){
geometry.vertices.push(startingPoint.clone());
}
var theLine = new THREE.Line(geometry, lineMat);
theLine.geometry.dynamic = true;
scene.add(theLine);
return theLine
}
function addPoint(myPoint, theLine) {
theLine.geometry.vertices.push(theLine.geometry.vertices.shift());
theLine.geometry.vertices[max_len-1] = myPoint;
theLine.geometry.verticesNeedUpdate = true;
}
function createSkySphere(radius, scene, camera, imgUrl) {
var texture = new THREE.TextureLoader().load(imgUrl);
var matSky = new THREE.MeshPhongMaterial({ map: texture, overdraw: 0.5 } );
matSky.side = THREE.BackSide;
var sphereSky = new THREE.SphereGeometry(radius, 128 , 128);
var skyMesh = new THREE.Mesh( sphereSky, matSky );
scene.add(skyMesh);
skyMesh.position.set(camera.position.x, camera.position.y, camera.position.z);
return skyMesh;
}
function render(renderer, camera, scene, target) {
renderer.clear();
camera.lookAt(target.position);
renderer.render(scene, camera);
}
最后是对上面几个方面代码对综合调用,完成计算,更新,交互,展示,代码稍复杂点:
var canvas = document.getElementById('bodies-canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var spaceUnit = canvas.width/ 1000;
var posX = 0 * spaceUnit, posY = 0 * spaceUnit, posZ = 0 * spaceUnit;
var mass = 1.0 * Math.pow(10, 14);
var radius = spaceUnit * 40;
var x = posX;
var y = posY;
var z = posZ;
var velocity = {x:0.1,y:0, z:0};
var color = 'red';
var objA = makeObject('objA',
mass, radius, x, y, z, velocity, color)
mass = 1.0 * Math.pow(10, 11);
radius = spaceUnit * 20;
x = posX + 200*spaceUnit;
y = posY + 200*spaceUnit;
z = posZ + 200*spaceUnit;
velocity = {x:0.0, y:-1, z:2.5};
color = 'blue';
var objB = makeObject('objB',
mass, radius, x, y, z, velocity, color)
mass = 1.0 * Math.pow(10, 12);
radius = spaceUnit * 15;
x = posX;
y = posY - 400*spaceUnit;
z = posZ;
velocity = {x:1, y:0.5, z:2.0};
color = 'orange';
var objC = makeObject('objC',
mass, radius, x, y, z, velocity, color)
console.info(objA);
console.info(objB);
console.info(objC);
var camera, scene, renderer, controls;
var fov = 60, near = 1, far = 10000 * spaceUnit;
var aspect = canvas.width / canvas.height;
var meshA, meshB, meshC;
var lineA, lineB, lineC;
var matA = new THREE.LineBasicMaterial({linewidth: 1, color: objA.color});
var pointA = new THREE.Vector3(objA.x, objA.y, objA.z);
var matB = new THREE.LineBasicMaterial({linewidth: 1, color: objB.color});
var pointB = new THREE.Vector3(objB.x, objB.y, objB.z);
var matC = new THREE.LineBasicMaterial({linewidth: 1, color: objC.color});
var pointC = new THREE.Vector3(objC.x, objC.y, objC.z);
function init() {
camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
scene = new THREE.Scene();
scene.add(new THREE.AmbientLight(0x999999));
controls = new THREE.OrbitControls( camera );
lineA = createNewLine(pointA, matA, scene);
lineB = createNewLine(pointB, matB, scene);
lineC = createNewLine(pointC, matC, scene);
meshA = new THREE.Mesh(
new THREE.SphereBufferGeometry( objA.r, 16, 8 ),
new THREE.MeshBasicMaterial( { color: objA.color, wireframe: true } )
);
scene.add( meshA );
meshA.position.set(objA.x, objA.y, objA.z);
meshB = new THREE.Mesh(
new THREE.SphereBufferGeometry( objB.r, 16, 8 ),
new THREE.MeshBasicMaterial( { color: objB.color, wireframe: true } )
);
scene.add( meshB );
meshB.position.set(objB.x, objB.y, objB.z);
meshC = new THREE.Mesh(
new THREE.SphereBufferGeometry( objC.r, 16, 8 ),
new THREE.MeshBasicMaterial( { color: objC.color, wireframe: true } )
);
scene.add( meshC );
meshC.position.set(objC.x, objC.y, objC.z);
var maxX = Math.max(meshA.position.x + objA.r,
meshB.position.x + objB.r,
meshC.position.x + objC.r);
var maxY = Math.max(meshA.position.y + objA.r,
meshB.position.y + objB.r,
meshC.position.y + objC.r);
var maxZ = Math.max(meshA.position.z + objA.r,
meshB.position.z + objB.r,
meshC.position.z + objC.r);
camera.position.x = maxX;
camera.position.z = maxZ + 1 + 1500*spaceUnit;
console.log('camera.position:', camera.position);
var r = 2 * Math.max(maxX, maxY, maxZ, camera.position.z);
createSkySphere(r, scene, camera, '/images/newton-law/space.jpg');
renderer = new THREE.WebGLRenderer({canvas: canvas, antialias: true});
renderer.setSize(canvas.width, canvas.height);
window.addEventListener('resize', onWindowResized, false);
onWindowResized(null);
}
function onWindowResized(event) {
if (canvas && renderer && camera){
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
renderer.setSize(canvas.width, canvas.height);
renderer.setViewport(0, 0, canvas.width, canvas.height);
camera.aspect = canvas.width / canvas.height;
camera.updateProjectionMatrix();
}
}
var limitDraw = 1000/30, limitCalc = 1000/30, calcTimes = 10;
var lastDrawTime = Date.now() - limitDraw;
var lastCalcTime = Date.now() - limitCalc;
function update() {
var now = Date.now();
var deltaCalc = now - lastCalcTime;
if (deltaCalc >= limitCalc) {
lastCalcTime = now;
for(var i = 0; i < calcTimes; i++){
calcUpdate2Object(objA, objB, limitCalc/1000);
calcUpdate2Object(objA, objC, limitCalc/1000);
calcUpdate2Object(objB, objC, limitCalc/1000);
}
}
var deltaDraw = now - lastDrawTime;
if (deltaDraw >= limitDraw) {
meshA.position.set(objA.x, objA.y, objA.z);
meshB.position.set(objB.x, objB.y, objB.z);
meshC.position.set(objC.x, objC.y, objC.z);
meshA.rotation.y += 0.005;
meshB.rotation.y += 0.01;
meshC.rotation.y += 0.01;
pointA = new THREE.Vector3(objA.x, objA.y, objA.z);
addPoint(pointA, lineA);
pointB = new THREE.Vector3(objB.x, objB.y, objB.z);
addPoint(pointB, lineB);
pointC = new THREE.Vector3(objC.x, objC.y, objC.z);
addPoint(pointC, lineC);
controls.update(deltaDraw);
render(renderer, camera, scene, meshA);
}
requestAnimationFrame(update);
}
init();
update();
最后(全屏体验):