两体
显示所有步骤
万有引力公式直接揭示两个物体之间的吸引力, 也就是万有引力公式是宇宙里两体运行规律的数学表达。 当然牛顿第一、第二、第三定律也有公式形式, 万有引力公式更是牛顿三大定律的精彩集成。
先看看实现的两体运行效果(全屏体验):
实现代码分三部分,一部分是十几行的html页面代码(如下黑色背景区域里的内容),剩下两部分都是javascript代码。javascript代码实现了两方面的功能,一方面是绘制图像,另一方面是用牛顿三定律及其万有引力计算物体的速度,加速度,位置等信息。
页面代码非常之少,几乎是最精简的web代码了,与一般页面不同的是定义了一个canvas, 我们将在其中绘制万有引力下的运动物体:
<!DOCTYPE html>
<html>
<head>
<script>通用的第一部分javascript代码</script>
</head>
<body>
<div><canvas id='2-objects-canvas'></canvas></div>
<script>少量变化的第二部分javascript代码</script>
</body>
<html>
通用的第一部分javascript代码里,大部分是我们根据牛顿定律,万有引力公式实现的几个函数:
var G = 6.67259 * Math.pow(10, -11);
var requestAnimationFrame = window.requestAnimationFrame
|| window.mozRequestAnimationFrame
|| window.webkitRequestAnimationFrame
|| window.msRequestAnimationFrame;
function drawObject(context, obj) {
context.beginPath();
context.arc(obj.x, obj.y, obj.r, 0, Math.PI * 2, true);
context.closePath();
context.fillStyle = obj.color;
context.fill();
context.beginPath();
context.strokeStyle = obj.color;
for(var i=0; i < obj.path.length; i++){
if (i === 0){
context.moveTo(obj.path[i].x, obj.path[i].y);
}
else{
context.lineTo(obj.path[i].x, obj.path[i].y);
}
}
context.stroke();
context.closePath();
}
function distance(A, B){
var dX = A.x - B.x;
var dY = A.y - B.y;
return Math.sqrt(dX*dX + dY* dY);
}
function force(A, B, d){
if ( d === undefined ) d = distance(A,B);
if ( !d ) return 0;
return G * A.m * B.m / d / d;
}
function direction(A, B, normalize, dist){
if (normalize){
if (!dist) dist = distance(A,B);
if(!dist)
return {x: 0, y: 0};
else
return { x: (B.x - A.x)/dist, y: (B.y - A.y)/dist};
}
else{
return {x: B.x - A.x, y: B.y - A.y};
}
}
function updateObject(obj, scalarF, normDirect, deltaT){
obj.forced.x = scalarF * normDirect.x;
obj.forced.y = scalarF * normDirect.y;
obj.acc.x = obj.forced.x / obj.m;
obj.acc.y = obj.forced.y / obj.m;
obj.dv.x = obj.acc.x * deltaT;
obj.dv.y = obj.acc.y * 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.v.x = obj.v.x + obj.dv.x;
obj.v.y = obj.v.y + obj.dv.y;
obj.path.push( {x: obj.x, y: obj.y});
if (obj.path.length > max_len){
obj.path = obj.path.slice(-max_len);
}
}
function transformObit(obj, offset){
obj.x = obj.x + offset.x;
obj.y = obj.y + offset.y;
for(var i=0; i < obj.path.length; i++){
obj.path[i].x = obj.path[i].x + offset.x;
obj.path[i].y = obj.path[i].y + offset.y;
}
}
function calcUpdate2Object(A, B, deltaT){
var d = distance(A, B);
var f = force(A, B, d);
var fAB = direction(A, B, true, d);
var fBA = {x: -fAB.x, y: -fAB.y};
updateObject(A, f, fAB, deltaT);
updateObject(B, f, fBA, deltaT);
}
function makeObject(name, m, r, x, y, v, c){
return {
name: name,
m: m,
r: r,
x: x,
y: y,
v: v,
color: c,
forced: {x: 0, y: 0},
acc: {x: 0, y: 0},
dv: {x: 0, y: 0},
path: [],
};
}
少量变化的第二部份代码,是根据上面的公式计算刷新数据,并根据数据更新绘制两体到页面上:
var max_len = 1024 * 30;
var canvas = document.getElementById('2-objects-canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var context = canvas ? canvas.getContext('2d') : null;
var posX = 250, posY = canvas.height * 2 / 3;
var objA = makeObject('sun',1.000 * Math.pow(10, 14), 30,
posX, posY, {x:0.2,y:0}, 'red')
var objB = makeObject('earth',100.000 * Math.pow(10, 10), 8,
posX, posY-400, {x:2.2,y:0}, 'blue')
var limitDraw = 1000/30, limitCalc = 10, calcTimes = 10;
var lastDrawTime = Date.now() - limitDraw;
var lastCalcTime = Date.now() - limitCalc;
function update(){
var now = Date.now();
var deltaDraw = now - lastDrawTime;
if (context && deltaDraw >= limitDraw) {
lastDrawTime = now;
context.fillStyle = 'black';
context.fillRect(0, 0, canvas.width, canvas.height);
drawObject(context, objB);
drawObject(context, objA);
}
var deltaCalc = now - lastCalcTime;
if (deltaCalc >= limitCalc) {
lastCalcTime = now;
for(var i = 0; i < calcTimes; i++){
calcUpdate2Object(objA, objB, limitCalc/1000);
}
}
requestAnimationFrame(update);
}
requestAnimationFrame(update);
整个代码里我们没有使用任何第三方代码,三部分全部加起来不到两百行。在后面三体有变化的部分,是根据物体个数而做少量调整, 这个在下一步我们将看到。