console
const pi = Math.PI;
const deg90 = pi / 2;
function getRandomColor() {
var arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
var color = "#";
for (let i = 0; i < 6; i++) {
let random = parseInt(Math.random() * 16);
color += arr[random];
}
return color;
}
function vecSum(v1, v2) {
return [v1[0] + v2[0], v1[1] + v2[1]];
}
function vecMinus(v1, v2) {
return [v1[0] - v2[0], v1[1] - v2[1]];
}
function vecMold(vec) {
return Math.hypot(vec[0], vec[1]);
}
function vecZoom(vec, num) {
if (isNaN(vec[0]) || isNaN(vec[1])) {
console.error("向量数乘错误!", vec, " 乘以 ", num)
return [0, 0];
}
return [num * vec[0], num * vec[1]];
}
function vecAngle(vec) {
return Math.atan(vec[1] / vec[0]);
}
function vecRotate(vec, angle) {
let tempVec = [0, 0];
tempVec[0] = vec[0] * Math.cos(angle) - vec[1] * Math.sin(angle);
tempVec[1] = vec[0] * Math.sin(angle) + vec[1] * Math.cos(angle);
return tempVec;
}
function getDistance(v1, v2) {
return vecMold(vecMinus(v1, v2));
}
function getMassCenter(starArr) {
let vector = [0, 0];
let sumMass = 0;
starArr.forEach(star => {
if (star != null) {
vector = vecSum(vector, vecZoom(star.pos, star.mass));
sumMass += star.mass;
}
});
return vecZoom(vector, 1 / sumMass);
}
function getGravity(s1, s2) {
let dis = getDistance(s1.pos, s2.pos);
return vecZoom(vecZoom(vecMinus(s2.pos, s1.pos), 1 / dis), G * s1.mass * s2.mass / dis);
}
function getBump(s1, s2) {
let newstar = new Star(
s1.id,
getMassCenter([s1, s2]),
Math.pow(s1.radius ** 3 + s2.radius ** 3, 1 / 3),
s1.mass + s2.mass,
[(s1.vel[0] * s1.mass + s2.vel[0] * s2.mass) / (s1.mass + s2.mass),
(s1.vel[1] * s1.mass + s2.vel[1] * s2.mass) / (s1.mass + s2.mass)
]
);
return newstar;
}
function transform(pos) {
return vecSum(vecZoom(pos, zoomIndex), vecSum([hosX, hosY], offsetUser));
}
function zoom(type) {
offsetStep = parseInt(document.getElementById('offsetStep').value);
switch (type) {
case 'out':
zoomIndex *= 0.8;
break;
case 'in':
zoomIndex *= 1.2;
break;
case 'center':
let center = getMassCenter(stars);
stars.forEach(star => {
star.pos = vecMinus(star.pos, center)
})
break;
case 'left':
offsetUser = vecSum(offsetUser, [offsetStep, 0])
break;
case 'right':
offsetUser = vecSum(offsetUser, [-offsetStep, 0])
break;
case 'up':
offsetUser = vecSum(offsetUser, [0, offsetStep])
break;
case 'down':
offsetUser = vecSum(offsetUser, [0, -offsetStep])
break;
default:
console.error("错误的zoom type!");
break;
}
flipAll();
}
function Star(id, pos, radius, mass, vel) {
this.id = id || 0;
this.pos = [0, 0];
this.pos[0] = pos[0] || 0;
this.pos[1] = pos[1] || 0;
this.radius = radius || 100;
this.mass = mass || 10;
this.vel = [0, 0];
this.vel[0] = vel[0] || 0;
this.vel[1] = vel[1] || 0;
this.color = getRandomColor();
this.render = function () {
ctx.beginPath();
tpos = transform(this.pos)
ctx.arc(tpos[0], tpos[1], this.radius * zoomIndex, 0, 7, false);
ctx.fillStyle = this.color;
ctx.fill();
}
}
function createNewStar() {
let tempStar = new Star(1, [100, -100], 10, 100, [0, 0]);
let tempID = 0;
let idPool = [];
stars.forEach(star => {
idPool[star.id] = true;
});
for (let i = 0; i < idPool.length + 1; i++) {
if (idPool[i] == undefined) {
tempID = i;
}
}
tempStar.id = tempID;
tempStar.mass = parseInt(prompt("输入天体质量")) || 150;
tempStar.radius = parseInt(prompt("输入天体半径")) || 10;
tempStar.pos[0] = parseInt(prompt("输入天体位置 x分量")) || 0;
tempStar.pos[1] = parseInt(prompt("输入天体位置 y分量")) || 0;
tempStar.vel[0] = parseInt(prompt("输入天体速度 x分量")) || 0;
tempStar.vel[1] = parseInt(prompt("输入天体速度 y分量")) || 0;
console.log("创建新天体!", tempStar);
stars.push(tempStar)
flipAll();
};
function flipAll() {
ctx.fillStyle = "#F1F3F4";
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < stars.length; i++) {
stars[i].render();
}
ctx.fillStyle = "red";
let temp = transform(getMassCenter(stars));
ctx.fillRect(temp[0], temp[1], 3, 3);
}
var canvas = document.getElementById('_canvas');
var ctx = canvas.getContext('2d');
ctx.strokeStyle = 'black';
var hosX = canvas.width / 2
var hosY = canvas.height / 2;
var zoomIndex = 1;
var offsetX = 0;
var offsetY = 0;
var isRunning = true;
var clocker;
var clockTime;
var ultraClockNum;
var offsetUser = [0, 0];
var gui;
var centerForever;
var G;
var delta_t;
var options;
var star1 = new Star(1, [100, -100], 12, 100, [-15, 0]);
var star2 = new Star(2, [-100, -200], 20, 150, [-2, 0]);
var star3 = new Star(3, [-300, 350], 17, 125, [4, 5]);
var stars = [star1, star2, star3];
var MODS = [{}, {}, {}, {}, {}];
function tabRunning() {
if (isRunning) {
clearInterval(clocker);
} else {
clocker = setInterval(main, clockTime);
}
isRunning = !isRunning;
}
var Options = function () {
this.message = '未来生物研究所™ 出品';
this.引力常数G = 0.5;
this.时间步长 = 0.05;
this.刷新周期 = 1;
this.永远居中 = false;
this.如果一切能重来 = function () {
window.location.reload()
};
this.创建新天体 = createNewStar;
this.保存展开状态 = function () {
gui.saveToLocalStorageIfPossible()
}
this.超频 = function () {
if (confirm('警告:\n超频为不可逆操作,一旦开始就TM停不下来!\n(超频好像还能叠加的,小心显卡烧糊哟~)')) {
setInterval(main, clockTime);
}
}
};
function initGUI() {
options = new Options();
gui = new dat.GUI();
let f1 = gui.addFolder("大局观");
f1.add(options, 'message');
f1.add(options, '引力常数G', -5, 10);
f1.add(options, '时间步长', 0.01, 0.1);
f1.add(options, '刷新周期', 0, 200);
f1.add(options, '永远居中');
let f2 = gui.addFolder("神经病");
f2.add(options, '超频')
f2.add(options, '保存展开状态')
f2.add(options, '如果一切能重来');
let f3 = gui.addFolder('对象');
f3.add(options, '创建新天体');
}
function updateOptions() {
G = options.引力常数G;
delta_t = options.时间步长;
if (options.永远居中) {
zoom('center')
}
ultraClockNum = options.超频;
if (clockTime != options.刷新周期) {
clockTime = options.刷新周期;
clearInterval(clocker);
clocker = setInterval(main, clockTime);
}
}
var isLLZL = false;
stat = Stats();
stat.domElement.style.position = 'absolute';
stat.domElement.style.right = '0px';
stat.domElement.style.top = '0px';
stat.domElement.style.right = null;
document.body.appendChild(stat.domElement);
initGUI();
updateOptions();
tabRunning();
flipAll();
function main() {
stat.begin();
stars.forEach(star => {
let acc = [0, 0];
stars.forEach(objStar => {
if (objStar.id != star.id) {
acc = vecSum(acc, vecZoom(getGravity(star, objStar), 1 / star.mass));
if (vecMold(vecMinus(star.pos, objStar.pos)) < (star.radius + objStar.radius)) {
stars = stars.filter((starF) => {
return starF.id != star.id && starF.id != objStar.id;
});
let newStar = getBump(star, objStar)
stars.push(newStar);
console.log("碰撞发生了,生成了天体:", newStar);
}
}
});
if (isLLZL) {
acc = vecSum(acc, vecZoom(vecRotate(star.vel, deg90), 0.8));
}
star.vel = vecSum(star.vel, vecZoom(acc, delta_t));
star.pos = vecSum(star.pos, vecZoom(star.vel, delta_t));
});
updateOptions();
flipAll();
stat.end();
}
<!DOCTYPE html>
<html lang="ch">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="beauty.css">
<script src="stat.js"></script>
<script src="dat-gui.js"></script>
<title>Three-body Problem</title>
</head>
<body>
<div>
<canvas height="800" width="1000" id="_canvas"></canvas>
</div>
<div>
<button class="btn" onclick="zoom('out')">缩小-</button>
<button class="btn" onclick="zoom('in')">放大+</button>
<button class="btn" onclick="zoom('center')" onkeydown="options.永远居中=true;">居中</button>
<button class="btn" onclick="tabRunning()">暂停/开始</button>
</div>
<div>
<button class="btn" onclick="zoom('left')">左移</button>
<button class="btn" onclick="zoom('right')">右移</button>
<button class="btn" onclick="zoom('up')">上移</button>
<button class="btn" onclick="zoom('down')">下移</button>
<input type="number" id="offsetStep" value="30">
</div>
<div>
<input type="checkbox" id="mod_llzl" value="false" onclick="isLLZL=!isLLZL">洛伦兹力</input>
</div>
</body>
</html>
<script src="数学之魂魄.js"></script>
<script src="物理之韵律.js"></script>
<script>
try {
for (var i = 1; i <= 5; i++) {
document.write("<script type=\"text/javascript\" src=\"mods/Mod_" + i + ".js\"><\/script>");
}
} catch (error) {
}
</script>
.btn {
background-color: white;
color: black;
width: auto;
height: auto;
font-size: 1.25em;
padding: 5px 10px 5px 10px;
margin: 4px 2px;
-webkit-transition-duration: 0.4s;
transition-duration: 0.4s;
cursor: pointer;
border: 2px solid #4CAF50;
border-radius: 5px;
}
.btn:hover {
background-color: #4CAF50;
color: white;
}
.btn:active {
background-color: rgb(13, 107, 19);
}
#ok {
margin-left: 25%;
width: 40%;
border: 2px solid dodgerblue;
}
#ok:hover {
background-color: dodgerblue;
color: white;
}
#ok:active {
background-color: rgb(20, 87, 155);
}
* {
font-family: "微软雅黑", Microsoft YaHei;
}
canvas {
border: 1px solid black;
background: linear-gradient(#F1F3F4, rgb(171, 173, 173));
}