matplotlib 齐次坐标系 绘制 2D 闪烁斑点

绘制闪烁的斑点群,需要考虑几个群体属性:群体的生成位置 (xylim),斑点数量 (n),斑点的半径均值 (r),斑点的寿命均值 (delta)

而对于每一个斑点,又需要考虑斑点个体属性:出生时间 (start)、出生位置 (xy)、半径 (radius)、颜色 (color)、寿命 (deltas),以控制斑点的外形和闪烁速度

在 Spot 这个对象中,我定义了以下几个函数:

  • __init__:将形参写入闪烁斑点的群体属性,并调用 produce 生成指定数量的斑点
  • produce:筛除寿命耗尽的斑点,并生成新的斑点的个体属性
  • scatter:根据群体属性 xylim 在指定方框内生成斑点的中心点,可重写
  • __call__:根据闪烁斑点的生存时间,计算闪烁斑点的不透明度,并利用 CoordSys_2d 对斑点的位置进行仿射变换,再逐一绘制斑点
import time

import matplotlib.patches as pch
import matplotlib.pyplot as plt
import numpy as np

# coord.py 详见: https://blog.csdn.net/qq_55745968/article/details/129912954
from coord import CoordSys_2d

red = 'orangered'
orange = 'orange'
yellow = 'yellow'
green = 'greenyellow'
cyan = 'aqua'
blue = 'deepskyblue'
purple = 'mediumpurple'
pink = 'violet'


class Spot:
    ''' 闪烁斑点对象
        xylim: xy 坐标区间, [[xmin, ymin], [xmax, ymax]]
        n: 闪烁斑点的数量
        r: 斑点的半径均值, 标准差为 r/2, 最小值为 r/4
        delta: 斑点生存时间的均值, 标准差为 delta, 最小值为 delta/10'''
    colors = [red, orange, yellow, green, cyan, blue, purple, pink]

    def __init__(self, xylim: np.ndarray, n: int,
                 r: float = .2, delta: float = 1., alpha: float = .7):
        # <群体属性>
        self.xylim = xylim
        self.n, self.r = n, r
        self.delta = delta
        self.alpha = alpha
        # <个体属性>
        # 出生时间, 生存时间
        self.start = np.array([])
        self.deltas = np.array([])
        # 出生位置, 半径, 颜色
        self.xy = np.ones([0, 2])
        self.radius = np.array([])
        self.color = np.array([])
        # 生产斑点
        self.produce()

    def scatter(self, n):
        return self.xylim[0] + np.random.rand(n, 2) * (self.xylim[1] - self.xylim[0])

    def produce(self, filt=None):
        # 筛除生存时间耗尽的斑点
        if isinstance(filt, np.ndarray):
            for key in ('start', 'color', 'xy', 'radius', 'deltas'):
                setattr(self, key, getattr(self, key)[filt])
        # 补全缺失的斑点
        lack = self.n - self.xy.shape[0]
        if lack > 0:
            # 记录出生时间, 生存时间
            self.start = np.concatenate((self.start, np.full(lack, fill_value=time.time())))
            self.deltas = np.concatenate((self.deltas, np.maximum(np.random.normal(
                loc=self.delta, scale=self.delta, size=lack), self.delta / 10)))
            # 随机位置, 随机半径, 随机颜色
            self.xy = np.concatenate((self.xy, self.scatter(lack)), axis=0)
            self.radius = np.concatenate((self.radius, np.maximum(np.random.normal(
                loc=self.r, scale=self.r / 2, size=lack), self.r / 4)))
            self.color = np.concatenate((self.color, np.random.choice(self.colors, size=lack)))

    def __call__(self, fig, state: CoordSys_2d = None):
        ''' 刷新斑点的透明度
            state: CoordSys_2d 对象'''
        x = time.time() - self.start
        # y = 4/d^2 x (d - x)
        alpha = self.alpha * np.maximum(4 / self.deltas ** 2 * x * (self.deltas - x), 0)
        # 向图像添加斑点
        for i, xy in enumerate(np.stack(state.apply(*self.xy.T), axis=-1) if state else self.xy):
            patch = pch.Circle(xy, self.radius[i], alpha=alpha[i], edgecolor=None, facecolor=self.color[i])
            fig.add_patch(patch)
        self.produce(alpha > 0)

而对于闪烁斑点的旋转、平移,主要使用了 https://hebitzj.blog.csdn.net/article/details/129912954 中所编写的类 Coord_2d,这个类有以下几个主要函数:

  • transform:给定 x,y 轴上的偏移量、旋转角,并执行相对变换 / 绝对变换
  • apply:给定描述 x,y 的点集,根据齐次坐标系矩阵对该点集进行平移和旋转

利用这个类,可以实现闪烁斑点的持续旋转

if __name__ == '__main__':
    plt.rcParams['figure.figsize'] = [6.4, 6.4]

    fig = plt.subplot()
    # 初始化齐次坐标系
    state = CoordSys_2d()
    # 初始化闪烁斑点
    spot = Spot(xylim=np.array([[-2, 2], [-1, 1]]).T, n=100, r=.2, delta=1)

    while True:
        fig.cla()
        plt.xlim([-2, 2])
        plt.ylim([-2, 2])
        # 使当前的齐次坐标系旋转 2°
        state = state.transform(theta=2)
        # 调用闪烁斑点的 __call__ 函数绘制
        spot(fig, state=state)
        plt.pause(0.01)

猜你喜欢

转载自blog.csdn.net/qq_55745968/article/details/130049672
今日推荐