弹跳小球:物理模拟与碰撞检测的数学本质

目录

  • 弹跳小球:物理模拟与碰撞检测的数学本质
    • 引言
    • 第一章 物理模型精解
      • 1.1 运动微分方程
      • 1.2 能量损失模型
    • 第二章 碰撞检测体系
      • 2.1 边界碰撞检测
      • 2.2 物体间碰撞检测
    • 第三章 系统架构设计
      • 3.1 物理引擎模块
      • 3.2 数据流架构
    • 第四章 性能优化策略
      • 4.1 时间步长控制
      • 4.2 空间分割优化
    • 第五章 高级碰撞响应
      • 5.1 动量守恒计算
      • 5.2 摩擦扭矩计算
    • 结语
    • 附录:部分代码

弹跳小球:物理模拟与碰撞检测的数学本质

需要源码请+V:xrbcgfh0214

弹跳小球:物理模拟与碰撞检测的数学本质_第1张图片

引言

弹跳小球作为物理引擎的基础实验场,完美展现了经典力学与计算机图形学的交融。本文将深入解析二维弹性碰撞的数学模型,揭示实时物理模拟背后的微分方程与优化策略。


第一章 物理模型精解

1.1 运动微分方程

小球运动遵循牛顿第二定律:

{ d v d t = g − μ v d x d t = v \begin{cases} \frac{d\mathbf{v}}{dt} = \mathbf{g} - \mu \mathbf{v} \\ \frac{d\mathbf{x}}{dt} = \mathbf{v} \end{cases} {dtdv=gμvdtdx=v

离散化后实现数值积分:

v n + 1 = v n + ( g − μ v n ) Δ t x n + 1 = x n + v n Δ t \mathbf{v}_{n+1} = \mathbf{v}_n + (\mathbf{g} - \mu \mathbf{v}_n)\Delta t \\ \mathbf{x}_{n+1} = \mathbf{x}_n + \mathbf{v}_n\Delta t vn+1=vn+(gμvn)Δtxn+1=xn+vnΔt

1.2 能量损失模型

碰撞恢复系数决定速度衰减:

v n e w = − e ⋅ v o l d ( 0 ≤ e ≤ 1 ) v_{new} = -e \cdot v_{old} \quad (0 \leq e \leq 1) vnew=evold(0e1)

速度更新
位置更新
碰撞检测
速度反转
继续运动

第二章 碰撞检测体系

2.1 边界碰撞检测

屏幕边界判断条件:

{ x m i n = r x ≤ r x m a x = W − r x ≥ W − r y m i n = r y ≤ r y m a x = H − r y ≥ H − r \begin{cases} x_{min} = r & x \leq r \\ x_{max} = W-r & x \geq W-r \\ y_{min} = r & y \leq r \\ y_{max} = H-r & y \geq H-r \end{cases} xmin=rxmax=Wrymin=rymax=HrxrxWryryHr

2.2 物体间碰撞检测

两球碰撞条件:

∣ ∣ x i − x j ∣ ∣ ≤ r i + r j ||\mathbf{x}_i - \mathbf{x}_j|| \leq r_i + r_j ∣∣xixj∣∣ri+rj


第三章 系统架构设计

3.1 物理引擎模块

物理核心
运动计算
碰撞检测
响应处理
积分器
粗检测
精检测
摩擦力计算
反弹计算

3.2 数据流架构

用户输入
物理引擎
渲染系统
屏幕输出
音效系统

第四章 性能优化策略

4.1 时间步长控制

自适应步长算法:

Δ t n e w = min ⁡ ( Δ t m a x , ϵ max ⁡ ( ∣ ∣ a ∣ ∣ ) ) \Delta t_{new} = \min(\Delta t_{max}, \frac{\epsilon}{\max(||\mathbf{a}||)}) Δtnew=min(Δtmax,max(∣∣a∣∣)ϵ)

4.2 空间分割优化

四叉树空间划分:

整个场景
西北区域
东北区域
西南区域
东南区域
物体A
物体B
物体C

第五章 高级碰撞响应

5.1 动量守恒计算

完全弹性碰撞公式:

{ v 1 ′ = v 1 − 2 m 2 m 1 + m 2 ( v 1 − v 2 ) ⋅ n ∣ ∣ n ∣ ∣ 2 n v 2 ′ = v 2 − 2 m 1 m 1 + m 2 ( v 2 − v 1 ) ⋅ n ∣ ∣ n ∣ ∣ 2 n \begin{cases} \mathbf{v}_1' = \mathbf{v}_1 - \frac{2m_2}{m_1+m_2} \frac{(\mathbf{v}_1-\mathbf{v}_2)\cdot\mathbf{n}}{||\mathbf{n}||^2} \mathbf{n} \\ \mathbf{v}_2' = \mathbf{v}_2 - \frac{2m_1}{m_1+m_2} \frac{(\mathbf{v}_2-\mathbf{v}_1)\cdot\mathbf{n}}{||\mathbf{n}||^2} \mathbf{n} \end{cases} {v1=v1m1+m22m2∣∣n2(v1v2)nnv2=v2m1+m22m1∣∣n2(v2v1)nn

5.2 摩擦扭矩计算

旋转速度更新:

ω n e w = ω o l d + r × F f I Δ t \omega_{new} = \omega_{old} + \frac{r \times F_f}{I}\Delta t ωnew=ωold+Ir×FfΔt


结语

弹跳小球的物理模拟展现了连续介质力学在离散世界的完美映射。从微分方程的数值解算到碰撞响应的动量传递,每个技术细节都构建起虚拟世界的物理可信度。这种基础模型为更复杂的刚体仿真奠定了基础。

扩展阅读方向

  • 多体系统耦合振动分析
  • 流体力学SPH方法迁移应用
  • 基于物理的实时破坏系统

附录:部分代码

import pygame
import sys
import random
import numpy as np
from ball import Ball
from physics_engine import PhysicsEngine

class BallSimulation:
    """弹跳小球物理模拟主程序"""
    
    def __init__(self):
        # 初始化pygame
        pygame.init()
        
        # 设置屏幕
        self.width = 800
        self.height = 600
        self.screen = pygame.display.set_mode((self.width, self.height))
        pygame.display.set_caption("弹跳小球:物理模拟与碰撞检测")
        
        # 创建时钟对象
        self.clock = pygame.time.Clock()
        
        # 创建物理引擎
        self.physics_engine = PhysicsEngine(self.width, self.height)
        
        # 设置颜色
        self.colors = [
            (255, 0, 0),    # 红色
            (0, 255, 0),    # 绿色
            (0, 0, 255),    # 蓝色
            (255, 255, 0),  # 黄色
            (255, 0, 255),  # 紫色
            (0, 255, 255),  # 青色
            (255, 165, 0),  # 橙色
            (128, 0, 128)   # 紫色
        ]
        
        # 加载字体 - 修复字体问题,使用系统默认字体
        try:
            # 尝试使用微软雅黑(Windows系统常见字体,支持中文)
            self.font = pygame.font.SysFont('microsoft yahei', 14)
        except:
            try:
                # 备选Sans字体
                self.font = pygame.font.SysFont('simhei', 14)
            except:
                # 如果都失败,使用默认字体并降低大小
                self.font = pygame.font.Font(None, 14)
        
        # 初始状态
        self.paused = False
        self.show_stats = True
        self.adaptive_timestep = False
        self.show_all_trajectories = False
        
        # 重力控制相关
        self.gravity_rotation_speed = 2.0  # 每帧旋转角度
        self.gravity_magnitude_change_rate = 5.0  # 每帧变化量
        self.show_gravity_vector = True  # 是否显示重力向量
    
    def create_random_ball(self, x=None, y=None):
        """创建一个随机属性的小球"""
        # 随机位置
        if x is None:
            x = random.randint(50, self.width - 50)
        if y is None:
            y = random.randint(50, self.height - 50)
        
        # 随机半径和质量
        radius = random.randint(15, 30)
        mass = radius / 10
        
        # 随机初始速度
        vx = random.uniform(-200, 200)
        vy = random.uniform(-200, 200)
        
        # 随机恢复系数
        restitution = random.uniform(0.7, 0.95)
        
        # 随机空气阻力
        mu = random.uniform(0.01, 0.05)
        
        # 随机颜色
        color = random.choice(self.colors)
        
        return Ball(x, y, radius, mass, color, vx, vy, restitution, mu)
    
    def handle_events(self):
        """处理用户输入事件"""
        for event in pygame.event.get():
            # 退出程序
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            
            # 鼠标按下事件
            elif event.type == pygame.MOUSEBUTTONDOWN:
                # 左键添加小球
                if event.button == 1:
                    x, y = pygame.mouse.get_pos()
                    ball = self.create_random_ball(x, y)
                    # 如果全局轨迹显示开启,则设置新球的轨迹也显示
                    if self.show_all_trajectories:
                        ball.show_trajectory = True
                    self.physics_engine.add_ball(ball)
                
                # 右键清除所有小球
                elif event.button == 3:
                    self.physics_engine.balls.clear()
            
            # 键盘按下事件
            elif event.type == pygame.KEYDOWN:
                # 空格暂停/继续
                if event.key == pygame.K_SPACE:
                    self.paused = not self.paused
                
                # A键添加随机球
                elif event.key == pygame.K_a:
                    ball = self.create_random_ball()
                    # 如果全局轨迹显示开启,则设置新球的轨迹也显示
                    if self.show_all_trajectories:
                        ball.show_trajectory = True
                    self.physics_engine.add_ball(ball)
                
                # C键清除所有球
                elif event.key == pygame.K_c:
                    self.physics_engine.balls.clear()
                
                # S键显示/隐藏统计信息
                elif event.key == pygame.K_s:
                    self.show_stats = not self.show_stats
                
                # T键切换自适应时间步长
                elif event.key == pygame.K_t:
                    self.adaptive_timestep = not self.adaptive_timestep
                    self.physics_engine.adaptiveTimeStep = self.adaptive_timestep
                
                # R键切换所有球的轨迹显示
                elif event.key == pygame.K_r:
                    self.show_all_trajectories = not self.show_all_trajectories
                    for ball in self.physics_engine.balls:
                        ball.show_trajectory = self.show_all_trajectories
                
                # G键切换重力向量显示
                elif event.key == pygame.K_g:
                    self.show_gravity_vector = not self.show_gravity_vector
                
                # 1-4键设置预设重力场景
                elif event.key == pygame.K_1:
                    # 向下重力 (正常)
                    self.physics_engine.set_gravity_direction(np.array([0, 1]))
                    self.physics_engine.set_gravity_magnitude(9.8 * 60)
                elif event.key == pygame.K_2:
                    # 向上重力 (反重力)
                    self.physics_engine.set_gravity_direction(np.array([0, -1]))
                    self.physics_engine.set_gravity_magnitude(9.8 * 60)
                elif event.key == pygame.K_3:
                    # 向左重力
                    self.physics_engine.set_gravity_direction(np.array([-1, 0]))
                    self.physics_engine.set_gravity_magnitude(9.8 * 60)
                elif event.key == pygame.K_4:
                    # 向右重力
                    self.physics_engine.set_gravity_direction(np.array([1, 0]))
                    self.physics_engine.set_gravity_magnitude(9.8 * 60)
                elif event.key == pygame.K_5:
                    # 零重力
                    self.physics_engine.set_gravity_magnitude(0)
                elif event.key == pygame.K_6:
                    # 月球重力 (约地球的1/6)
                    self.physics_engine.set_gravity_magnitude(9.8 * 60 / 6)
                elif event.key == pygame.K_7:
                    # 木星重力 (约地球的2.5倍)
                    self.physics_engine.set_gravity_magnitude(9.8 * 60 * 2.5)
                
                # 数字键8-9选择特定球切换轨迹
                elif pygame.K_8 <= event.key <= pygame.K_9:
                    ball_index = event.key - pygame.K_8
                    if ball_index < len(self.physics_engine.balls):
                        self.physics_engine.balls[ball_index].toggle_trajectory()
        
        # 获取按键状态 (持续按下)
        keys = pygame.key.get_pressed()
        
        # 旋转重力方向
        if keys[pygame.K_LEFT]:
            self.physics_engine.rotate_gravity(self.gravity_rotation_speed)
        if keys[pygame.K_RIGHT]:
            self.physics_engine.rotate_gravity(-self.gravity_rotation_speed)
        
        # 调整重力大小
        if keys[pygame.K_UP]:
            new_magnitude = self.physics_engine.gravity_magnitude + self.gravity_magnitude_change_rate
            self.physics_engine.set_gravity_magnitude(new_magnitude)
        if keys[pygame.K_DOWN]:
            new_magnitude = max(0, self.physics_engine.gravity_magnitude - self.gravity_magnitude_change_rate)
            self.physics_engine.set_gravity_magnitude(new_magnitude)
    
    def draw_gravity_vector(self):
        """绘制重力向量"""
        if not self.show_gravity_vector:
            return
        
        # 重力向量起点 (屏幕中心)
        center_x, center_y = self.width // 2, self.height // 2
        
        # 计算终点 (根据重力方向和大小)
        # 缩放向量长度以适应屏幕
        scale_factor = min(self.width, self.height) / 5
        normalized_magnitude = min(1.0, self.physics_engine.gravity_magnitude / (9.8 * 60 * 3))
        
        end_x = center_x + int(self.physics_engine.gravity_direction[0] * scale_factor * normalized_magnitude)
        end_y = center_y + int(self.physics_engine.gravity_direction[1] * scale_factor * normalized_magnitude)
        
        # 绘制箭头主体
        arrow_color = (150, 50, 50)
        pygame.draw.line(self.screen, arrow_color, (center_x, center_y), (end_x, end_y), 3)
        
        # 绘制箭头头部
        arrow_head_length = 10
        arrow_head_width = 7
        
        # 计算箭头方向
        direction = np.array([end_x - center_x, end_y - center_y], dtype=float)
        if np.linalg.norm(direction) < 1e-10:  # 避免零向量
            return
            
        direction = direction / np.linalg.norm(direction)
        
        # 计算垂直于方向的向量
        perpendicular = np.array([-direction[1], direction[0]])
        
        # 箭头尖端
        tip = np.array([end_x, end_y])
        
        # 计算箭头底部两个点
        base1 = tip - arrow_head_length * direction + arrow_head_width * perpendicular
        base2 = tip - arrow_head_length * direction - arrow_head_width * perpendicular
        
        # 绘制箭头头部
        pygame.draw.polygon(self.screen, arrow_color, [
            (int(tip[0]), int(tip[1])),
            (int(base1[0]), int(base1[1])),
            (int(base2[0]), int(base2[1]))
        ])
    
    def draw_stats(self):
        """绘制统计信息"""
        if not self.show_stats:
            return
        
        stats = self.physics_engine.get_performance_stats()
        
        # 格式化重力信息
        gravity_direction = self.physics_engine.gravity_direction
        gravity_angle = np.degrees(np.arctan2(gravity_direction[1], gravity_direction[0]))
        
        # 准备显示文本
        texts = [
            f"FPS: {int(self.clock.get_fps())}",
            f"球数量: {stats['ball_count']}",
            f"碰撞检测次数: {stats['collision_checks']}",
            f"碰撞处理次数: {stats['collisions_resolved']}",
            f"自适应时间步长: {'开启' if self.adaptive_timestep else '关闭'}",
            f"轨迹显示: {'全部开启' if self.show_all_trajectories else '部分开启'}",
            f"重力大小: {self.physics_engine.gravity_magnitude / 60:.1f}g",
            f"重力角度: {gravity_angle:.1f}°",
            f"暂停状态: {'是' if self.paused else '否'}"
        ]
        
        # 绘制文本背景 - 使用Surface创建半透明背景
        text_height = 20
        panel_width = 200
        panel_height = 10 + len(texts) * text_height
        
        # 创建半透明surface
        s = pygame.Surface((panel_width, panel_height))
        s.set_alpha(180)  # 设置透明度 (0-255)
        s.fill((10, 10, 50))  # 深蓝色背景
        self.screen.blit(s, (10, 10))
        
        # 绘制文本
        for i, text in enumerate(texts):
            text_surface = self.font.render(text, True, (255, 255, 255))
            self.screen.blit(text_surface, (15, 15 + i * text_height))
    
    def draw_instructions(self):
        """绘制操作指南"""
        instructions = [
            "左键点击: 在点击位置添加小球",
            "右键点击: 清除所有小球",
            "A键: 随机位置添加小球",
            "C键: 清除所有小球",
            "S键: 显示/隐藏统计信息",
            "T键: 开启/关闭自适应时间步长",
            "R键: 显示/隐藏所有小球的轨迹",
            "G键: 显示/隐藏重力向量",
            "1-4键: 切换重力方向 (下/上/左/右)",
            "5-7键: 切换重力大小 (零/月球/木星)",
            "8-9键: 切换特定小球的轨迹",
            "方向键: 调整重力方向和大小",
            "空格键: 暂停/继续模拟"
        ]
        
        # 绘制文本背景 - 使用Surface创建半透明背景
        text_height = 20
        panel_width = 270
        panel_height = 10 + len(instructions) * text_height
        
        # 创建半透明surface
        s = pygame.Surface((panel_width, panel_height))
        s.set_alpha(180)  # 设置透明度 (0-255)
        s.fill((50, 10, 50))  # 深紫色背景
        self.screen.blit(s, (self.width - 280, 10))
        
        # 绘制文本
        for i, text in enumerate(instructions):
            text_surface = self.font.render(text, True, (255, 255, 255))
            self.screen.blit(text_surface, (self.width - 275, 15 + i * text_height))
    
    def run(self):
        """主循环"""
        # 默认添加一些球
        for _ in range(5):
            ball = self.create_random_ball()
            self.physics_engine.add_ball(ball)
        
        while True:
            # 处理事件
            self.handle_events()
            
            # 清屏
            self.screen.fill((240, 240, 240))
            
            # 绘制边界
            pygame.draw.rect(self.screen, (200, 200, 200), 
                             (0, 0, self.width, self.height), 2)
            
            # 绘制重力向量
            self.draw_gravity_vector()
            
            # 更新物理(如果未暂停)
            if not self.paused:
                self.physics_engine.update()
            
            # 绘制所有球
            for ball in self.physics_engine.balls:
                ball.draw(self.screen)
            
            # 绘制统计信息
            self.draw_stats()
            
            # 绘制操作指南
            self.draw_instructions()
            
            # 更新屏幕
            pygame.display.flip()
            
            # 控制帧率
            self.clock.tick(60)

# 启动模拟
if __name__ == "__main__":
    simulation = BallSimulation()
    simulation.run()

你可能感兴趣的:(python,运动微分,碰撞检测,物理模拟,pygame,自适应,重力,数学建模)