简介
参考《代码本色》的第0~4章内容及其实例程序创作一组编程习作,体现随机行为及牛顿运动学。
第0章 随机游走
目标:
实现鼠标指引“画笔”,但“画笔”在向鼠标靠近的同时具有随机运动的特性。同时统计所画路径点横坐标的分布。
探索过程:
1.概率和非均匀分布
有50%概率向鼠标所在方向移动的Walker对象 :
class Walker {
int x, y;
float xoff = 0.0f;
Walker() {
x = width/2;
y = height/2;
}
void render() {
stroke(255);
strokeWeight(2);
point(x, y);
}
// Randomly move up, down, left, right, or stay in one place
void step() {
float r = random(1);
// A 50% of moving towards the mouse
if (r < 0.5) {
int xdir = (mouseX-x);
int ydir = (mouseY-y);
if (xdir != 0) {
xdir /= abs(xdir);
}
if (ydir != 0) {
ydir /= abs(ydir);
}
x += xdir;
y += ydir;
} else {
int xdir = int(random(-2, 2));
int ydir = int(random(-2, 2));
println(xdir);
x += xdir;
y += ydir;
}
/*int xoffd=int(random(-2,2));
float n=noise(xoff);
int c=(int)(xoffd*4*n);
x += c;
y += c;
xoff += 0.01;
println(c);*/
x = constrain(x, 0, width-1);
y = constrain(y, 0, height-1);
}
}
2.Perlin噪声(一种更平滑的算法)
噪声就是给定一个输入变量,生成一个值在0-1范围内的伪随机变量的函数。在图形学中一般是输入一个坐标得到一个范围在0-1之间的变量,再利用各种颜色计算得到一些比较酷炫的效果,像火焰、云彩、地形等。
这里只是简单应用Perlin噪声使Walker对象移动随机性增加。
在有50%概率向鼠标所在方向移动的基础上对Walker对象增加Perlin噪声(即上述代码中注释部分):
int xoffd=int(random(-2,2));
float n=noise(xoff);
int c=(int)(xoffd*4*n);
x += c;
y += c;
xoff += 0.01;
println(c);
3.主程序(包括对所画点横坐标的计数统计):
Walker w;
float[] vals;
float[] norms;
void setup() {
size(640,360);
// Create a walker object
w = new Walker();
vals = new float[width];
norms = new float[width];
background(0);
}
void draw() {
int index = int(w.x);
vals[index]++;
stroke(150);
boolean normalization = false;
float maxy = 0.0;
for (int x = 0; x < vals.length; x++) {
line(x,height,x,height-norms[x]);
if (vals[x] > height) normalization = true;
if(vals[x] > maxy) maxy = vals[x];
}
for (int x = 0; x < vals.length; x++) {
if (normalization) norms[x] = (vals[x] / maxy) * (height);
else norms[x] = vals[x];
}
// Run the walker object
w.step();
w.render();
}
效果:
第1章 向量
目标:
实现一组同时朝着鼠标加速的运动物体,物体与鼠标之间存在牵引线,同时可统计物体位置向量的长度。
探索过程:
1.朝鼠标加速运动的简单实现原理:
PVector mouse = new PVector(mouseX,mouseY);
PVector dir = PVector.sub(mouse,location);//第一步:计算方向
dir.normalize();//第二步:单位化
dir.mult(0.5);//第三步:改变长度
acceleration = dir;//第四步:得到加速度
velocity.add(acceleration);
velocity.limit(topspeed);
location.add(velocity);
2.实现一组同时朝着鼠标加速的运动物体的Walker对象:
class Mover {
// The Mover tracks position, velocity, and acceleration
PVector position;
PVector velocity;
PVector acceleration;
// The Mover's maximum speed
float topspeed;
Mover() {
// Start in the center
position = new PVector(random(width),random(height));
velocity = new PVector(0,0);
topspeed = 5;
}
void update() {
// Compute a vector that points from position to mouse
PVector mouse = new PVector(mouseX,mouseY);
acceleration = PVector.sub(mouse,position);
// Set magnitude of acceleration
//acceleration.setMag(0.2);
acceleration.normalize();
acceleration.mult(0.2);
// Velocity changes according to acceleration
velocity.add(acceleration);
// Limit the velocity by topspeed
velocity.limit(topspeed);
// position changes by velocity
position.add(velocity);
}
void display() {
stroke(0);
strokeWeight(2);
fill(127,200);
ellipse(position.x,position.y,48,48);
}
}
3.主程序(包括牵引线的绘制以及向量长度的统计):
Mover[] movers = new Mover[20];
void setup() {
size(640,360);
for (int i = 0; i < movers.length; i++) {
movers[i] = new Mover();
}
}
void draw() {
background(255);
for (int i = 0; i < movers.length; i++) {
movers[i].update();
movers[i].display();
fill(200);
noStroke();
rect(0,i*(360/movers.length),movers[i].position.mag()/2,10);
stroke(150);
strokeWeight(1);
line(mouseX,mouseY,movers[i].position.x,movers[i].position.y);
}
}
其中,向量长度的计算原理为勾股定理:
float mag() {
return sqrt(x*x + y*y);
}
效果:
第2章 力
目标:
实现随机生成一组小球,自由落体后进入水面,模拟物体掉入水中的效果。物体穿过窗口底部的灰色区域(代表流体)时,会减速。并且物体越小,速度减小地越快。因为牛顿第二运动定律表明加速度等于力除以质量,在同一个力的作用下,物体的质量越大,加速度就越小,所以该示例中物体的质量越小,减速越快。
通过线段长度表示每个物体所受力以及速度的大小。
鼠标每点击一次,可以对所有物体增加一个瞬时的向上的力。
按下键盘“R"键可以重新开始演示。
探索过程:
1.随机小球:
位置由速度控制,而速度由加速度控制。加速度是一切运动的起因。而根据牛顿第二定律,力变成了运动的起因。
通过applyForce()函数实现对物体施加力。
class Mover {
// position, velocity, and acceleration
PVector position;
PVector velocity;
PVector acceleration;
// Mass is tied to size
float mass;
Mover(float m, float x, float y) {
mass = m;
position = new PVector(x, y);
velocity = new PVector(0, 0);
acceleration = new PVector(0, 0);
}
void applyForce(PVector force) {
// Divide by mass
PVector f = PVector.div(force, mass);
// Accumulate all forces in acceleration
acceleration.add(f);
}
void update() {
// Velocity changes according to acceleration
velocity.add(acceleration);
// position changes by velocity
position.add(velocity);
// We must clear acceleration each frame
acceleration.mult(0);
}
// Draw Mover
void display() {
stroke(0);
strokeWeight(2);
fill(127, 200);
ellipse(position.x, position.y, mass*16, mass*16);
}
// Bounce off bottom of window
void checkEdges() {
if (position.y > height) {
velocity.y *= -0.9; // A little dampening when hitting the bottom
position.y = height;
}
}
}
2.液体生成:
根据阻力公式,阻力 = 阻力系数×速度×速度,由此计算小球在液体中所受的阻力。
class Liquid {
// Liquid is a rectangle
float x, y, w, h;
// Coefficient of drag
float c;
Liquid(float x_, float y_, float w_, float h_, float c_) {
x = x_;
y = y_;
w = w_;
h = h_;
c = c_;
}
// Is the Mover in the Liquid?
boolean contains(Mover m) {
PVector l = m.position;
return l.x > x && l.x < x + w && l.y > y && l.y < y + h;
}
// Calculate drag force
PVector drag(Mover m) {
// Magnitude is coefficient * speed squared
float speed = m.velocity.mag();
float dragMagnitude = c * speed * speed;
// Direction is inverse of velocity
PVector dragForce = m.velocity.get();
dragForce.mult(-1);
// Scale according to magnitude
// dragForce.setMag(dragMagnitude);
dragForce.normalize();
dragForce.mult(dragMagnitude);
return dragForce;
}
void display() {
noStroke();
fill(50);
rect(x, y, w, h);
}
}
3.主程序(包括受力、速度显示以及交互的实现):
Mover[] movers = new Mover[9];
// Liquid
Liquid liquid;
void setup() {
size(640, 360);
reset();
// Create liquid object
liquid = new Liquid(0, height/2, width, height/2, 0.1);
}
void draw() {
background(255);
// Draw water
liquid.display();
PVector dragForce = new PVector(0, 0);
for (int i = 0; i < movers.length; i++) {
// Is the Mover in the liquid?
if (liquid.contains(movers[i])) {
// Calculate drag force
dragForce = liquid.drag(movers[i]);
// Apply drag force to Mover
movers[i].applyForce(dragForce);
}
// Gravity is scaled by mass here!
PVector gravity = new PVector(0, 0.1*movers[i].mass);
// Apply gravity
movers[i].applyForce(gravity);
// Update and display
movers[i].update();
movers[i].display();
movers[i].checkEdges();
line(70*(i+1),height/2,70*(i+1)+movers[i].velocity.x*100,height/2+movers[i].velocity.y*30);
stroke(155);
line(70*(i+0.5),height/2,70*(i+0.5),height/2+(gravity.y+dragForce.y)*100);
}
fill(0);
text("press 'R' to reset", 10, 30);
text("click mouse to apply an upward force", 10, 50);
if (keyPressed) {
if (key == 'r') {
reset();
}
}
}
void mousePressed() {
PVector press = new PVector(0,-6);
for (int i = 0; i < movers.length; i++) {
movers[i].applyForce(press);
}
}
// Restart all the Mover objects randomly
void reset() {
for (int i = 0; i < movers.length; i++) {
movers[i] = new Mover(random(0.5, 3), 40+i*70, 0);
}
}
效果:
第3章 振荡
目标:
实现一个可以通过鼠标交互的钟摆模型,并可视化钟摆摆动的角速度。
探索过程:
1.钟摆模型:
a.运动规律:
角度 = 角度 + 角速度
角速度 = 角速度 + 角加速度
牛顿第二运动定律告诉我们力和加速度的关系,也就是F = M* A,或者A = F / M。因此,如果钟摆上物体受到的力等于重力乘以θ的正弦,就可以得到: 钟摆的角加速度 = 重力加速度 * sin(θ)。
钟摆在最高点角速度最小为0,在最低点角速度最大。
b.模型构建:
class Pendulum {
float r;//摆臂长度
float angle;//摆臂角度
float aVelocity;//角速度
float aAcceleration;//角加速度
}
根据上面的公式更新钟摆的角度:
void update() {
float gravity = 0.4;//任意常数
aAcceleration = (-1 * gravity / r) * sin(angle);//根据公式计算加速度
aVelocity += aAcceleration;//增加速度
angle += aVelocity;//增加角度
}
display()函数用于绘制钟摆。
钟摆模型类(包括鼠标控制交互):
class Pendulum {
PVector position; // position of pendulum ball
PVector origin; // position of arm origin
float r; // Length of arm
float angle; // Pendulum arm angle
float aVelocity; // Angle velocity
float aAcceleration; // Angle acceleration
float ballr; // Ball radius
float damping; // Arbitary damping amount
boolean dragging = false;
// This constructor could be improved to allow a greater variety of pendulums
Pendulum(PVector origin_, float r_) {
// Fill all variables
origin = origin_.get();
position = new PVector();
r = r_;
angle = PI/4;
aVelocity = 0.0;
aAcceleration = 0.0;
damping = 0.995; // Arbitrary damping
ballr = 48.0; // Arbitrary ball radius
}
void go() {
update();
drag(); //for user interaction
display();
}
// Function to update position
void update() {
// As long as we aren't dragging the pendulum, let it swing!
if (!dragging) {
float gravity = 0.4; // Arbitrary constant
aAcceleration = (-1 * gravity / r) * sin(angle); // Calculate acceleration (see: http://www.myphysicslab.com/pendulum1.html)
aVelocity += aAcceleration; // Increment velocity
aVelocity *= damping; // Arbitrary damping
angle += aVelocity; // Increment angle
}
}
void display() {
position.set(r*sin(angle), r*cos(angle), 0); // Polar to cartesian conversion
position.add(origin); // Make sure the position is relative to the pendulum's origin
stroke(0);
strokeWeight(2);
// Draw the arm
line(origin.x, origin.y, position.x, position.y);
ellipseMode(CENTER);
fill(175);
if (dragging) fill(0);
// Draw the ball
ellipse(position.x, position.y, ballr, ballr);
}
// The methods below are for mouse interaction
// This checks to see if we clicked on the pendulum ball
void clicked(int mx, int my) {
float d = dist(mx, my, position.x, position.y);
if (d < ballr) {
dragging = true;
}
}
// This tells us we are not longer clicking on the ball
void stopDragging() {
aVelocity = 0; // No velocity once you let go
dragging = false;
}
void drag() {
// If we are draging the ball, we calculate the angle between the
// pendulum origin and mouse position
// we assign that angle to the pendulum
if (dragging) {
PVector diff = PVector.sub(origin, new PVector(mouseX, mouseY)); // Difference between 2 points
angle = atan2(-1*diff.y, diff.x) - radians(90); // Angle relative to vertical axis
}
}
}
2.主程序:
用画面中间小球左右的位置表示钟摆运动的角速度。
Pendulum p;
void setup() {
size(640,360);
// Make a new Pendulum with an origin position and armlength
p = new Pendulum(new PVector(width/2,0),175);
}
void draw() {
//background(255);
noStroke();
fill(255,5);
rect(0,0,width,height);
p.go();
fill(0);
text("c l i c k o n b o b t o d r a g",10,height-5);
float x = 3000*p.aVelocity;
fill(127);
translate(width/2,height/2);
line(0,70,x,70);
ellipse(x,70,16,16);
}
void mousePressed() {
p.clicked(mouseX,mouseY);
}
void mouseReleased() {
p.stopDragging();
}
效果:
第4章 粒子系统
目标:
让粒子系统的原点能够动态移动,让粒子从鼠标所在的位置发射出来。鼠标点击时可以固定一个粒子系统的原点。为粒子系统加入排斥对象。
1.粒子系统:
粒子就是在屏幕中移动的对象,它有位置、速度和加速度变量,有构造函数用于内部变量的初始化,有display()函数用于绘制自身,还有update()函数用于更新位置。
典型的粒子系统中都有一个发射器,发射器是粒子的源头,它控制粒子的初始属性,包括位置、速度等。发射器发射的粒子可能是一股粒子,也可能是连续的粒子流,或是同时包含这两种发射方式。有一点非常关键:在一个典型的粒子系统中,粒子在发 射器中诞生,但并不会永远存在。假设粒子永不消亡,系统中的粒子将越积越多,Sketch的运行速度也会越来越慢,最后程序会挂起。新的粒子不断产生,与此同时,旧的粒子应该不断消亡,只有这样,程序的性能才不会受到影响。决定粒子何时消亡的方法很多,比如,粒子可以和另一个粒子结合在一起,或在离开屏幕时消亡。
用数组实现复杂的粒子系统。
class Particle {
PVector position;
PVector velocity;
PVector acceleration;
float lifespan;
float mass = 1;
Particle(PVector l) {
acceleration = new PVector(0,0.05);
velocity = new PVector(random(-1,1),random(-2,0));
position = l.get();
lifespan = 255.0;
}
void run() {
update();
display();
}
void applyForce(PVector force){
PVector f=force.get();
f.div(mass);
acceleration.add(f);
}
// Method to update position
void update() {
velocity.add(acceleration);
position.add(velocity);
acceleration.mult(0);
lifespan -= 2.0;
}
// Method to display
void display() {
stroke(0,lifespan);
strokeWeight(2);
fill(127,lifespan);
ellipse(position.x,position.y,12,12);
}
// Is the particle still useful?
boolean isDead() {
if (lifespan < 0.0) {
return true;
} else {
return false;
}
}
}
class ParticleSystem {
ArrayList<Particle> particles;
PVector origin;
ParticleSystem(PVector position) {
origin = position.get();
particles = new ArrayList<Particle>();
}
ParticleSystem(int num, PVector v) {
particles = new ArrayList<Particle>(); // Initialize the arraylist
origin = v.get(); // Store the origin point
for (int i = 0; i < num; i++) {
particles.add(new Particle(origin)); // Add "num" amount of particles to the arraylist
}
}
void addParticle() {
particles.add(new Particle(origin));
}
void addParticle(float x, float y) {
particles.add(new Particle(new PVector(x, y)));
}
void applyForce(PVector f) {
for (Particle p: particles) {
p.applyForce(f);
}
}
/* void applyRepeller(Repeller r) {
for (Particle p: particles) {
PVector force = r.repel(p);
p.applyForce(force);
}
}*/
void run() {
for (int i = particles.size()-1; i >= 0; i--) {
Particle p = particles.get(i);
p.run();
if (p.isDead()) {
particles.remove(i);
}
}
}
boolean dead() {
if (particles.isEmpty()) {
return true;
}
else {
return false;
}
}
}
2.排斥对象:
排斥对象对其他对象有斥力作用,以防止对方靠近。
在粒子系统中加入一个新函数,该函数以Repeller对象为参数,它的功能是将排 斥力作用在每个粒子上。
void applyRepeller(Repeller r) {
for (Particle p: particles) {
PVector force = r.repel(p);
p.applyForce(force);
}
}
class Repeller {
// Gravitational Constant
float G = 100;
// position
PVector position;
float r = 10;
Repeller(float x, float y) {
position = new PVector(x,y);
}
void display() {
stroke(0);
strokeWeight(2);
fill(175);
ellipse(position.x,position.y,48,48);
}
// Calculate a force to push particle away from repeller
PVector repel(Particle p) {
PVector dir = PVector.sub(position,p.position); // Calculate direction of force
float d = dir.mag(); // Distance between objects
dir.normalize(); // Normalize vector (distance doesn't matter here, we just want this vector for direction)
d = constrain(d,5,100); // Keep distance within a reasonable range
float force = -1 * G / (d * d); // Repelling force is inversely proportional to distance
dir.mult(force); // Get force vector --> magnitude * direction
return dir;
}
}
3.主程序:
ParticleSystem ps;
Repeller repeller;
ArrayList<ParticleSystem> systems;
void setup() {
size(640,360);
ps = new ParticleSystem(new PVector(width/2,50));
repeller=new Repeller(width/2-10,height/2);
systems=new ArrayList<ParticleSystem>();
}
void draw() {
background(255);
PVector gravity = new PVector(0,0.1);
for (ParticleSystem pss: systems) {
pss.applyForce(gravity);
pss.applyRepeller(repeller);
repeller.display();
pss.run();
pss.addParticle();
}
// Option #1 (move the Particle System origin)
ps.origin.set(mouseX,mouseY,0);
ps.addParticle();
// Apply gravity force to all Particles
ps.applyForce(gravity);
ps.applyRepeller(repeller);
repeller.display();
ps.run();
fill(0);
text("click mouse to add particle systems",10,height-30);
}
void mousePressed() {
systems.add(new ParticleSystem(1,new PVector(mouseX,mouseY)));
}
效果: