为什么这么久不更博?因为没时间+懒

效果如下所示~

为了防止拖慢网页,点开more查看吧~

Your browser does not support the canvas element.

前言

其实这是寒假时的老项目了,后来改成了Javascript版本 而且代码风格十分混乱

大概就是花了两天推了一堆公式,找出了球体弹性碰撞的公式,然后就拿コンピュータ实现了一下ww

虽然有很多混乱的地方,和极其低下的效率(毕竟我还是高一诶写不出高级的也正常吧)

为了分享Javascript代码,先把python代码(原始代码)拿出来

python代码

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
#!/usr/bin/python3
# -*- coding: UTF-8 -*-

# 这是原始Python代码,看不懂js版本的可以参考
import tkinter as tk # 导入 Tkinter 库
import math
import random

class Pair():
# 数据组
def __init__(self, x, y):
self.x = x
self.y = y
def __lt__(self, b):
if isinstance(b, Pair):
return self.x < b.x
else:
return NotImplemented

class Group(Pair):
# 浮点数据组类
def __init__(self, x, y):
super().__init__(x, y)
self.x = float(self.x)
self.y = float(self.y)
def __add__(self, b):
if isinstance(b, Group):
return type(self)(self.x + b.x, self.y + b.y)
else:
return NotImplemented
def __sub__(self, b):
if isi
nstance(b, Group):
return type(self)(self.x - b.x, self.y - b.y)
else:
return NotImplemented
def mod(self):
return math.sqrt(self.x*self.x+self.y*self.y)
def __lt__(self, b):
if isinstance(b, Group):
return self.x < b.x
else:
return NotImplemented

class Vect(Group):
# 向量 子类
def __init__(self, x, y):
super().__init__(x, y)
def __mul__(self, b):
if isinstance(b, Group):
return self.x*b.x+self.y*b.y
else:
return type(self)(self.x * b, self.y * b)
def __rmul__(self, a):
return type(self)(self.x * a, self.y * a)

class Pos(Group):
# 坐标 子类
def __init__(self, x, y):
super().__init__(x, y)

class Vel(Vect):
# 速度 子类
def __init__(self, x, y):
super().__init__(x, y)

class Ball():
def __init__(self, ID, pos, r=10, v = Vel(0,0), m = 50000, fill = "", locked = False):
self.ID=ID
self.pos=pos
self.r=r
self.v=v
self.m=m
self.fill = fill
self.locked=locked
def moveto(self, pos):
self.pos=pos
def move(self):
self.pos += self.v

class Timer():
# 计时器
def __init__(self, root, func, time, enabled):
self.rt = root
self.func = func
self.t=int(time)
self.enabled=enabled
if enabled:
self.enable()
def timeup(self):
self.func()
self.ti = self.rt.after(self.t, self.timeup)
def enable(self):
self.enabled = True
self.timeup()
def unable(self):
self.enabled = False
self.rt.after_cancel(self.ti)
def reset_time(self, t):
self.unable()
self.t=int(t)
self.enable()

class ID_Pool():
# ID池
def __init__(self):
self.pool=[]
self.topid=0
def getid(self):
if len(self.pool) == 0:
self.topid+=1
return self.topid
else:
return self.pool.pop()
def back(self, p):
self.pool.append(p)

Balls = []
id_pool = ID_Pool()
FPS = 60
DeadTime = 1e9
CountNum = 0

def draw_ball(ball):
if ball.fill != "":
cv.create_oval(ball.pos.x - ball.r, ball.pos.y - ball.r, ball.pos.x + ball.r, ball.pos.y + ball.r, fill = ball.fill)
else:
cv.create_oval(ball.pos.x - ball.r, ball.pos.y - ball.r, ball.pos.x + ball.r, ball.pos.y + ball.r)

def creat_ball(p, r=3, v=Vel(0,0), m=500, fill = "", locked = False):
ball_t = Ball(id_pool.getid(), p, r, v, m, fill, locked)
Balls.append(ball_t)
draw_ball(ball_t)

def del_ball(ball):
Balls.remove(ball)

def update(cv):
#更新整个画布
cv.delete("all")
for ball in Balls:
draw_ball(ball)

def solvefunc(a, b, c):
delta = math.sqrt(b*b-4*a*c)
return [(-b+delta)/(2*a), (-b-delta)/(2*a)]

