关于此项目——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.GetSystemMetricsSIZE = [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, speed1m1 = 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(); returnspeed1 = [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) / 20dcr = (ce[0] - cs[0]) / 20dcg = (ce[1] - cs[1]) / 20dcb = (ce[2] - cs[2]) / 20for 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 1if num < 0: return -1return 0
def set_force(): # 处理obj1受到obj2的引力global last, m1, m2, r1, r2, speed1, speed2distx = 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))) / 2rawdm = 0.5*s * (r1+r2) if 0.5*s * (r1+r2) < m1 else m1 # 粗略计算被吸收的物质质量dm = 0#m2 += dm; m1 -= dm#newm1, newm2 = m1, m2while m1 >= 0  and m2 >= 0 and dm <= rawdm * 4:m2 += 1; m1 -= 1; dm += 1if not (m1 >= 0 and m2 >= 0): breakr1 = getr(m1); r2 = getr(m2)if abs(dist-(r1+r2)) <= 5: breakif m1 < 0: m1 = 0ns2 = []#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, ns2r1 = getr(m1) if m1 > 0 else 0r2 = 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 = distif obj2[0] + r2 > SIZE[0]: speed2[0] = -abs(speed2[0]); obj2[0] = SIZE[0] - r2if obj2[0] - r2 < 0: speed2[0] = abs(speed2[0]); obj2[0] = r2if obj2[1] + r2> SIZE[1]: speed2[1] = -abs(speed2[1]); obj2[1] = SIZE[1] - r2if 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 * tpsspeed2[1] += f*t(disty) / m2 * tpsobj2[0] += speed2[0] / uptime * tpsobj2[1] += speed2[1] / uptime * tpsif obj1[0] + r1> SIZE[0]: speed1[0] = -abs(speed1[0]); obj1[0] = SIZE[0] - r1if obj1[0] - r1< 0: speed1[0] = abs(speed1[0]); obj1[0] = r1if obj1[1] + r1> SIZE[1]: speed1[1] = -abs(speed1[1]); obj1[1] = SIZE[1] - r1if 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 * tpsspeed1[1] += -f*t(disty) / m1 * tpsobj1[0] += speed1[0] / uptime * tpsobj1[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 = Falseelif i.type == pygame.KEYDOWN:keys = pygame.key.get_pressed()if keys[pygame.K_ESCAPE]:running = Falseif keys[pygame.K_LEFT]:m2 = m2 * 0.9r2 = getr(m2)if keys[pygame.K_RIGHT]:m2 = m2 * 1.1r2 = getr(m2)if keys[pygame.K_a]:m1 = m1 * 0.9r1 = getr(m1)if keys[pygame.K_d]:m1 = m1 * 1.1r1 = getr(m1)if keys[pygame.K_s]:m2 = m1r2 = 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 = Falseif 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中的星体渲染。
如果有懂引力模拟相关知识的大佬或者对引力模拟有兴趣的同学,欢迎指教或改进代码。
谢谢!

Chaos Emulator v0.2.3 自建引力模拟器分享相关推荐

  1. Chaos Emulator核心功能开发历程

    说明 Chaos Emulator是目前我个人开发的一个引力模拟器.项目的背景说明请看v0.2.3发布介绍.我将在这篇文章中对这个模拟器的重要版本进行详细说明.相关的代码发布见代码库. 初次搭建:v0 ...

  2. 妙用postman系列——postman建组、分享

    妙用postman系列--postman建组.分享 添加新的组和请求. 3.生成分享链接 4.导入分享链接

  3. bemusic,一个音乐网站建站心得分享

    发现bemusic这个网站源代码大约在今年的4月中旬,之前和期间和测试过几个音乐网站源代码,如:phpsound,JYmusic,soundkit等,也测试过wordpress的几个音乐方面的主题,总 ...

  4. android 源码编译 模拟器emulator启动,Android 源码 (AOSP) - 编译 ( 模拟器 )

    前言 走到这一步, 我们终于可以启动一下模拟器玩玩了, 经过两天编译, 感觉什么都好,就是头冷. 在这里插入图片描述 开搞,开搞. AOSP系列 开搞 自己的配置 硬件 Mac mini (Late ...

  5. 如何建立自己的网站—建站历程分享

    在学习网页制作期间,尤其是在完成了一个作业(姑且称之为)后,我相信大多数人和我一样,都曾想过:如何上传自己网站,让别人看见? 接下来我就来分享一下我艰难的建站过程,并记录一下那段苦逼的日子. 现在我们 ...

  6. 医院管理系统完整项目代码以及数据库建表语句分享(包括加盐和拦截器)

    分为三种工作人员 管理员:添加其他工作人员 挂号员:帮病人选择医生和科室里的具体哪个房间 门诊医生:获得挂号员挂在自己这里的病人的信息并且进行诊断 病人没有账号,可以挂号,也可以点击My diagno ...

  7. 阿里云——大神建个人网站分享(转载)

    2017年02月26日 13:51:35 杜瑞祺 阅读数:39632 https://blog.csdn.net/duruiqi_fx/article/details/54918393 时间过得真快, ...

  8. Mysql建库建表语句分享

    前言:在生产环境中,常常会被叫发一下sql脚本,建库脚本,那么我们怎么创建呢,给大家分享一下吧! 建库语句: CREATE DATABASE test DEFAULT CHARACTER SET ut ...

  9. 自建通用Makefile 分享

    1年前写的,现在一些小型项目上,还是很好用的,兼容性还ok 分享一下 现在用不到了感觉 .PHONY:all cleanSHELL:=/bin/bash MKDIR = mkdir -p RM = r ...

最新文章

  1. quickbuild php,QuickBooks API(php)集成
  2. unity3d做会减少的血条_Unity3d中NGUI加强版血条(Healthbar)的制作
  3. <马哲>社会基本矛盾2017-12-27
  4. Btree索引和Hash索引
  5. activemq生产者和消费者的双向通信
  6. 新闻发布项目——接口类(commentDao)
  7. 就国内某个程序员问答网站的简单的分析
  8. Debug学习资源汇总
  9. OC NSNumber NSValue
  10. python王者战斗_把英雄分类,看 Python 带你上王者
  11. 基于改进的残差网络的指纹识别算法
  12. 如何压缩ppt大小的方法不减画质?
  13. HEXO+ICARUS主题配置安装经验分享
  14. 给出三个坐标点,求三角形周长。
  15. 服务器win7无限重启,win7系统无限重启的解决方法
  16. // #ifdef MP-WEIXIN微信小程序无效,不执行
  17. Quick bi是什么?
  18. 工具推荐 10款移动界面原型设计工具
  19. hydrus1d使用说明_Hydrus-1D中文说明.doc
  20. 软件安装与升级——rpm

热门文章

  1. 批量创建工作表并以本月日期命名——《超级处理器》应用
  2. 抖音搬运视频如何伪原创
  3. 对于目标检测中mAP@0.5的理解
  4. Boost研究:Boost Log
  5. 运维工程师发展受限,那么运维转型大数据是个机会吗?
  6. carla创建地图(四)基于ue4创建地图
  7. exadata的infiniband交换机的ilom
  8. dos(cmd)命令
  9. Ctrl+win+d win的虚拟桌面如何撤销
  10. 竖版视频怎么批量转换成横版视频