自己动手写二维物理引擎(一)

点击访问原文链接

前言

这一篇,继续之前的想法,开始写物理引擎的代码。

由于pygame不能同时打开多个窗口,我决定换Qt来显示窗口。

开始

导入库

打开physics.py

from PySide6 import * 
import math
  • PySide6:这是Qt官方的PyQt6的库。这里我们用它来实现窗口以及内容的显示。
  • math:Python数学库,用来处理一些高级的物理计算。

全局常量

DYMANIC=0
STATIC=1

这些常量定义了物体的状态等信息,后学会有更多常量写在这里。

Qt应用程序

qapp=QtWidgets.QApplication([])
def run():
    qapp.exec()

之后在demo.py中运行physics.run()即可运行应用程序。

物理空间

编写一个Space(QtWidegets.QWidget)类来模拟一个物理空间,这将被显示为一个窗口。

实例化

class Space(QtWidgets.QWidget):	
    def __init__(self,width,height,caption,g):
        super().__init__()
        self.width=width
        self.height=height
        self.caption=caption
        self.objects=[]
        self.g=g
        self.timer = QtCore.QTimer()
  • width:窗口的宽度
  • height:窗口的高度
  • caption:窗口的标题
  • objects:物理空间中所有物体列表
  • g:重力加速度(常量)
  • timer:使用qt计时器,用来定时运行一些函数

启动窗口

# class Space(QtWidgets.QWidget):	
	def launch(self):
        self.resize(self.width,self.height)
        self.setWindowTitle(self.caption)
        self.timer.start(5)
        self.timer.timeout.connect(self.moveObjects)
        self.timer.timeout.connect(self.update)
        self.show()

该函数可以使物理空间窗口弹出并初始化。

移动与绘制

一个使窗口移动,并制造重力,另一个绘制。

# class Space(QtWidgets.QWidget):
    def moveObjects(self):
        for i in self.objects:
            if i.stype==DYMANIC:
                gravity=Force(self.g,math.pi/2)
                i.forceCompound(gravity)
                i.move()
    def paintEvent(self,event):
        painter = QtGui.QPainter(self)
        for i in self.objects:
            if isinstance(i,Circle):
                painter.setPen(i.pen)
                painter.drawEllipse(i.cpoint,i.r,i.r)
                painter.drawLine(i.cpoint.x(),i.cpoint.y(),
                              i.cpoint.x()+i.r*math.cos(i.direction),i.cpoint.y()+i.r*math.sin(i.direction))
            elif isinstance(i,Segment):
                painter.setPen(i.pen)
                painter.drawLine(i.x1,i.y1,i.x2,i.y2)

力与物体对象

力是最简单的一个类,它是一个向量,只有两个成员:方向和大小。

特别注意,所有的角单位都用弧度( π  rad = 18 0 ∘ \pi\space \text{rad}=180^{\circ} π rad=180

class Force():
    def __init__(self,size,angle):
        self.size=size
        self.angle=angle

物体对象

物体对象有很多种,如刚体、流体、关节

实例化

class Object():
    def __init__(self,z,window,color,mass,angle):
        self.z=z
        self.window=window
        self.window.objects.append(self)
        self.color=QtGui.QColor()
        self.color.setRgb(color[0],color[1],color[2])
        self.mass=mass
        self.velocity=0
        self.angle=angle
  • z:层坐标,z不同的两个元素不会发生碰撞。
  • window:所在的窗口
  • color:对象显示的颜色
  • mass:对象的质量
  • velocity:运动速度
  • angle:运动方向

人为施力

在这里插入图片描述

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

可以看出,我们可以利用三角函数把物体当前的方向和速度分解为水平方向上的速度和垂直方向上的速度,对施加的力作同样的处理,将水平和垂直的分别相加再转换为新的速度和角度就可以实现人为施力。

于是又了下面的代码:

# class Object():
	def forceCompound(self,add_force):
        cur_force_size=self.velocity
        cur_force_dx=cur_force_size*math.cos(self.angle)+add_force.size*math.cos(add_force.angle)/self.mass
        cur_force_dy=cur_force_size*math.sin(self.angle)+add_force.size*math.sin(add_force.angle)/self.mass
        self.velocity=math.sqrt(cur_force_dx**2+cur_force_dy**2)
        if cur_force_dx>=0:
            self.angle=math.atan(cur_force_dy/cur_force_dx)
        else:
            self.angle=math.atan(cur_force_dy/cur_force_dx)+math.pi

删除对象

把对象移除,不多赘述

# class Object():
    def destroy(self):
        self.window.objects.remove(self)
        self.window=None
        del self

刚体

目前先实现圆、线段。之后会实现多边形。

class Solid(Object):
    def __init__(self,z,window,color,stype,mass,angle,direction):
        super().__init__(z,window,color,mass,angle)
        self.stype=stype
        self.direction=direction
        self.pen = QtGui.QPen(self.color)
        self.pen.setWidth(2)
class Circle(Solid):
    def __init__(self,window,z,x,y,r,direction,stype,mass,angle=0,color=(0,0,255)):
        super().__init__(z,window,color,stype,mass,angle,direction)
        self.cpoint=QtCore.QPointF(x,y)
        self.r=r
    def move(self):
        self.cpoint.setX(self.cpoint.x()+self.velocity*math.cos(self.angle))
        self.cpoint.setY(self.cpoint.y()+self.velocity*math.sin(self.angle))
    def hit(self,c):
        pass
class Segment(Solid):
    def __init__(self,window,z,x1,y1,x2,y2,direction,stype,mass,angle=0,color=(0,0,255)):
        super().__init__(z,window,color,stype,mass,angle,direction)
        self.x1=x1
        self.y1=y1
        self.x2=x2
        self.y2=y2
    def move(self):
        self.x1+=self.velocity*math.cos(self.angle)
        self.y1+=self.velocity*math.sin(self.angle)
        self.x2+=self.velocity*math.cos(self.angle)
        self.y2+=self.velocity*math.sin(self.angle)
    def hit(self,c):
        pass

angledirection的区别

angle是指运动方向。而direction是只物体本身的朝向。

效果测试

demo.py

import physics

window1=physics.Space(1000,600,'123',9.8)
window1.launch()
circle1=physics.Circle(window1,0,100,100,40,0,physics.DYMANIC,1000)
force1=physics.Force(1000,0)
circle1.forceCompound(force1)
segment1=physics.Segment(window1,0,50,550,950,550,0,physics.STATIC,1000)
physics.run()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

下期预告

下一期会实现多边形以及碰撞检测。886!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值