def force(b1, b2):
plus = -1
x = b1.pos.x - b2.pos.x
y = b1.pos.y - b2.pos.y
dis = x*x + y*y
if dis == 0:
return
if math.sqrt(dis) < b1.r + b2.r :
plus = (b1.r + b2.r) / math.sqrt(dis)
dis = (b1.r + b2.r) ** 2
f = 0.05 * b1.m * b2.m / dis
fx = f / math.sqrt(dis) * x
fy = f / math.sqrt(dis) * y
return Vect(fx * plus, fy * plus)

def hit(b1, b2):
# 碰撞计算
global CountNum
CountNum += 100
dv = b1.v-b2.v
if dv.mod()==0:
return
if hited(b1,b2) == False:
return
dp = b2.pos-b1.pos
dt=0
if dp.mod() < b1.r+b2.r:
#print("dis = ", dp.mod(), "dv=", dv.x,",",dv.y)
a = dv.x**2 + dv.y**2
b = -2 * (dv.x * dp.x + dv.y * dp.y)
c = dp.x**2 + dp.y**2 - (b1.r+b2.r) **2
dt = solvefunc(a, b, c)[1]
b1.pos += dt * b1.v
b2.pos += dt * b2.v
#print("dt=", dt)
# 球恢复到“碰撞瞬间”

b1.v-=b2.v
dp = b2.pos-b1.pos
alpha = 0

#print("now dis = ", dp.mod(), "dv=", dv.x,",",dv.y)
if dp.x == 0:
if dp.y > 0:
alpha = math.asin(1)
else:
alpha = math.asin(-1)
else:
alpha = math.atan(dp.y/dp.x)
v0 = b1.v * Vel(math.cos(alpha), math.sin(alpha))
v0c = b1.v - (v0 * Vel(math.cos(alpha), math.sin(alpha)))
#print("v0=" , v0, "vel=", Vel(math.cos(alpha), math.sin(alpha)).x, Vel(math.cos(alpha), math.sin(alpha)).y)
b1.v=b2.v
b2.v += float((2*b1.m)/(b1.m+b2.m)) * (v0 * Vel(math.cos(alpha), math.sin(alpha)))
b1.v += float((b1.m-b2.m)/(b1.m+b2.m)) * (v0 * Vel(math.cos(alpha), math.sin(alpha))) + v0c
# 球弹开
dt = -dt
b1.pos += dt * b1.v
b2.pos += dt * b2.v

#弹性系数
#b1.v=b1.v*0.9
#b2.v=b2.v*0.9

def hited(b1, b2):
# 是否碰撞
return (b1.v-b2.v).mod() != 0 and dis(b1.pos, b2.pos) <= b1.r + b2.r - 0.00001

def count_hit():
global CountNum
stp = len(Balls)
hits = []
for i in range(0, stp - 1):
for j in range(i + 1, stp):
CountNum += 2
if hited(Balls[i], Balls[j]):
hits.append(Pair(Balls[i].r + Balls[j].r - dis(Balls[i].pos, Balls[j].pos), [Balls[i], Balls[j]]))
hits.sort()
#if len(hits):
# print(str(len(hits)))
return hits

def dis(p1, p2):
return (p1-p2).mod()

def move():
# 初次计算
energy=0
for ball in Balls:
energy+=ball.v*ball.v*ball.m
global CountNum
CountNum += 1
# ball.v.y+=1
ball.move()
if ball.pos.x <= 0:
ball.v.x *= -1
ball.pos.x = 1
if ball.pos.x >= WIDTH:
ball.v.x *= -1
ball.pos.x = WIDTH - 1
if ball.pos.y <= 0:
ball.v.y *= -1
ball.pos.y = 1
if ball.pos.y >= HEIGHT:
ball.v.y *= -1
ball.pos.y = HEIGHT - 1
#print(energy)
def main_loop():
# 主循环
global CountNum
CountNum = 0
move()
while CountNum < DeadTime:
hitlist = count_hit()
if len(hitlist) == 0:
break
for hits in hitlist:
hit((hits.y)[0], (hits.y)[1])
for ball in Balls:
for oth in Balls:
if ball.ID != oth.ID:
ball.v += 1/ball.m * force(ball, oth)
update(cv)

