使用Processing和PixelFlow库创建交互式流体太极动画
- 引言
- 准备工作
- 效果展示
- 代码结构
- 代码解析
- 第一部分:导入库和设置基本参数
- 第二部分:流体类定义
- `MyFluidDataConfig` 类详解
- `MyFluidData` 类详解
- `my_update` 方法详解
- 流体类定义完整代码
- 第三部分:太极类定义
- 太极类定义完整代码
- 结语
引言
本教程将指导您如何使用Processing编程环境结合PixelFlow库来创建一个交互式的流体太极动画。PixelFlow是一个基于GPU的实时图形库,它提供了高效的流体模拟功能。我们将通过麦克风输入的音频振幅来驱动流体模拟,并在流体之上绘制一个动态的太极图案。
准备工作
-
安装Processing:访问Processing官网下载并安装Processing IDE。
-
导入DwPixelFlow库:在Processing中,通过菜单"Sketch" -> “Import Library…” -> “Add Library…”,搜索并安装
DwPixelFlow
库。 -
导入声音库:同样地,搜索并安装
Minim
库,用于处理声音输入。
效果展示
代码结构
我们的项目将包含以下几个主要部分:
- 设置窗口和基本参数:定义窗口大小、背景色和线条颜色。
- 声音输入设置:使用Processing的
AudioIn
和Amplitude
类来获取麦克风输入的音频振幅。 - 流体模拟设置:初始化PixelFlow库,创建流体对象,并设置其参数。
- 太极图案设置:创建太极对象,并为其分配绘制层。
- 主循环:在
draw
函数中更新流体模拟和太极图案。
代码解析
第一部分:导入库和设置基本参数
import com.thomasdiewald.pixelflow.java.DwPixelFlow;
import com.thomasdiewald.pixelflow.java.fluid.DwFluid2D;
import processing.core.*;
import processing.opengl.PGraphics2D;
import processing.sound.*;
int viewport_w = 600; // 定义窗口宽
int viewport_h = 600; // 定义窗口高
color bg_color = #FFFFFF; // 背景色
color line_color = #000000; // 线条颜色
DwFluid2D fluid; // 流体
PGraphics2D pg_fluid; // 流体层
// 声音麦克风输入
AudioIn audioIn;
// 振幅-音量
Amplitude rms;
Taichi taichi; // 太极
PGraphics2D pg_taichi; // 太极图层
PGraphics2D pg_obstacles; // 障碍物
float taichi_radius = 100;
void settings() {
size(viewport_w, viewport_h, P2D);
}
void setup() {
background(bg_color);
frameRate(60);
// 设定声音
setupSound();
// 设置流体参数
setupFluid();
// 设置太极图
setupTaichi();
}
void setupSound() {
audioIn = new AudioIn(this, 0);
audioIn.play();
rms = new Amplitude(this);
rms.input(audioIn);
}
void setupFluid() {
// 初始化pixelflow
DwPixelFlow context = new DwPixelFlow(this);
context.print();
context.printGL();
// 流体模拟
fluid = new DwFluid2D(context, width, height, 1);
fluid.param.dissipation_velocity = 0.70f;
fluid.param.dissipation_density = 0.60f;
fluid.addCallback_FluiData(new MyFluidData(rms));
// 流体层
pg_fluid = (PGraphics2D)createGraphics(width, height, P2D);
// 障碍物层
pg_obstacles = (PGraphics2D)createGraphics(viewport_w, viewport_h, P2D);
pg_obstacles.smooth(4);
}
void setupTaichi() {
pg_taichi = (PGraphics2D)createGraphics(viewport_w, viewport_h, P2D);
pg_taichi.smooth(4);
taichi = new Taichi(new PVector(width / 2, height / 2), 100, pg_taichi, rms);
}
void draw() {
// 流体更新
drawFluid();
// 太极图
drawTaichi();
}
void drawFluid() {
// 绘制障碍物
drawObstacles();
// 给流体增加障碍物
fluid.addObstacles(pg_obstacles);
// 流体更新
fluid.update();
fluid.renderFluidTextures(pg_fluid, 0);
// 显示流体层
image(pg_fluid, 0, 0);
// 显示障碍物层
image(pg_obstacles, 0, 0);
}
void drawObstacles() {
pg_obstacles.beginDraw();
pg_obstacles.blendMode(REPLACE);
pg_obstacles.clear();
float x = width * 0.5;
float y = height * 0.5;
pg_obstacles.pushMatrix();
pg_obstacles.translate(x, y);
pg_obstacles.stroke(line_color);
pg_obstacles.strokeWeight(2);
pg_obstacles.noFill();
pg_obstacles.circle(0, 0, 500);
pg_obstacles.popMatrix();
pg_obstacles.endDraw();
}
void drawTaichi() {
pg_taichi.beginDraw();
pg_taichi.blendMode(REPLACE);
pg_taichi.clear();
pg_taichi.pushMatrix();
taichi.display();
pg_taichi.popMatrix();
pg_taichi.endDraw();
image(pg_taichi, 0, 0);
}
第二部分:流体类定义
从您提供的代码看来,这段代码是用于在Processing环境中结合DwFluid库创建一个模拟流体的视觉效果的。代码中涉及到很多类和方法,主要包含以下几个方面:
-
MyFluidDataConfig
类:用于配置流体运动的参数,比如圆的位置、半径、运动的角度和速度以及颜色等。 -
MyFluidData
类:实现了DwFluid2D.FluidData
接口,用于更新流体物理仿真的状态,同时整合声音输入作为流体动态变化的一部分。 -
my_update
方法:是流体物理仿真更新的核心,它计算流体粒子的速度和位置,并且将这些数据传递给流体库来模拟流动。
现在,让我们进一步分析并解释每个部分的作用以及如何操作。
MyFluidDataConfig
类详解
这个类定义了流体仿真所需要的一些配置参数。例如:
x
,y
: 圆心位置。radius
: 半径。isClockwise
: 定义旋转方向。angleSpeed
: 角速度。rx
,ry
,prx
,pry
: 当前和之前的位置坐标。angle
: 当前角度。c
: 颜色。
MyFluidData
类详解
此类使用 MyFluidDataConfig
中定义的配置来创建两个配置对象 config1
和 config2
。它们负责控制流体仿真中两个独立的流动体的行为。此外,它接收一个Amplitude
对象(rms
),它可能用于分析音频信号并将其影响应用于流体动画。
my_update
方法详解
这个方法是每一帧都会调用的,用于更新流体的状态。步骤如下:
- 使用极坐标计算弧上点的位置。
- 随机量产生抖动,使流动效果更自然。
- 计算速度,并根据是否顺时针调整y轴速度。
- 将计算的位置和速度用来更新流体对象的状态。
- 根据声音级别调整流体密度的半径大小。
- 添加颜色和密度到流体中。
流体类定义完整代码
public class MyFluidDataConfig {
float x; // 圆心位置x
float y; // 圆心位置y
float radius = 100; // 半径
boolean isClockwise; // 是否是顺时针
float angleSpeed = 0.04;
float rx, ry, prx, pry; // 圆周运动,弧上的点位置,以及上一帧的点位置
float angle = 0; // 角度
color c; // 颜色
}
public class MyFluidData implements DwFluid2D.FluidData {
MyFluidDataConfig config1;
MyFluidDataConfig config2;
Amplitude rms;
MyFluidData(Amplitude rms) {
this.rms = rms;
float x1 = width * 0.5;
float y1 = height * 0.5;
config1 = new MyFluidDataConfig();
config1.x = x1;
config1.y = y1;
config1.radius = 130;
config1.isClockwise = true;
config1.angleSpeed = 0.05;
config1.c = color(0.0, 0.0, 0.0);
config1.angle = PI / 2;
config2 = new MyFluidDataConfig();
config2.x = x1;
config2.y = y1;
config2.radius = 130;
config2.isClockwise = true;
config2.angleSpeed = 0.04;
config2.c = color(0.0, 0.0, 0.0);
config2.angle = - PI / 2;
}
void my_update(DwFluid2D fluid, MyFluidDataConfig config) {
float vscale = 14;
float soundLevel = rms.analyze() * 1000;
float delta = random(-3, 3);
// 极坐标下计算弧上点的位置,用一个随机量进行抖动
config.rx = config.x + (config.radius + delta) * cos(config.angle);
config.ry = config.y + (config.radius + delta) * sin(config.angle);
// 计算速度
float vx = (config.rx - config.prx) * vscale;
float vy = (config.ry - config.pry) * vscale;
// 顺时针的话,需要乘以-1,因为y轴相反
if (config.isClockwise) {
vy = (config.ry - config.pry) * (-vscale);
}
float px = config.rx;
float py = config.ry;
// 顺时针的话,需要乘以-1,因为y轴相反
if (config.isClockwise) {
py = height - config.ry;
}
// 给流体上的点添加速度
fluid.addVelocity(px, py, 16, vx, vy);
float radius1 = map(soundLevel, 10, 600, 15, 20);
float radius2 = map(soundLevel, 10, 500, 8, 12);
println(soundLevel, radius1, radius2);
// 给流体上的点添加密度,颜色为c,半径为radius1,稍微大点
fluid.addDensity(px, py, radius1, red(config.c) / 255.0, green(config.c) / 255.0, blue(config.c) / 255.0, 1.0f);
// 给流体上的点添加密度,颜色为白色,半径为radius2,稍微小点
fluid.addDensity(px, py, radius2, 1.0f, 1.0f, 1.0f, 1.0f);
//fluid.addTemperature(px, py, 30, 10);
// 现在终将成为过去
config.prx = config.rx;
config.pry = config.ry;
// 增加弧度角,用于下一帧计算,才能旋转
float angleSpeed = constrain(radians(soundLevel * 0.03), 0.01, 0.08) * 3;
println(soundLevel, angleSpeed);
config.angle += angleSpeed;
}
@Override
public void update(DwFluid2D fluid) {
my_update(fluid, config1);
my_update(fluid, config2);
}
}
第三部分:太极类定义
太极类定义完整代码
class Taichi {
PVector location;
float radius;
PGraphics2D pg;
float angle = 0;
Amplitude rms;
Taichi(PVector location, float radius, PGraphics2D pg, Amplitude rms) {
this.location = location;
this.radius = radius;
this.pg = pg;
this.rms = rms;
}
void display() {
float d = 2 * radius;
float soundLevel = rms.analyze();
angle += TWO_PI/360 * soundLevel * 20+6;
pg.noStroke();
pg.translate(location.x, location.y); // 平移坐标系,方便使用相对位置进行绘制
pg.rotate(angle);
pg.fill(0);
pg.arc(0, 0, d, d, PI / 2, PI * 3 / 2);
pg.fill(255);
pg.arc(0, 0, d, d, -PI / 2, PI / 2);
pg.fill(255);
pg.circle(0, d / 4, radius);
pg.fill(0);
pg.circle(0, -d / 4, radius);
pg.fill(0);
pg.circle(0, d / 4, radius / 5);
pg.fill(255);
pg.circle(0, -d / 4, radius / 5);
}
}
结语
通过本教程,您将学习到如何结合Processing的声音输入和PixelFlow的流体模拟功能,创建一个动态的太极动画。您可以根据自己的创意进一步扩展这个项目,例如添加更多的交互元素或改变流体的行为。记得在编写代码时保持耐心,实践是学习编程的最佳方式。