Chaos Emulator v0.2.3 自建引力模拟器分享

关于此项目——Chaos Emulator

我是Osmos游戏的狂热分子,因为对Osmos中的星体运动,尤其是吸引体的引力模拟极为感兴趣,所以编写了Chaos Emulator这个简单的引力模拟器。引力模拟器的概念大部分与Osmos相同,所以建议先了解Osmos这个伟大的物理游戏
引力模拟器是开源的,遵循简单粗暴的MIT协议。GitHub仓库在这里
(请原谅代码格式问题,因为此程序目前仅个人开发)
给这个项目起名字为Chaos(混沌)的原因是,前不久才真正认识了混沌理论(或者是一种思想观)。而另一个原因是,我被这个词的发音吸引了……原来它读作/'keɪɒs/。

0.2.3的新功能

此版本目前仍旧保留一颗吸引体和一颗伴星的运作模式。默认地,吸引体会吸收伴星,而无论它们谁的质量更大。
在0.2.3版本中,添加了新的渲染方案,并且可使用如下的键盘操作:

  • S键:让吸引体变得和普通星体大小相等
  • A键:让普通星体缩小
  • D键:让普通星体变大
  • ←键:让吸引体缩小
  • →键:让吸引体变大

项目效果和原理

以下是程序截图:
渲染效果
通过指定全局常量FILL的布尔值来指示是否在每帧渲染前擦除上一帧的图像。
在这里插入图片描述
图中所示的骤然缩小的残迹,是吸引体吸收伴星时产生的。吸收过程的模拟添加于v0.2.2,但是现在尚未进行精确优化,所以在吸引体小于伴星时吸收的模拟会偏差。
项目使用离散化的引力计算方法,即根据星体当前的位置和惯性等属性来计算受引力影响的下一帧的位置,而并未采用数学轨迹模型的静态预计算方式。也是因为如此,Python的效率水平无法满足该程序的算力要求,所以后续亟待转用C++重写。
程序使用全屏模式显示,全局常量SIZE数对表示视窗的水平长度和垂直长度。在Windows平台下,程序会自动尝试获取显示屏的分辨率,作为全屏显示的尺寸。在其他平台下,程序使用默认的分辨率,即1366 * 768(我的电脑最高分辨率是这样的,不同分辨率的电脑可以自行修改)。
项目未使用贴图的方式,渲染的图像渐变是根据内置绘制方法实现的。

待改进

  • 由于目前只是自己测试来玩的,所以写得比较随性,有些代码和变量可能没有给出具体的解释,请见谅,日后会发布完善的新版本。
  • 算力性能限制,原因已经说了,是语言效率问题。
  • 当前版本不是批量引力模拟,所以采用很多变量,而不是用对象来代表星体,理由同上,仅为了测试。

项目代码

GitHub仓库内代码在此
请注意:该项目使用PyGame作为图形界面,采用全屏显示。
以下是具体代码:

# 引入库
import pygame, math, sys, random, ctypes
pygame.init()
# 常量
try:
    GetSystemMetrics = ctypes.windll.user32.GetSystemMetrics
    SIZE = [GetSystemMetrics(0), GetSystemMetrics(1)]
except:
    SIZE = [1366, 768]

tps = 1 # 模拟器中的时间(秒)与现实时间(秒)之比
fps = 10 # 刷新率
FILL = True
# 以下常量是游戏中的时间常量
uptime = 1000 / fps  # 游戏刷新间隔(毫秒)
def getr(m):
    return m ** 0.3 * 6#math.log(m1*80) * 5
def reset_star1():
    global m1, r1, r2, obj1, obj2, speed1
    m1 = random.randint(1, 10000)#random.randint(50, 1600)
    r1 = getr(m1)
    
    obj1 = [random.randint(0 + int(r1), SIZE[0] - int(r1)), random.randint(0+int(r1), SIZE[1]-int(r1))]
    distx = obj1[0] - obj2[0]
    disty = obj1[1] - obj2[1]
    dist = math.sqrt(distx**2+disty**2)
    if dist < (r1+r2) * 1.5: reset_star1(); return
    speed1 = [random.randint(-400, 400), random.randint(-400, 400)]
# 测试用数据


m2 = random.randint(20, 1000)
r2 = getr(m2)
obj2 = [random.randint(0 + int(r2), SIZE[0]-int(r2)), random.randint(0+int(r2), SIZE[1]-int(r2))]
speed2 = [random.randint(-400, 400), random.randint(-400, 400)]

reset_star1()
c1 = [242, 112, 34]
c2 = [154,41,133]
c3 = [125, 39, 236]
c7 = [255, 142, 255]

c4 = [18, 20, 64]
c5 = [36, 39, 128]
c6 = [151, 196, 255]