def mouse_press(event):
global mouseball
mouseball = creat_ball(Pos(event.x,event.y), 20, Vel(0,0), 50000, fill = "yellow")

def mouse_up(event):
global mouseball
del_ball(mouseball)

def cmd_click():
if timer.enabled:
timer.unable()
else:
timer.enable()

WIDTH = 500
HEIGHT = 500

root = tk.Tk()
root.title("Test")
root.geometry('630x520')

timer = Timer(root, main_loop, 1000/FPS, False)

cv = tk.Canvas(root, bg='skyblue', height=HEIGHT, width=WIDTH)
cmd = tk.Button(root, text="Start move!", width=int(100/7), height=int(20/17), command=cmd_click)
#tst = tk.Button(root, text="Kill Velocity", width=15, height=2, command=stopall)

#for i in [60*x for x in range(1,2)]:
# for j in [60*x for x in range(int(i/60),9)]:
# creat_ball(Pos(i,j), 20, Vel(0,0), fill = "red")
# creat_ball(Pos(200,230), 20, Vel(0,6), 500000, fill = "blue")
# creat_ball(Pos(300,270), 20, Vel(0,-6), 5000, fill = "red")

cv.bind("<ButtonRelease-1>", mouse_press)
cv.bind("<Double-Button-1>", mouse_up)



cv.place(x=5, y=5)
cmd.place(x=505 + 10, y=5)
#tst.place(x=505 + 10, y=55)

root.mainloop()

Js代码

Javascript不可以重载运算符真是恶心死了改了半天

嗯,比起python代码稍微变动了一点

首先当然是造一个Canvas

1
2
3
<canvas id="myCanvas" width="500" height="500" style="border:1px solid #c3c3c3;">
Your browser does not support the canvas element.
</canvas>

之后……

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
"use strict";
var c=document.getElementById("myCanvas");
var cxt=c.getContext("2d");
cxt.fillStyle="#FF0000";

var WIDTH = c.width, HEIGHT = c.height;
var FPS = 30;

var Balls = []

var mouse_var = {
mousedown: false,
x: 0,
y: 0,
paused_ballid: 'NaN'

}

function draw_ball(id) {
cxt.beginPath();
cxt.arc(Balls[id].pos.x,Balls[id].pos.y,Balls[id].r,0,Math.PI*2,true);
cxt.closePath();
cxt.fill();
}

function creat_ball(x,y,r=20,m=1,vx=0,vy=0, e=0) {
//创建球
Balls.push({
pos: {
x: x,
y: y
}, // 直角坐标位置
r: r, // 半径
m: m, // 质量
v: {
x: vx,
y: vy
}, //速度(分解后)
move: function(){
// 单次移动函数
this.pos.x += this.v.x;
this.pos.y += this.v.y;
},
e: e,
paused: false
});
}

function clear_screen() {
// 清屏
cxt.clearRect(0,0,WIDTH,HEIGHT);
}

function update() {
//更新画布
clear_screen();
for (var ball_id = 0; ball_id < Balls.length; ball_id++) {
draw_ball(ball_id);
}
}

function solvefunc(a, b, c) {
// 求解二次函数
var delta = Math.sqrt(b*b-4*a*c);
return [(-b+delta)/(2*a), (-b-delta)/(2*a)];
}

function force(b1, b2, pro) {
// b1,b2传入id, pro传入字符串表示属性,例如:force(1,2,"m")
// 解形如k*q1*q2/R^2的受力
var x = Balls[b1].pos.x - Balls[b2].pos.x;
var y = Balls[b1].pos.y - Balls[b2].pos.y;
var distance = x*x + y*y;
if (dis == 0){
return [0,0];
}
var f = Balls[b1][pro] * Balls[b2][pro] / distance;
var fx = f / Math.sqrt(distance) * x;
var fy = f / Math.sqrt(distance) * y;
return [-fx, -fy];
}

function dis(p1, p2) {
//传入{x:x,y:y}
return Math.sqrt((p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y));
}

function hited(b1, b2) {
// 是否碰撞

if (b1 >= Balls.length || b2 > Balls.length) return false;
var dv_x = Balls[b1].v.x-Balls[b2].v.x;
var dv_y = Balls[b1].v.y-Balls[b2].v.y;
return (dv_x*dv_x+dv_y*dv_y!=0) && dis(Balls[b1].pos, Balls[b2].pos) <= Balls[b1].r + Balls[b2].r - 1e-8;
//1e-8是为了防止浮点数计算误差,下同。
}

