19、利用向量实现变形虫模拟运动

利用向量实现变形虫模拟运动

1. 面向对象编程中的抽象

在面向对象编程里,抽象是一项关键设计理念。我们要决定隐藏哪些细节,通过属性和方法暴露哪些内容。以 Python 为例,汽车对象就是对现实世界汽车的抽象代码表示。为了让车辆在屏幕上移动,无需对每个螺栓、齿轮和电线进行建模,这样做简化了代码。而且, Car 类会把复杂的 Python 指令(如外观、运动等)简化为直观的方法,像 shiftGear() steer() accelerate()

作为程序员,要考虑如何在程序中运用抽象,包括在 Python 里对对象的建模。最佳方法并非总是清晰明确,也没有绝对的对错之分。但要记住,好的抽象能让代码更简洁、清晰、模块化且易于维护。

2. 将 Python 代码拆分为多个文件

随着程序复杂度的提升,代码行数会不断增加。为了更好地组织项目,Python 提供了将代码拆分为多个文件(模块)的机制。

操作步骤如下:
1. 在 Processing 编辑器中,点击 microscopic 标签右侧的箭头,从弹出菜单中选择 New Tab ,并将新文件命名为 amoeba 。Processing 会自动为文件名添加 .py 扩展名,此时 amoeba.py 模块会作为一个标签与 microscopic 标签并列显示。
2. 切换到 microscopic 标签,选中 Amoeba 类的所有代码并剪切,然后切换到 amoeba.py 标签并粘贴代码。
3. 切换回 microscopic 标签,剩余代码从 a1 = Amoeba(400, 200, 100) 开始。
4. 使用 import 关键字导入模块,导入语句必须位于实例化变形虫的代码之前,通常放在文件顶部。以下是 microscopic 标签的完整代码:

from amoeba import Amoeba
a1 = Amoeba(400, 200, 100)

def setup():
    size(800, 400)
    frameRate(120)

def draw():
    background('#004477')
    a1.display()

模块的使用有诸多好处,它能减少主草图的代码行数,隐藏每个模块的内部工作原理,让程序员专注于更高级的逻辑。同时,模块也便于其他程序员浏览项目代码,理解程序结构。

3. 用向量编程实现运动

这里使用的是欧几里得向量来为变形虫的运动建模。欧几里得向量具有大小和方向两个属性,可用于表示推动变形虫的力。

例如,变形虫从位置 A 移动到位置 B,移动的总距离 4 个单位就是向量的大小,它描述了力的强度,但不表明力的方向。向量由多个标量组成,如向量 v = (4, 3) 表示一个力,能使变形虫从先前位置向右移动 4 个单位,向上移动 3 个单位。

通过勾股定理可以计算向量的大小,不过 Processing 提供了内置的 PVector 类来处理向量,其中的 mag() 方法可用于计算向量的大小。

v = PVector(4, 3)
magnitude = v.mag()
print(magnitude)  # 输出 5.0
4. PVector 类的使用

PVector 是 Processing 内置的用于处理欧几里得向量的类,无需导入即可在草图中使用。创建二维向量时, PVector() 类需要 x y 两个参数。

为了让变形虫在显示窗口中移动,我们可以创建一个 PVector 实例来表示推进力。

操作步骤如下:
1. 切换到 amoeba.py 标签,在 __init__() 方法中添加一个新的推进向量:

class Amoeba(object):
    def __init__(self, x, y, diameter, xspeed, yspeed):
        ...
        self.propulsion = PVector(xspeed, yspeed)
  1. 切换到 microscopic 标签,使用 Amoeba() 的第四个和第五个参数将推进向量的 x y 分量分别设置为 3 和 -1,并在 draw() 函数中增加变形虫的 x y 属性:
a1 = Amoeba(400, 200, 100, 3, -1)

def draw():
    background('#004477')
    a1.x += a1.propulsion.x
    a1.y += a1.propulsion.y
    a1.display()

每帧中,变形虫 a1 x 值增加 3 个像素, y 值减少 1 个像素,在默认的 Processing 坐标系中, y 值减少意味着变形虫向上移动。运行草图,变形虫将沿对角线轨迹快速移动,从显示窗口中心开始,很快从右上角下方移出。

我们还可以使用 PVector 实例来存储变形虫的 x y 坐标。

操作步骤如下:
1. 切换到 amoeba.py 标签,将 self.x self.y 属性替换为一个名为 self.location 的新向量:

class Amoeba(object):
    def __init__(self, x, y, diameter):
        print('amoeba initialized')
        self.location = PVector(x, y)
        ...
  1. 由于 Amoeba 类中有多处引用了 self.x self.y ,需要将它们全部替换为 self.location.x self.location.y 。可以使用 Processing 菜单栏中的 Edit -> Find 工具进行查找和替换操作。
  2. 切换到 microscopic 标签,将 a1.x a1.y 分别改为 a1.location.x a1.location.y
a1.location.x += a1.propulsion.x
a1.location.y += a1.propulsion.y