c8 = [207, 224, 234]
BACKGROUND = [6, 3, 24]
filler1 = c4[:]
filler1[0] //= 2
filler1[1] //= 2
filler1[2] //= 2
g = 20
# 函数
last = r1+r2
def fill(center, rs, re, cs, ce):  # 绘制环形渐变,rs > re
    '''默认为20层渐变'''
    pygame.draw.circle(s, cs, center, rs)
    dr = (re - rs) / 20
    dcr = (ce[0] - cs[0]) / 20
    dcg = (ce[1] - cs[1]) / 20
    dcb = (ce[2] - cs[2]) / 20
    for i in range(1, 19):
        pygame.draw.circle(s, (int(cs[0] + dcr*i), int(cs[1] + dcg*i), int(cs[2] + dcb*i)), center, int(rs + dr*i))
    pygame.draw.circle(s, ce, center, re)

    
def t(num):
    if num > 0: return 1
    if num < 0: return -1
    return 0
def set_force(): # 处理obj1受到obj2的引力
    global last, m1, m2, r1, r2, speed1, speed2
    distx = obj1[0] - obj2[0]
    disty = obj1[1] - obj2[1]
    dist = math.sqrt(distx**2+disty**2)
    if r1+r2+10 > dist: # 两星体相撞,目前默认由星体2号吸收1号
        #if dist < min(r1, r2):
        #    speed1, speed2 = [0, 0], [0, 0]  # 这里改成使用last的表达式
        s = (r1+r2 - dist) / 4# 粗略计算重合的部分线段长
        # 目标:将距离s一部分留给1号,另一部分被2号吸收,并最终使两星体相切,即r1+r2==dist
        #d1 = ((m1-m2) + math.sqrt((m1-m2)**2 + min(dist, m1))) / 2
        rawdm = 0.5*s * (r1+r2) if 0.5*s * (r1+r2) < m1 else m1 # 粗略计算被吸收的物质质量
        dm = 0
        #m2 += dm; m1 -= dm
        #newm1, newm2 = m1, m2
        while m1 >= 0  and m2 >= 0 and dm <= rawdm * 4:
            m2 += 1; m1 -= 1; dm += 1
            if not (m1 >= 0 and m2 >= 0): break
            r1 = getr(m1); r2 = getr(m2)
            if abs(dist-(r1+r2)) <= 5: break
        if m1 < 0: m1 = 0
        ns2 = []
        #ns1, ns2 = [], []
        #ns1.append((speed1[0]*m1 + speed2[0]*dm/max(m1, 1)) / (m1+dm))
        #ns1.append((speed1[1]*m1 + speed2[1]*dm/max(m1, 1)) / (m1+dm))
        ns2.append((speed2[0]*m2 + speed1[0]*dm) / (m2+dm))
        ns2.append((speed2[1]*m2 + speed1[1]*dm) / (m2+dm))
        #ns2.append((speed2[0]*m2 + speed1[0]*dm/max(m1, 1)) / (m2+dm))
        #ns2.append((speed2[1]*m2 + speed1[1]*dm/max(m1, 1)) / (m2+dm))
        speed2 = ns2
        #speed1, speed2 = ns1, ns2
        r1 = getr(m1) if m1 > 0 else 0
        r2 = getr(m2) if m2 > 0 else 0
        #print(m1, m2, m1+m2)
    #if r1 < 0: return 'quit'
    
    if r1 + r2 > 1.5*dist:
        f = int(g * m1 * m2 / last**2)
    else:
        f = int(g * m1 * m2 / max(dist, last)**2)
        last = dist
    
    if obj2[0] + r2 > SIZE[0]: speed2[0] = -abs(speed2[0]); obj2[0] = SIZE[0] - r2
    if obj2[0] - r2 < 0: speed2[0] = abs(speed2[0]); obj2[0] = r2
    if obj2[1] + r2> SIZE[1]: speed2[1] = -abs(speed2[1]); obj2[1] = SIZE[1] - r2
    if obj2[1] - r2< 0: speed2[1] = abs(speed2[1]); obj2[1] = r2
    #if dist <= r1+r2:
    #    speed2[0] += last*t(distx) / m2
    #    speed2[1] += last*t(disty) / m2
    #else:
    if m2:
        speed2[0] += f*t(distx) / m2 * tps
        speed2[1] += f*t(disty) / m2 * tps

    obj2[0] += speed2[0] / uptime * tps
    obj2[1] += speed2[1] / uptime * tps
    
    if obj1[0] + r1> SIZE[0]: speed1[0] = -abs(speed1[0]); obj1[0] = SIZE[0] - r1
    if obj1[0] - r1< 0: speed1[0] = abs(speed1[0]); obj1[0] = r1
    if obj1[1] + r1> SIZE[1]: speed1[1] = -abs(speed1[1]); obj1[1] = SIZE[1] - r1
    if obj1[1] - r1< 0: speed1[1] = abs(speed1[1]); obj1[1] = r1

    #if dist <= r1+r2:
    #    speed1[0] += -last*t(distx) / m1
    #    speed1[1] += -last*t(disty) / m1
    #else:
    if m1:
        speed1[0] += -f*t(distx) / m1 * tps
        speed1[1] += -f*t(disty) / m1 * tps
    obj1[0] += speed1[0] / uptime * tps
    obj1[1] += speed1[1] / uptime * tps
    
    #last = f
    #print(distx, disty, obj1, obj2)