function hit(b1, b2) {
// 基于pyton3代码修改,可读性极差建议直接看py3源码...
// 运算符重载一时爽, Javascript重写火葬场
// 弹性碰撞处理
var dv_x = Balls[b1].v.x-Balls[b2].v.x;
var dv_y = Balls[b1].v.y-Balls[b2].v.y;

if (dv_x*dv_x+dv_y*dv_y==0) {
// 速度差为0
return;
}
if (hited(b1,b2) == false){
return;
}

var dp_x = Balls[b2].pos.x-Balls[b1].pos.x;
var dp_y = Balls[b2].pos.y-Balls[b1].pos.y;
// 位置差
var dt = 0;
// 当前时间与碰撞确切时间差

if (Math.sqrt(dp_x*dp_x + dp_y*dp_y) < Balls[b1].r+Balls[b2].r) {
var a = dv_x*dv_x + dv_y*dv_y;
var b = -2 * (dv_x * dp_x + dv_y * dp_y);
var c = dp_x**2 + dp_y**2 - (Balls[b1].r+Balls[b2].r)**2;
dt = solvefunc(a, b, c)[1];
Balls[b1].pos.x += dt * Balls[b1].v.x;
Balls[b1].pos.y += dt * Balls[b1].v.y;
Balls[b2].pos.x += dt * Balls[b2].v.x;
Balls[b2].pos.y += dt * Balls[b2].v.y;
//一堆乱七八糟的公式自己推吧
}
// 球恢复到“碰撞瞬间”

Balls[b1].v.x-=Balls[b2].v.x;
Balls[b1].v.y-=Balls[b2].v.y;

dp_x = Balls[b2].pos.x-Balls[b1].pos.x;
dp_y = Balls[b2].pos.y-Balls[b1].pos.y;

var alpha = 0;
// 角度
if (Math.abs(dp_x) <= 1e-8) {
if (dp.y > 0) {
alpha = Math.asin(1);
} else {
alpha = Math.asin(-1);
}
} else {
alpha = Math.atan(dp_y/dp_x);
}

function mul(a,b) {
return a.x*b.x + a.y*b.y;
// 向量点积
}
function imul(a,b) {
//向量数量积
return {x:b.x*a, y:b.y*a};
}
function add(a,b) {
//向量和
return {x:b.x+a.x, y:b.y+a.y};
}

var v0 = mul(Balls[b1].v , {x:Math.cos(alpha), y: Math.sin(alpha)});
var v0c = add(Balls[b1].v, imul(-v0, {x:Math.cos(alpha), y:Math.sin(alpha)}));

//下面这段可读性实在太差了……
Balls[b1].v = Balls[b2].v;
Balls[b2].v = add(Balls[b2].v, imul(((2*Balls[b1].m)/(Balls[b1].m+Balls[b2].m)), imul(v0, {x:Math.cos(alpha), y:Math.sin(alpha)})));
Balls[b1].v = add(add(Balls[b1].v, imul(((Balls[b1].m-Balls[b2].m)/(Balls[b1].m+Balls[b2].m)), imul(v0, {x:Math.cos(alpha), y:Math.sin(alpha)}))), v0c);
// 球弹开
dt = -dt
Balls[b1].pos = add(Balls[b1].pos, imul(dt, Balls[b1].v));
Balls[b2].pos = add(Balls[b2].pos, imul(dt, Balls[b2].v));
}

function move_all() {
// 初次计算移动
for (var ball_id = 0; ball_id < Balls.length; ball_id++) {
if (Balls[ball_id].paused == true) continue;
Balls[ball_id].move();
if (Balls[ball_id].pos.x - Balls[ball_id].r <= 0) {
Balls[ball_id].v.x *= -1;
Balls[ball_id].pos.x = 1 + Balls[ball_id].r;
}
if (Balls[ball_id].pos.x + Balls[ball_id].r >= WIDTH) {
Balls[ball_id].v.x *= -1;
Balls[ball_id].pos.x = WIDTH - 1 - Balls[ball_id].r;
}
if (Balls[ball_id].pos.y - Balls[ball_id].r <= 0) {
Balls[ball_id].v.y *= -1;
Balls[ball_id].pos.y = 1 + Balls[ball_id].r;
}
if (Balls[ball_id].pos.y + Balls[ball_id].r >= HEIGHT) {
Balls[ball_id].v.y *= -1;
Balls[ball_id].pos.y = HEIGHT - 1 - Balls[ball_id].r;
}
}
}