使用 PVector 加法可以更高效地实现上述操作,将推进向量和位置向量相加:

def draw():
    background('#004477')
    a1.location += a1.propulsion
    a1.display()
5. 向量的加法和减法
向量加法

PVector 类支持使用 + 运算符进行向量加法, += 作为增强赋值运算符,可将左侧向量操作数更新为自身与右侧操作数的和。

例如,添加一个新的水流向量 current 来模拟对角流动的水流,辅助变形虫的运动:

current = PVector(1, -2)

def draw():
    background('#004477')
    a1.location += a1.propulsion
    a1.location += current
    a1.display()

向量加法通过将一个向量的 x 分量与另一个向量的 x 分量相加, y 分量同理。向量加法是可交换的,即改变操作数的顺序不影响结果。

向量减法

向量减法的结果是两个向量的差。与向量加法不同,向量减法是非交换的,改变操作数的顺序会得到不同的结果。

可以使用 - 运算符进行 PVector 实例的减法操作:

print(current - a1.propulsion)

为了让变形虫向鼠标指针移动,我们可以创建一个 pointer 向量来存储鼠标指针的 x-y 坐标,并计算 pointer location 的差向量:

current = PVector(1, -2)

def draw():
    background('#004477')
    pointer = PVector(mouseX, mouseY)
    difference = pointer - a1.location
    a1.location += difference
    ...

但直接这样做会使变形虫瞬间跳到鼠标指针位置,我们需要限制向量的大小,让变形虫逐步向指针移动。

6. 限制向量大小

PVector 类提供了 limit() 方法来限制向量的大小,而不影响其方向。该方法需要一个标量参数,表示最大大小。

修改 draw() 函数,引导变形虫向鼠标指针移动:

def draw():
    ...
    # 1 #a1.location += difference
    a1.propulsion += difference.limit(0.03)
    a1.location += a1.propulsion.limit(3)
    a1.location += current
    a1.display()

为了让多个变形虫以不同速度移动,我们可以在 Amoeba 类中添加一个 maxpropulsion 属性:

class Amoeba(object):
    def __init__(self, x, y, diameter, xspeed, yspeed):
        ...
        self.maxpropulsion = self.propulsion.mag()

同时调整 microscopic 标签中的代码,使用 maxpropulsion 属性:

a1 = Amoeba(400, 200, 100, 0.3, -0.1)
current = PVector(0.1, -0.2)

def draw():
    ...
    a1.propulsion += difference.limit(a1.maxpropulsion/100)
    a1.location += a1.propulsion.limit(a1.maxpropulsion)
    ...

以下是向量操作的总结表格:
| 操作 | 描述 | 示例代码 |
| — | — | — |
| 向量大小计算 | 使用 mag() 方法计算向量大小 | v = PVector(4, 3); magnitude = v.mag() |
| 向量加法 | 使用 + += 运算符进行向量相加 | a1.location += a1.propulsion |
| 向量减法 | 使用 - 运算符进行向量相减 | difference = pointer - a1.location |
| 限制向量大小 | 使用 limit() 方法限制向量大小 | a1.propulsion += difference.limit(0.03) |

下面是变形虫运动的简单流程图:

graph TD;
    A[初始化变形虫和向量] --> B[进入 draw() 函数];
    B --> C[计算差向量];
    C --> D[更新推进向量];
    D --> E[更新位置向量];
    E --> F[显示变形虫];
    F --> B;

利用向量实现变形虫模拟运动

7. 模拟中添加多个变形虫

之前我们处理的是单个变形虫实例 a1 ,现在要创建一个变形虫群体。可以从同一个类创建任意数量的实例,下面将在同一显示窗口中生成八个变形虫,每个变形虫的大小和起始坐标都不同。

手动添加变形虫的问题

手动定义多个变形虫实例,如:

a1 = Amoeba(400, 200, 100, 0.3, -0.1)
sam = Amoeba(643, 105, 56, 0.4, -0.4)
bob = Amoeba(295, 341, 108, -0.3, -0.1)
lee = Amoeba(97, 182, 198, -0.1, 0.2)

这种方式存在明显的缺点。要显示这些变形虫, draw() 函数需要为每个实例调用 display() 方法:

def draw():
    ...
    a1.display()
    sam.display()
    bob.display()
    lee.display()
    ...

如果要让它们移动, draw() 函数还需要更多代码。当变形虫数量增加时,这种方式效率极低。

使用列表管理变形虫

更好的方法是将变形虫存储在列表中,使用循环生成所需数量的变形虫,并通过另一个循环调用每个变形虫的 display() 方法和移动代码。

以下是修改后的代码:

from amoeba import Amoeba
amoebas = []
for i in range(8):
    diameter = random(50, 200)
    speed = 1000 / (diameter * 50)
    x, y = random(800), random(400)
    amoebas.append(Amoeba(x, y, diameter, speed, speed))

