针对《代码本色》一书中以运动为主题的编程创造,这里进行一些p5的尝试。
粒子旋转
控制粒子按照动态角度进行旋转,旋转角度和时间的平方呈正比。
按住下键控制偏转角度变小
按住上键控制偏转角度变大
上箭头键控制粒子的旋转偏转角增大,下箭头控制粒子的旋转偏转角度减小。由于粒子的旋转角度按照帧率运动,所以粒子的轨迹会逐渐变成为圆。此时,增大或缩小旋转偏转角度就约等于增大或缩小旋转半径。而角度过大或者过小的粒子角度就会作为“不稳定”的存在脱离原本的旋转轨道。
let objs;
var rotatesize = 0.0074;
function setup() {
createCanvas(800, 800);
objs = new Array();
noStroke();
colorMode(HSB, 360, 100, 100);
}
function draw() {
background(10, 0.02);
translate(width / 2, height / 2);
objs.push(new myline(createVector(2, 0).rotate(frameCount * frameCount / 1000), color(frameCount % 360, frameCount % 20 + 20, frameCount % 100 + 50)));
objs.forEach(element => {
element.cnt++;
element.position.add(element.vec);
if (element.cnt < 600) {
element.vec.rotate(rotatesize);
}
fill(element.color);
ellipse(element.position.x, element.position.y, 2, 2);
});
if (frameCount > 1000) {
objs.shift();
}
}
function mouseClicked() {
if (mouseButton == LEFT) {
rotatesize = rotatesize + 0.001;
}
else if (mouseButton == CENTER) {
rotatesize = rotatesize - 0.001;
}
}
class myline {
constructor(vec, col) {
this.position = createVector(0, 0);
this.vec = vec;
this.color = col;
this.cnt = 0;
}
}
引力与斥力(接实验A3的粒子吸引)
上一次的粒子只有单一的色彩,本次将粒子的颜色进行优化,使其看起来像一个小光斑。同时,设置了粒子的边界运动,粒子运动出边界时,会将其从另一侧进入。
用该系统模拟眼泪下坠过程
按下e键降低粒子运动速度,按下q键生成粒子,按下w键清空画布。相比较于A3作业的结果多了一些光晕,这是通过选择blendMode模式来使得整体粒子的观感提升。
var g, friction;
var particles = [];
var n = 0;
var fr;
function setup() {
createCanvas(600, 600);
blendMode(ADD);
imageMode(CENTER);
frameRate(60);
background(0);
}
function draw() {
clear();
background(0);
for (var i = 0; i < n; i++) {
for (var j = 0; j < n; j++) {
if (i != j) {
var force = particles[i].attract(particles[j]);
particles[i].applyForce(force);
}
}
}
for (var i = 0; i < n; i++) {
particles[i].update();
particles[i].wallThrough();
particles[i].display();
}
}
function mouseClicked() {
particles.push(new ParticleA(mouseX, mouseY, 0, 0));
particles[n].createParticleImage();
n++;
}
function keyTyped() {
if (key === 'q') {
particles.push(new ParticleA(mouseX, mouseY, 0, 0));
particles[n].createParticleImage();
n++;
}
else if (key === 'w') {
particles = [];
n = 0;
}
else if (key === 'e') {
for (var particle of particles) {
particle.velocity.x = random(-2, 2);
particle.velocity.y = random(-2, 2);
}
}
}
class ParticleA {
constructor(x, y, vx, vy) {
this.mass = 10.0;
this.location = createVector(random(width), random(height));
this.location = createVector(x, y);
this.velocity = createVector(0.0, 0.0);
this.accelaration = createVector(vx, vy);
this.img;
}
update() {
this.velocity.add(this.accelaration);
this.velocity.mult(1.0 - friction);
this.location.add(this.velocity);
this.accelaration.set(0.0, 0.0);
}
display() {
image(this.img, this.location.x, this.location.y);
}
attract(particle) {
var force = p5.Vector.sub(particle.location, this.location);
var distance = force.mag();
distance = constrain(distance, 4.0, 1000.0);
if (distance > 20.0) {
force.normalize();
var strength = (g * this.mass * particle.mass) / pow(distance, 2.0) / 100.0;
force.mult(strength);
print(1);
}
else {
force.normalize();
var strength = (g * this.mass * particle.mass) / pow(distance, 2.0) / 100.0;
force.mult(strength);
print(2);
}
return force;
}
applyForce(force) {
var f = p5.Vector.div(force, this.mass);
this.accelaration.add(f);
}
wallThrough() {
if (this.location.x > width) {
this.location.x = 0;
}
if (this.location.x < 0) {
this.location.x = width;
}
if (this.location.y > height) {
this.location.y = 0;
}
if (this.location.y < 0) {
this.location.y = height;
}
}
createParticleImage() {
var side = 400;
var center = 200;
this.img = createImage(side, side);
var num = pow(10, 1.8);
var Cr = random(100, 255);
var Cg = random(100, 255);
var Cb = random(100, 255);
while ((Cr / Cg > 0.8 && Cr / Cg < 1.2) && (Cr / Cb > 0.8 && Cr / Cb < 1.2)) {
var Cr = random(50, 255);
var Cg = random(50, 255);
var Cb = random(50, 255);
}
this.img.loadPixels();
for (var y = 0; y < side; y++) {
for (var x = 0; x < side; x++) {
var d = (sq(center - x) + sq(center - y)) / num;
var col = color(Cr / d, Cg / d, Cb / d);
this.img.set(x, y, col);
}
}
this.img.updatePixels();
return this.img;
}
}
水波模拟
单点波动
模拟多点水波浮动
由于需要控制每个点的大小,所以粒子系统需要统筹所有屏幕上点的运动。因此,当波运动到全屏幕时,需要计算width*height个点的运动情况,这样的内存消耗很大可能会造成卡顿。目前没有想到比较好的解决方法。
通过粒子的大小来表示水波运动情况,这是基于水波运动过程可以分解为两个方向上的分量。同时,在三维空间上水的运动轨迹是一个椭圆,所以向心加速度是一个恒定值。基于这一点,我们通过给圆的变化速度设置一个加速度值,这样就可以表现水波在二维平面上的分量。
var scrWidth = 600;
var scrHeight = 600;
var W = 10;
var R = 2;
var grid;
var w_t;
var h_t;
class Particle {
constructor(x, y) {
this.pos = new p5.Vector(x, y, 0);
this.veloZ = 0;
}
tick(grid, x, y) {
var a = new p5.Vector(0, 0, 0);
if (x > 0) {
a.add(p5.Vector.sub(grid.p[(x - 1) * h_t + y].pos, this.pos));
}
if (x < w_t - 1) {
a.add(p5.Vector.sub(grid.p[(x + 1) * w_t + y].pos, this.pos));
}
if (y > 0) {
a.add(p5.Vector.sub(grid.p[x * w_t + y - 1].pos, this.pos));
}
if (y < h_t - 1) {
a.add(p5.Vector.sub(grid.p[x * w_t + y + 1].pos, this.pos));
}
this.veloZ += a.z * 0.01;
this.veloZ -= this.pos.z * 0.05;
this.veloZ -= this.veloZ * 0.01;
this.pos.z += this.veloZ;
}
draw() {
var r = lerp(1, R * 2, this.pos.z + 1);
fill('#48B9FE');
ellipse(this.pos.x, this.pos.y, r, r);
}
}
class Grid {
constructor(w, h) {
this.p = [];
for (var i = 0; i < h; i++) {
for (var j = 0; j < w; j++) {
this.p.push(new Particle(j * W, i * W));
}
}
}
tick() {
if (mouseIsPressed) {
var x = int((mouseX + W / 2) / W);
var y = int((mouseY + W / 2) / W);
this.p[x * w_t + y].pos.z -= 1;
}
for (var i = 0; i < h_t; i++) {
for (var j = 0; j < w_t; j++) {
this.p[j + i * w_t].tick(this, j, i);
}
}
}
draw() {
var dx = (scrWidth - (h_t - 1) * W) / 2;
var dy = (scrHeight - (w_t - 1) * W) / 2;
translate(dx, dy);
ellipseMode(RADIUS);
noStroke();
fill(255, 50, 0);
for (var i = 0; i < h_t; i++) {
for (var j = 0; j < w_t; j++) {
this.p[j + i * w_t].draw();
}
}
}
}
function setup() {
createCanvas(600, 600);
w_t = int(scrWidth / W);
h_t = int(scrHeight / W);
grid = new Grid(w_t, h_t);
}
function draw() {
background(40);
grid.tick();
grid.draw();
}
流体模拟
通过流体方程来模拟流体运动,由于红色的效果比较好,所以使用红色来展示。
具体的思路是先根据鼠标的位置先生成上下左右中五个点的像素值,然后根据前一次和后一次的中心位置来确定流体的流向。位于运动背向的像素值快速消失,运动正向的像素值缓慢消失。为了有一个冲力效果,控制方向直线上的点运动快,方向两侧的店运动。然后通过ADD模式下的覆盖绘制,就可以控制整体红色染料的流动。
var N = 128;
var size;
var u = [];
var v = [];
var u_prev = [];
var v_prev = [];
var dens = [];
var dens_prev = [];
var Im = [];
var source = 10;
var diff = 0.0;
var visc = 0.0;
var dt = 0.01;
var dx = 1.0;
var vc = 5.0;
function setup() {
createCanvas(500, 500);
background(0);
stroke(0, 0, 200);
blendMode(ADD);
initSim();
}
function draw() {
background(0);
dens_prev = dens.slice();
u_prev = u.slice();
v_prev = v.slice();
add_density();
add_velocity();
vel_step();
dens_step();
vort_step();
drawDensity();
}
function initSim() {
size = (N + 2) * (N + 2);
for (var i = 0; i < size; i++) {
u[i] = 0.0;
v[i] = 0.0;
dens[i] = 0.0;
Im[i] = 0.0;
}
}
function ImageX(i, j) {
return i + (N + 2) * j;
}
function PixelX(x, y) {
return (x + width * y) * 4;
}
function normalize(x, y) {
var lenxgth = sqrt(x * x + y * y) + 1e-5;
x = x / length;
y = y / length;
}
function add_density() {
if (mouseIsPressed) {
dens[ImageX(int((N / width) * mouseX), int((N / height) * mouseY))] += source;
dens[ImageX(int((N / width) * mouseX - 1), int((N / height) * mouseY))] += source / 2;
dens[ImageX(int((N / width) * mouseX + 1), int((N / height) * mouseY))] += source / 2;
dens[ImageX(int((N / width) * mouseX), int((N / height) * mouseY - 1))] += source / 2;
dens[ImageX(int((N / width) * mouseX), int((N / height) * mouseY + 1))] += source / 2;
}
}
function add_velocity() {
var i;
if (mouseIsPressed) {
i = ImageX(int((N / width) * mouseX), int((N / height) * mouseY));
var xv = (N / width) * (mouseX - pmouseX);
var yv = (N / height) * (mouseY - pmouseY);
u[i] += xv * (2 / (abs(xv) + 1)) * 15;
v[i] += yv * (2 / (abs(yv) + 1)) * 15;
}
}
function calc_vorticity(Im0, u0, v0) {
for (var i = 1 ; i <= N ; i++) {
for (var j = 1 ; j <= N ; j++) {
Im0[ImageX(i, j)] = (u0[ImageX(i, j + 1)] - u0[ImageX(i, j - 1)]) / dx - (v0[ImageX(i + 1, j)] - v0[ImageX(i - 1, j)]) / dx;
}
}
}
function confine_vorticity(Im0, u0, v0) {
var Imgrad_x, Imgrad_y;
for (var i = 1 ; i <= N ; i++) {
for (var j = 1 ; j <= N ; j++) {
Imgrad_x = abs(Im0[ImageX(i - 1, j)]) - abs(Im0[ImageX(i + 1, j)]);
Imgrad_y = abs(Im0[ImageX(i, j - 1)]) - abs(Im0[ImageX(i, j + 1)]);
normalize(Imgrad_x, Imgrad_y);
u0[ImageX(i, j)] += Imgrad_y * Im0[ImageX(i, j)] * vc * dt;
v0[ImageX(i, j)] += -Imgrad_x * Im0[ImageX(i, j)] * vc * dt;
}
}
}
function diffuse(b, x, x0, diff0) {
var a = dt * diff0 * N * N;
for (var k = 0 ; k < 20 ; k++) {
for (var i = 1 ; i <= N ; i++) {
for (var j = 1 ; j <= N ; j++) {
x[ImageX(i, j)] = (x0[ImageX(i, j)] + a * (x[ImageX(i - 1, j)] + x[ImageX(i + 1, j)] + x[ImageX(i, j - 1)] + x[ImageX(i, j + 1)])) / (1 + 4 * a);
}
}
set_bnd(b, x);
}
}
function advect(b, d, d0, u0, v0) {
var i0, j0, i1, j1;
var x, y, s0, t0, s1, t1, dt0;
dt0 = dt * N;
for (var i = 1 ; i <= N ; i++) {
for (var j = 1 ; j <= N ; j++) {
x = i - dt0 * u0[ImageX(i, j)];
y = j - dt0 * v0[ImageX(i, j)];
if (x < 0.5)
x = 0.5;
if (x > N + 0.5)
x = N + 0.5;
i0 = int(x);
i1 = i0 + 1;
if (y < 0.5)
y = 0.5;
if (y > N + 0.5)
y = N + 0.5;
j0 = int(y);
j1 = j0 + 1;
s1 = x - i0;
s0 = 1 - s1;
t1 = y - j0;
t0 = 1 - t1;
d[ImageX(i, j)] = s0 * (t0 * d0[ImageX(i0, j0)] + t1 * d0[ImageX(i0, j1)]) + s1 * (t0 * d0[ImageX(i1, j0)] + t1 * d0[ImageX(i1, j1)]);
}
}
set_bnd(b, d);
}
function dens_step() {
diffuse(0, dens_prev, dens, diff);
advect(0, dens, dens_prev, u, v);
}
function vel_step() {
diffuse(1, u_prev, u, visc);
diffuse(2, v_prev, v, visc);
project(u_prev, v_prev, u, v);
advect(1, u, u_prev, u_prev, v_prev);
advect(2, v, v_prev, u_prev, v_prev);
project(u, v, u_prev, v_prev);
}
function vort_step() {
calc_vorticity(Im, u, v);
confine_vorticity(Im, u, v);
}
function project(u0, v0, p, div) {
var h = 1.0 / N;
for (var i = 1 ; i <= N ; i++) {
for (var j = 1 ; j <= N ; j++) {
div[ImageX(i, j)] = 0.5 * h * (u0[ImageX(i + 1, j)] - u0[ImageX(i - 1, j)] + v0[ImageX(i, j + 1)] - v0[ImageX(i, j - 1)]);
p[ImageX(i, j)] = 0;
}
}
set_bnd(0, div);
set_bnd(0, p);
var residual;
for (var k = 0 ; k < 100 ; k++) {
for (i = 1 ; i <= N ; i++) {
for (j = 1 ; j <= N ; j++) {
residual = -4 * p[ImageX(i, j)] + p[ImageX(i - 1, j)] + p[ImageX(i + 1, j)] + p[ImageX(i, j - 1)] + p[ImageX(i, j + 1)] - div[ImageX(i, j)];
p[ImageX(i, j)] += residual * (1.7 / 4);
}
}
set_bnd(0, p);
}
for (i = 1 ; i <= N ; i++) {
for (j = 1 ; j <= N ; j++) {
u0[ImageX(i, j)] -= 0.5 * (p[ImageX(i + 1, j)] - p[ImageX(i - 1, j)]) / h;
v0[ImageX(i, j)] -= 0.5 * (p[ImageX(i, j + 1)] - p[ImageX(i, j - 1)]) / h;
}
}
set_bnd(1, u0);
set_bnd(2, v0);
}
function set_bnd(b, x) {
for (var i = 1 ; i <= N ; i++) {
x[ImageX(0, i)] = b == 1 ? -x[ImageX(1, i)] : x[ImageX(1, i)];
x[ImageX(N + 1, i)] = b == 1 ? -x[ImageX(N, i)] : x[ImageX(N, i)];
x[ImageX(i, 0)] = b == 2 ? -x[ImageX(i, 1)] : x[ImageX(i, 1)];
x[ImageX(i, N + 1)] = b == 2 ? -x[ImageX(i, N)] : x[ImageX(i, N)];
}
x[ImageX(0, 0)] = 0.5 * (x[ImageX(1, 0)] + x[ImageX(0, 1)]);
x[ImageX(0, N + 1)] = 0.5 * (x[ImageX(1, N + 1)] + x[ImageX(0, N)]);
x[ImageX(N + 1, 0)] = 0.5 * (x[ImageX(N, 0)] + x[ImageX(N + 1, 1)]);
x[ImageX(N + 1, N + 1)] = 0.5 * (x[ImageX(N, N + 1)] + x[ImageX(N + 1, N)]);
}
function drawDensity() {
var dx, dy, ddx, ddy;
var df, di;
loadPixels();
for (var x = 0; x < width; x++) {
for (var y = 0; y < height; y++) {
dx = (N / width) * x;
ddx = dx - int(dx);
dy = (N / height) * y;
ddy = dy - int(dy);
df = (dens[ImageX(floor(dx), floor(dy))] * (1.0 - ddx) + dens[ImageX(ceil(dx), floor(dy))] * ddx) * (1.0 - ddy) + (dens[ImageX(floor(dx), ceil(dy))] * (1.0 - ddx) + dens[ImageX(ceil(dx), ceil(dy))] * ddx) * ddy;
di = int(df * 255);
if (di < 0)
di = 0;
if (di > 255)
di = 255;
pixels[PixelX(x, y)] = pixels[PixelX(x, y)] * (1 - df) + di * df;
pixels[PixelX(x, y) + 1] = 0;
pixels[PixelX(x, y) + 2] = 0;
pixels[PixelX(x, y) + 3] = 255;
}
}
updatePixels();
}