function count_hit() {
var hits = [];
for (var i = 0; i < Balls.length; i++) {
if (Balls[i].paused == true) continue;
for (var j = i + 1; j < Balls.length; j++) {
if (Balls[j].paused == true) continue;
if (hited(i,j)) {
hits.push({deep: Balls[i].r + Balls[j].r - dis(Balls[i].pos, Balls[j].pos), a:i, b:j});
}
}
}
hits.sort(function(a,b){return a.r-b.r});
return hits;
}

function make_fouce(pro, k) {
// 受力统一计算
for (var i = 0; i < Balls.length; i++) {
if (Balls[i].paused == true) continue;
for (var j = 0; j < Balls.length; j++) {
if (i != j) {
if (Balls[j].paused == true) continue;
if (Balls[i][pro] == 0) continue;
if (Balls[j][pro] == 0) continue;

var fc = force(i, j, pro);
Balls[i].v.x += 1/Balls[i][pro] * fc[0] * k;
Balls[i].v.y += 1/Balls[i][pro] * fc[1] * k;

}
}
}
}

function main_function(){
move_all();
var hitlist = count_hit()
while (hitlist.length != 0) {
for (var i = 0; i < hitlist.length; i++){
hit(hitlist[i].a, hitlist[i].b);
}
hitlist = count_hit();
}
make_fouce("m", 1000); //重力场, 1000为引力常数
update();
//main_loop
setTimeout(function(){main_function()},1000/FPS);
}

creat_ball(100,200,20,1,1,2);
creat_ball(200,300,20,1,-2,-1);

function c_mousedown() {
mouse_var.mousedown = true;
mouse_var.x = event.offsetX;
mouse_var.y = event.offsetY;
mouse_var.paused_ballid = 'NaN';
var mouse_hited = false;
for (var ball_id = 0; ball_id < Balls.length; ball_id++) {
if (dis(Balls[ball_id].pos, {x:event.offsetX,y:event.offsetY}) < Balls[ball_id].r) {
Balls[ball_id].paused = true;
mouse_var.paused_ballid = ball_id;
mouse_hited = true;
break;
}
}
if (event.button == 0) {
if (!mouse_hited) creat_ball(event.offsetX, event.offsetY);
} else if (event.button == 2) {
if (mouse_var.paused_ballid != 'NaN') Balls.splice(mouse_var.paused_ballid, 1);
}


}
function c_mousemove() {
if (event.button == 0) {
if (mouse_var.paused_ballid != 'NaN') {
Balls[mouse_var.paused_ballid].pos.x = event.offsetX;
Balls[mouse_var.paused_ballid].pos.y = event.offsetY;

}
}
}

function c_mouseup() {
if (mouse_var.paused_ballid != 'NaN') {
Balls[mouse_var.paused_ballid].paused = false;
Balls[mouse_var.paused_ballid].v={x:0, y:0}
}
mouse_var.mousedown = false;
mouse_var.x = event.offsetX;
mouse_var.y = event.offsetY;
mouse_var.paused_ballid = 'NaN';
}

c.addEventListener('mousedown', c_mousedown);
c.addEventListener('mouseup', c_mouseup);
c.addEventListener('mousemove', c_mousemove);
c.oncontextmenu = function(){return false;};


main_function();

物理原理

高中知识直接推断而来

有动量守恒(pa+pb=pa’+pb’)和能量守恒(Ea+Eb=Ea’+Eb’)

按二维问题解出方程就好

另外,注意由于是按“刻”来计算碰撞,圆心连线角度不是theta,应该要“倒回来”一定的时间恢复到碰撞临界状态再计算(即hit函数前半部分所作的),然后再“快进”一段时间让两球分离

遇到的坑

  • 从0开始推公式真是难推啊……其实也不难(
  • 乱七八糟的临界状态
  • tan90度得分开求
  • 运算符重载一时爽, Javascript重写火葬场

已知Bug

太多了

后继

可以在这个网页拿F12进行更多调试!
如:

1
creat_ball(x,y) //创造球