# 主程序
s = pygame.display.set_mode(SIZE, pygame.RESIZABLE | pygame.FULLSCREEN)
clock = pygame.time.Clock()
running = True
shotcnt = 0
while running:
    for i in pygame.event.get():
        if i.type == pygame.QUIT:
            running = False
        elif i.type == pygame.KEYDOWN:
            keys = pygame.key.get_pressed()
            if keys[pygame.K_ESCAPE]:
                running = False
            if keys[pygame.K_LEFT]:
                m2 = m2 * 0.9
                r2 = getr(m2)
            if keys[pygame.K_RIGHT]:
                m2 = m2 * 1.1
                r2 = getr(m2)
            if keys[pygame.K_a]:
                m1 = m1 * 0.9
                r1 = getr(m1)
            if keys[pygame.K_d]:
                m1 = m1 * 1.1
                r1 = getr(m1)
            if keys[pygame.K_s]:
                m2 = m1
                r2 = getr(m2)
    clock.tick(uptime)
    if FILL: s.fill(BACKGROUND)
    #if r1 < 0: break
    #pygame.draw.rect(s, (0, 0, 0), [0, 0, SIZE[0], SIZE[1]])
    pos1 = [int(obj1[0] + 0.5), int (obj1[1] + 0.5)]
    pos2 = [int(obj2[0] + 0.5), int (obj2[1] + 0.5)]
    if m1:
        fill(pos1, abs(int(r1)), abs(int(r1 / 1.1)), c4, c5)
        fill(pos1, abs(int(r1 / 1.1)), abs(int(r1 / 2)), c5, c6)
       #pygame.draw.circle(s, c4, pos1, abs(int(r1)))
        #pygame.draw.circle(s, c5, pos1, abs(int(r1 / 1.1)))
        #pygame.draw.circle(s, c6, pos1, abs(int(r1 / 1.5)))
    fill(pos2, int(r2), int(r2 * 0.75), filler1, c1)
   # fill(pos2, int(r2), int(r2 * 3 / 4), c1, c2)
    fill(pos2, int(r2 * 0.75), int(r2 / 2.5), c2, c3)
    #fill(pos2, int(r2 / 2.5), int(r2 / 16), c3, c7)
    #pygame.draw.circle(s, c1, pos2, int(r2))
    #pygame.draw.circle(s, c2, pos2, int(r2 * 3 / 4))
    pygame.draw.circle(s, c3, pos2, int(r2 / 2.5))
    pygame.draw.circle(s, c7, pos2, int(r2 / 16))
    pygame.display.flip()
    if set_force() == 'quit':
        running = False
    if m1 == 0:  # 重设一次
        reset_star1()

    #if m2 <= 1:
    #    print("Sun shrinked out!")
    #    break
    #m2 *= 0.995
    #m1 *= 1.005
    #r1 = getr(m1)
    #r2 = getr(m2)
    
pygame.quit()

计划

计划将这个项目开发成一个不同于Osmos游戏的引力模拟器,更加专注于真实模拟。
计划用C++语言改写为引力批量模拟模式。目前在和Osmos的开发者交流学习引力模拟的知识。很感谢这位开发者的耐心指导。渲染的图形效果灵感来自于Osmos中的星体渲染。
如果有懂引力模拟相关知识的大佬或者对引力模拟有兴趣的同学,欢迎指教或改进代码。
谢谢!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
chaosblade是一款用于进行混沌工程实验的工具包,可以用于模拟系统中的各种异常情况和故障。根据引用,如果你想快速体验chaosblade而不下载工具包,你可以使用docker镜像来运行它。你可以拉取chaosblade的docker镜像,并在容器内使用它进行实验。 引用提到,在使用chaosblade时,你可以通过修改方法的返回值来模拟异常情况。比如,你可以通过返回错误的结果来模拟MySQL数据库的异常情况。 如果你在Kubernetes上部署chaosblade,你可以使用helm命令来安装chaosblade-box。根据引用,你可以使用以下命令来安装chaosblade-box,并设置spring.datasource.password参数为你的数据库密码。安装完成后,你就可以在Kubernetes环境中使用chaosblade进行实验了。 总结起来,chaosblade是一个用于进行混沌工程实验的工具包,可以通过docker镜像或在Kubernetes上部署进行使用。你可以使用它来模拟各种异常情况,包括MySQL数据库的异常情况。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [详解ChaosBlade](https://blog.csdn.net/Avery123123/article/details/120794175)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [Chaosblade: 阿里一个超级牛逼的混沌实验实施工具](https://blog.csdn.net/varyall/article/details/115875965)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值