#生成艺术 #处理 #状态 #抽象 #绘制 #调用 #零成本

nightly pollock

一个适合生成艺术和简单游戏的开源 Rust 库,类似于 Processing

5 个不稳定版本

使用旧 Rust 2015

0.3.2 2018年9月28日
0.2.2 2018年9月27日
0.2.1 2018年9月27日
0.2.0 2018年9月27日
0.1.0 2018年9月23日

1804 in Rust 模式

无许可证AGPL-3.0-or-later

1.5MB
1K SLoC

Pollock

糟糕代码的框架

Pollock 是 Processing 理想和 API 在 Rust 中的足够惯用实现,用于制作生成艺术、小型游戏或仅作为玩具来玩耍。它尽量减少对代码的限制,使用它编写代码的感觉就像在动态语言中编写代码一样舒适,但更少错误和更好的性能。尽管如此,目前调试模式下的性能相当糟糕,因为我大量使用了零成本抽象,这严重依赖于优化。但是,发布模式下的性能已经非常出色,并且可能还可以进一步提高。一个限制是,我确实始终想要一个即时模式接口,尽管我很乐意找到一种方法来至少在后台部分使用保留模式。我已经将所有调用批处理为单个绘制调用。

它仍然是预预预alpha版本,但已经有一些酷炫的功能,如保存视频和类似模拟器的保存状态系统,可以用于提供类似热重载的工作流程 - 进行更改,重新运行程序并重新加载状态,以回到您所在的位置。

Pollock 设计理念

  • 糟糕的是最好的
  • 不要让我思考
  • 性能是国王,但可用性是上帝

Pollock 设计允许快速对不打算维护的代码进行修改。它是用于实验和错误的。

如果您之前使用过 Processing,API 将看起来很熟悉。如果您使用过一些流行的 Rust 游戏框架,API 可能看起来完全陌生。首先,您不需要定义一个特质。我选择使用构建器模式来避免需要在每个类型上注解的问题,并意味着您不需要理解 Rust,就能编写出东西,即使它只是简单的东西。

extern crate pollock;

use pollock::*;

fn main() {
    Pollock::setup(|p| {
        p.size = (600, 480);
        // Like 
        p.background = Fill::default();
        p.fill = Fill::none();
        p.stroke = Stroke::new(rgb(0, 0, 0), 3);
    }).draw(|p| {
        // Example spinning square
        let mut p = p.push();
        let size = 50;
        let (w, h) = p.size;
        let frame_count = p.frame_count as f64;

        p.rotate(frame_count / 100.);
        p.translate(v2(w, h) / 2.);
        p.rect(v2(-size / 2, -size / 2), size, size);
    }).run();
}

v2 创建一个向量,push 是 Processing 的 pushMatrix 的 Rust 版本(当新的 p 超出作用域时,更改会被弹出)。我认为其余部分都是不言而喻的。我使用来自 nalgebraVector2 类型作为我的向量类型,尽管这可能在将来改变,因为使用此类型时的错误信息是绝对无法辩护的。它确实允许你做到一些在 Processing 中难以实现的好东西。例如,你可以直接对向量进行加减运算,将它们乘以标量,以及其他在处理 2D 图形时非常有用的所有其他好事。我目前没有公开许多 nalgebra 的功能,但随着我对 API 设计的反馈越来越多,我可能会添加更多。

在以下示例中没有展示的是,我没有 startShapeendShape 的等价物,所有操作都是通过迭代器完成的。你可以从 V2(即 2D 向量)的迭代器中构建多边形和线条(自动完成连接)。这非常高效,可以说是更加直观,而且不会出现不小心忘记调用 endShape 的可能性。

处理可变状态

在 Python 中处理可变状态的方法是使用全局变量。这在 Pollock 中也适用(尽管它们是在你调用的 Pollock::run 函数的作用域内的可变变量),但 Pollock 还支持状态结构的概念。好处是你可以免费获取保存状态,但这确实增加了额外的类型注解负担,因此对于简单的项目,你可以直接使用全局变量。

使用状态结构就像只是从 setup 函数返回它,然后在主循环中使用 p.state 来访问它一样简单。例如,对于显示一些弹跳球的简单草图,它可能看起来像这样

extern crate pollock;
extern crate serde;
#[macro_use]
extern crate serde_derive;

use pollock::*;

#[derive(Serialize, Deserialize)]
struct State {
    balls: Vec<(V2, V2)>,
}

fn main() {
    let radius = 10.;

    Pollock::setup(|p| {
        p.size = (600, 480);
        p.background = Fill::default();
        p.stroke = Stroke::none();
        // We add alpha so we can see where the balls intersect
        // (also so I can show off the `rgba` function.
        p.fill = Fill::new(rgba(0, 0, 0, 100));

        State {
            balls: (0..10)
                .map(|_| {
                    (
                        v2(
                            p.random_range(0., p.width() as f64),
                            p.random_range(0., p.height() as f64),
                        ),
                        v2(
                            if p.random() { -1 } else { 1 },
                            if p.random() { -1 } else { 1 },
                        ),
                    )
                }).collect(),
        }
    }).draw(|p| {
        let (width, height) = p.size;
        // A limitation of Rust's disjointness analysis means that
        // we have to loop twice here.
        for (pos, vel) in &mut p.state.balls {
            if pos.x < radius {
                vel.x = 1.
            } else if pos.x > width as f64 - radius {
                vel.x = -1.
            }

            if pos.y < radius {
                vel.y = 1.
            } else if pos.y > height as f64 - radius {
                vel.y = -1.
            }

            *pos += *vel * 1.;
        }

        for (pos, _) in p.state.balls.iter() {
            p.circle(*pos, radius);
        }
    })
    .on_key_down(Key::O, |p| p.save_state("state"))
    .on_key_down(Key::P, |p| p.load_state("state"))
    .on_key_down(Key::Space, |p| p.paused = !p.paused)
    .run();
}

你可以看到它非常简洁,并且(在我看来)非常易于阅读,尽管你必须知道从 setup 返回状态使其可以在 p.state 中访问,这是相当神奇的。

如果你想知道,你可以在 setup 中访问 p.state,但它只是具有 () 类型。

你可以如何帮助

使用它!告诉我哪些功能缺失,以及它是否像我希望的那样易于使用。我确实担心我大量使用泛型和 Deref 可能会使文档难以阅读,但通过仔细使用类型别名,这可能有所帮助。我想将 Processing 教程移植到 Pollock,以找出哪些常见操作不受支持。

以下是一个我从 Processing 草图移植到 Pollock 的示例 gif

未来功能

  • 图像
  • 文本
  • 声音输入(来自文件和/或麦克风)用于可视化
  • 鼠标输入
  • 模块系统,用于向 PollockState 添加额外功能,使其看起来像是在核心库中实现。我有一些关于如何使用类型系统实现此目的的想法,但我还没有提出具体方案。
  • 更多内置数学助手

依赖项

~26–36MB
~440K SLoC