def draw():
    background('#004477')
    pointer = PVector(mouseX, mouseY)
    for a in amoebas:
        difference = pointer - a.location
        a.propulsion += difference.limit(a.maxpropulsion/100)
        a.location += a.propulsion.limit(a.maxpropulsion)
        a.location += current
        a.display()

在上述代码中,第一个 for 循环随机生成每个变形虫的直径、速度和起始坐标,并将其添加到 amoebas 列表中。第二个 for 循环遍历列表,为每个变形虫计算更新后的位置并显示。

处理变形虫越界问题

较大、较慢的变形虫可能会被水流推出显示窗口,为避免这种情况,可以添加边界环绕代码,使变形虫离开窗口后从另一侧重新出现。

for a in amoebas:
    ...
    r = a.d / 2
    if a.location.x - r > width:   
        a.location.x = 0 - r
    if a.location.x + r < 0:       
        a.location.x = width + r
    if a.location.y - r > height:  
        a.location.y = 0 - r
    if a.location.y + r < 0:       
        a.location.y = height + r

这四个 if 语句检查变形虫是否完全离开显示窗口的四条边,如果是,则将其重新定位到窗口的另一侧。

8. 总结与拓展

通过以上步骤,我们实现了多个变形虫在显示窗口中的模拟运动,并且可以让它们向鼠标指针移动。整个过程涉及到面向对象编程、向量运算、代码模块化等多个编程概念。

以下是整个项目的关键步骤总结表格:
| 步骤 | 操作 | 代码示例 |
| — | — | — |
| 代码拆分 | 创建 amoeba.py 模块并导入 | from amoeba import Amoeba |
| 向量使用 | 创建推进向量和位置向量 | self.propulsion = PVector(xspeed, yspeed); self.location = PVector(x, y) |
| 向量运算 | 向量加法、减法和限制大小 | a1.location += a1.propulsion; difference = pointer - a1.location; a1.propulsion += difference.limit(0.03) |
| 多变形虫管理 | 使用列表存储变形虫并循环处理 | amoebas = []; for a in amoebas: ... |
| 边界处理 | 实现边界环绕 | if a.location.x - r > width: a.location.x = 0 - r |

下面是整个变形虫模拟系统的流程图:

graph TD;
    A[初始化模块和向量] --> B[生成变形虫列表];
    B --> C[进入 draw() 函数];
    C --> D[计算鼠标指针与变形虫的差向量];
    D --> E[更新变形虫推进向量];
    E --> F[更新变形虫位置向量];
    F --> G[检查变形虫是否越界并处理];
    G --> H[显示变形虫];
    H --> C;

向量和 PVector 类还有更多的操作,如向量乘法、除法、归一化和处理三维向量等。这些知识可以应用于需要物理模拟的编程项目,如视频游戏等。你可以根据自己的需求进一步拓展这个变形虫模拟系统,例如添加更多的力、改变变形虫的行为等。

一、 内容概要 本资源提供了一个完整的“金属板材压弯成型”非线性仿真案例,基于ABAQUS/Explicit或Standard求解器完成。案例精确模拟了模具(凸模、凹模)与金属板材之间的接触、压合过程,直至板材发生塑性弯曲成型。 模型特点:包含完整的模具-工件装配体,定义了刚体约束、通用接触(或面面接触)及摩擦系数。 材料定义:金属板材采用弹塑性材料模型,定义了完整的屈服强度、塑性应变等真实应力-应变数据。 关键结果:提供了成型过程中的板材应力(Mises应力)、塑性应变(PE)、厚度变化​ 云图,以及模具受力(接触力)曲线,完整再现了压弯工艺的力学状态。 二、 适用人群 CAE工程师/工艺工程师:从事钣金冲压、模具设计、金属成型工艺分析与优化的专业人员。 高校师生:学习ABAQUS非线性分析、金属塑性成形理论,或从事相关课题研究的硕士/博士生。 结构设计工程师:需要评估钣金件可制造性(DFM)或预测成型回弹的设计人员。 三、 使用场景及目标 学习目标: 掌握在ABAQUS中设置金属塑性成形仿真的全流程,包括材料定义、复杂接触设置、边界条件与载荷步。 学习如何调试和分析大变形、非线性接触问题的收敛性技巧。 理解如何通过仿真预测成型缺陷(如减薄、破裂、回弹),并与理论或实验进行对比验证。 应用价值:本案例的建模方法与分析思路可直接应用于汽车覆盖件、电器外壳、结构件等钣金产品的冲压工艺开发与模具设计优化,减少试模成本。 四、 其他说明 资源包内包含参数化的INP文件、CAE模型文件、材料数据参考及一份简要的操作要点说明文档。INP文件便于用户直接修改关键参数(如压边力、摩擦系数、行程)进行自主研究。 建议使用ABAQUS 2022或更高版本打开。显式动力学分析(如用Explicit)对计算资源有一定要求。 本案例为教学与工程参考目的提供,用户可基于此框架进行拓展,应用于V型弯曲
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值