互动媒体:创意编程
第零章 引言
在代码中使用random()函数计算概率的方法有很多。一种常见的方法是:在数组中存放 一堆选好的数字,然后从这个数组中选择随机数,根据这些选择判定事件是否发生。
但这里使用的是random(1),会在0-1之前随机产生一个数,用小数来表示概率,比较符合直觉。
生活中总有很多例子无法用均匀分布的随机数模拟,高斯分布有时也无能为力。所以也经常会用自定义分布随机数。
这里我同时使用了这两个随机性,让小球有一定概率向鼠标移动,但距离却是使用自定义的随机数来决定,达到一个奇妙的效果。
Walker w;
void setup() {
size(640,360);
w = new Walker();
background(255);
}
void draw() {
w.step();
w.render();
}
class Walker {
int x, y;
Walker() {
x = width/2;
y = height/2;
}
void render() {
stroke( 142, 229, 238);
circle(x, y,25);
strokeWeight(2);
}
void step() {
float r = random(1);
if (r < 0.5) {
float j=random(1);
int xdir;
int ydir;
if(j<0.5){
xdir = 5*(mouseX-x);
ydir = 5*(mouseY-y);
}else
{xdir = (mouseX-x);
ydir = (mouseY-y);}
if (xdir != 0) {
xdir /= abs(xdir);
}
if (ydir != 0) {
ydir /= abs(ydir);
}
x += xdir;
y += ydir;
} else {
float v=random(1);
int xdir;
int ydir;
if(v<0.5){
xdir = int(random(-20, 20));
ydir = int(random(-20, 20));}
else{
xdir = int(random(-2, 2));
ydir = int(random(-2, 2));}
println(xdir);
x += xdir;
y += ydir;
}
x = constrain(x, 0, width-1);
y = constrain(y, 0, height-1);
}
}
第一章 向量
自然的很多类似概念,如加速度,速度,位置等都可以用向量来表示。除了加法,还有很多常用的向量运算。比如向量相减sub,相乘mult等。使用这些运算律做了一堆小球以一个指向鼠标的加速度运动的程序。并给了他一个随机的颜色,参考了后面的内容在边缘处做了处理。让碰到边缘的球从另一侧出来。
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();
}
}
class Mover {
// The Mover tracks position, velocity, and acceleration
PVector position;
PVector velocity;
PVector acceleration;
float topspeed;
Mover() {
position = new PVector(random(width),random(height));
velocity = new PVector(0,0);
topspeed = 5;
}
void update() {
PVector mouse = new PVector(mouseX,mouseY);
acceleration = PVector.sub(mouse,position);
acceleration.normalize();
acceleration.mult(0.2);
velocity.add(acceleration);
velocity.limit(topspeed);
position.add(velocity);
if (position.x>width){
position.x=0;
}
else if (position.x<0){
position.x=width;
}
if (position.y>height){
position.y=0;
}
else if (position.y<0){
position.y=height;
}
}
void display() {
float R;
float G;
float B;
R=random(258);
G=random(258);
B=random(258);
stroke(0);
strokeWeight(2);
fill(R,G,B);
ellipse(position.x,position.y,48,48);
}
}
第二章 力
物体的加速度等于力。所以我们可以用加速度去表示力。我们可以在对象上施加力,比如重力和风力。但如果有多个力同时作用的话,是需要进行处理的。
合力等于质量乘以加速度。或者可以说成:加速度等于所有力的和除以质量。这样就同时也满足牛顿第 一运动定律:如果所受外力的合力为零,物体就处于平衡状态(没有加速度)。我们会用力的累加方法实现牛顿第二定律,这种方法非常简单,只需将所有力相加。在任 意时刻,物体都可能受多种外力作用,它只需要知道如何将这些外力累加在一起,而不需要知道到底有多少种外力。
流体的阻力对物体的作用也是很明显的。在程序中有一部分灰色的区域,是模仿了流体的阻力。给一堆小球施加了重力,让他们掉入水中。在掉入水中之前按键盘的上下左右键的话,可以在空间中添加相应方向的风力。鼠标左键可以重置球的位置。 并在边界添加了事件监测,如果碰到了边界则弹回。
Mover[] movers = new Mover[9];
PVector wind = new PVector(0,0);
Liquid liquid;
void setup() {
size(640, 360);
reset();
liquid = new Liquid(0, height/2, width, height/2, 0.1);
}
void draw() {
background(255);
liquid.display();
for (int i = 0; i < movers.length; i++) {
if (liquid.contains(movers[i])) {
PVector dragForce = liquid.drag(movers[i]);
movers[i].applyForce(dragForce);
}
PVector gravity = new PVector(0, 0.1*movers[i].mass);
movers[i].applyForce(gravity);
movers[i].applyForce(wind);
movers[i].update();
movers[i].display();
movers[i].checkEdges( );
}
fill(0);
text("click mouse to reset", 10, 30);
}
void mousePressed() {
reset();
}
void keyPressed(){
PVector power1=new PVector(0,-0.02);
PVector power2=new PVector(0,0.02);
PVector power3=new PVector(-0.02,0);
PVector power4=new PVector(0.02,0);
if(keyCode==UP)
{
wind.add(power1);
}
if(keyCode==DOWN)
{
wind.add(power2);
}
if(keyCode==LEFT)
{
wind.add(power3);
}
if(keyCode==RIGHT)
{
wind.add(power4);
}
}
void reset() {
for (int i = 0; i < movers.length; i++) {
movers[i] = new Mover(random(0.5, 3), 40+i*70, 0);
}
}
class Liquid {
float x, y, w, h;
float c;
Liquid(float x_, float y_, float w_, float h_, float c_) {
x = x_;
y = y_;
w = w_;
h = h_;
c = c_;
}
boolean contains(Mover m) {
PVector l = m.position;
return l.x > x && l.x < x + w && l.y > y && l.y < y + h;
}
PVector drag(Mover m) {
float speed = m.velocity.mag();
float dragMagnitude = c * speed * speed;
PVector dragForce = m.velocity.get();
dragForce.mult(-1);
dragForce.normalize();
dragForce.mult(dragMagnitude);
return dragForce;
}
void display() {
noStroke();
fill(50);
rect(x, y, w, h);
}
}
class Mover {
PVector position;
PVector velocity;
PVector acceleration;
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) {
PVector f = PVector.div(force, mass);
acceleration.add(f);
}
void update() {
velocity.add(acceleration);
position.add(velocity);
acceleration.mult(0);
}
void display() {
stroke(0);
strokeWeight(2);
fill(127, 200);
ellipse(position.x, position.y, mass*16, mass*16);
}
void checkEdges() {
if(position.x > width)
{
position.x = width;
velocity.x *= -1;
}
else if (position.x < 0)
{
velocity.x *= -1;
position.x = 0;
}
if (position.y > height) {
velocity.y *= -1;
position.y = height;
}
else if(position.y<0)
{
velocity.y *= -1;
position.y = 0;
}
}
}
第三章 振荡
弹簧的弹力可以根据胡克定律计算得到,弹簧的弹力与弹簧的伸长量成正比。也就是说,弹簧被拉伸的越长,它的弹力也越大;被拉伸的越短,弹力越小。从数学上,这么表示该定律:
要想实现多个球的用弹簧连接的系统,则需要一个弹簧类。弹簧类用于管理弹簧的枢轴点位置、静止长度和计算作用在钟摆上的弹力。利用这个思想和之前的钟摆部分代码,我实现了一个有固定点的弹簧系统。
Bob[] bobs = new Bob[5];
Spring spring;
Spring[] springs = new Spring[4];
PVector gravity = new PVector(0,2);
void setup() {
size(640, 360);
bobs[0]=new Bob(320,100);
spring=new Spring(320,10,100);
for (int i = 1; i < bobs.length; i++) {
bobs[i] = new Bob(width/2, 100+i*40);
}
for (int i = 0; i < springs.length; i++) {
springs[i] = new Spring(bobs[i], bobs[i+1],40);
}
}
void draw() {
background(255);
spring.connect(bobs[0]);
spring.displayLine(bobs[0]);
for (Spring s : springs) {
s.update();
s.display();
}
for (Bob b : bobs) {
b.update();
b.display();
b.drag(mouseX, mouseY);
b.applyForce(gravity);
b.checkEdges();
}
}
void mousePressed() {
for (Bob b : bobs) {
b.clicked(mouseX, mouseY);
}
}
void mouseReleased() {
for (Bob b : bobs) {
b.stopDragging();
}
}
class Bob {
PVector position;
PVector velocity;
PVector acceleration;
float mass = 8;
float damping = 0.95;
PVector dragOffset;
boolean dragging = false;
// Constructor
Bob(float x, float y) {
position = new PVector(x,y);
velocity = new PVector();
acceleration = new PVector();
dragOffset = new PVector();
}
void update() {
velocity.add(acceleration);
velocity.mult(damping);
position.add(velocity);
acceleration.mult(0);
}
void applyForce(PVector force) {
PVector f = force.get();
f.div(mass);
acceleration.add(f);
}
void display() {
stroke(0);
strokeWeight(2);
fill(175,120);
if (dragging) {
fill(50);
}
ellipse(position.x,position.y,mass*2,mass*2);
}
void clicked(int mx, int my) {
float d = dist(mx,my,position.x,position.y);
if (d < mass) {
dragging = true;
dragOffset.x = position.x-mx;
dragOffset.y = position.y-my;
}
}
void stopDragging() {
dragging = false;
}
void drag(int mx, int my) {
if (dragging) {
position.x = mx + dragOffset.x;
position.y = my + dragOffset.y;
}
}
void checkEdges() {
if(position.x > width)
{
position.x = width;
velocity.x *= -1;
}
else if (position.x < 0)
{
velocity.x *= -1;
position.x = 0;
}
if (position.y > height) {
velocity.y *= -1;
position.y = height;
}
else if(position.y<0)
{
velocity.y *= -1;
position.y = 0;
}
}
}
class Spring {
// position
PVector anchor=new PVector(320,10);
// Rest length and spring constant
float len;
float k = 0.2;
Bob a;
Bob b;
Spring(float x,float y, int l){
anchor=new PVector(x,y);
l=1;}
Spring(Bob a_, Bob b_, int l) {
a = a_;
b = b_;
len = l;
}
void connect(Bob b) {
PVector force =PVector.sub(b.position,anchor);
float d = force.mag();
float stretch = d - len;
force.normalize();
force.mult(-1*k*stretch);
b.applyForce(force);
}
void update() {
PVector force = PVector.sub(a.position, b.position);
float d = force.mag();
float stretch = d - len;
force.normalize();
force.mult(-1 * k * stretch);
a.applyForce(force);
force.mult(-1);
b.applyForce(force);
}
void display() {
strokeWeight(2);
stroke(0);
line(a.position.x, a.position.y, b.position.x, b.position.y);
rect(anchor.x,anchor.y,10,10);
}
void displayLine(Bob b) {
strokeWeight(2);
stroke(0);
line(b.position.x, b.position.y, anchor.x, anchor.y);
}
}
第四章 粒子系统
粒子系统是由许多粒子组成的用于代表模糊对象的集合。在一段特定时间内,粒子在系统中生成、移动、转化,最后消亡。在一个典型的粒子系统中,粒子在发 射器中诞生,但并不会永远存在。假设粒子永不消亡,系统中的粒子将越积越多,运行速度也会越来越慢,最后程序会挂起。新的粒子不断产生,与此同时, 旧的粒子应该不断消亡,只有这样,程序的性能才不会受到影响。决定粒子何时消亡 的方法很多,比如,粒子可以和另一个粒子结合在一起,或在离开屏幕时消亡。这是 本章的第一个粒子类,我希望它尽可能简单,因此用一个lifespan变 量代表粒子的生存期,这个变量从255开始,逐步递减,递减到0时粒子消亡。
粒子组成的系统可以再组成一个系统的系统,这时我就需要创建一个ArrayList用于存放多个粒子系统对象,并在setup()函 数中将这个ArrayList初始为空。
基于以上的粒子系统知识,给粒子增加了一个重力和鼠标移动方向的加速度。在产生一个较大的粒子同时在周围也产生一些较小的粒子。下面是一个长按鼠标就会一直产生粒子系统的程序。
ParticleSystem ps;
void setup() {
size(600, 600);
colorMode(HSB, 25);
ps = new ParticleSystem();
}
void draw() {
background(255, 0, 255);
ps.run();
if (mousePressed && mouseButton == LEFT) {
ps.addnew(mouseX, mouseY);
}
}
class lizi extends Particle {
ArrayList<PVector> plist;
lizi(float x, float y) {
super(x, y);
PVector v = new PVector(mouseX - pmouseX, mouseY - pmouseY);
v.mult(0.1);
velocity.mult(getSpeed(100)).add(v);
plist = new ArrayList();
for (int i = 0; i < 3; i++) {
float xOffset = random(-10, 10);
float yOffset = random(-10, 10);
float hue = random(50);
plist.add(new PVector(xOffset, yOffset, hue));
}
}
void update() {
super.update();
}
void drawShape() {
for (PVector p : plist) {
stroke(p.z, 25, 125, 20);
strokeWeight(50);
point(p.x, p.y);
stroke(p.z, 255, 25, 10000);
strokeWeight(30);
point(p.x, p.y);
}
}
}
class Particle {
PVector location, velocity, acceleration, origin;
float angle, aVelocity, aAcceleration;
float lifespan, lifeRate, maxLifespan;
int type, hue;
Particle(float x, float y) {
origin = new PVector(x, y);
location = new PVector();
acceleration = new PVector(0, 0.05);
velocity = PVector.random2D();
lifespan = maxLifespan = 50;
lifeRate = random(0.35, 1);
hue = 20;
type = 1;
}
float getSpeed(float s){
float t = maxLifespan / lifeRate;
return s / t;
}
void run() {
update();
display();
}
void update() {
velocity.add(acceleration);
location.add(velocity);
aVelocity += aAcceleration;
angle += aVelocity;
lifespan -= lifeRate;
}
boolean isDead() {
if (lifespan < 0.0) {
return true;
} else {
return false;
}
}
void display() {
pushMatrix();
translate(origin.x, origin.y);
rotate(radians(angle));
translate(location.x, location.y);
scale(map(lifespan, 0, maxLifespan, 0, 1));
drawShape();
popMatrix();
}
void drawShape() {
stroke(hue, 255, 255);
strokeWeight(20);
point(0, 0);
}
}
class ParticleSystem {
ArrayList<Particle> plist;
ParticleSystem() {
plist = new ArrayList<Particle>();
}
void run() {
for (int i = plist.size() - 1; i >= 0; i--) {
Particle p = plist.get(i);
p.run();
if (p.isDead()) {
plist.remove(i);
}
}
}
void addnew(float x, float y) {
plist.add(new lizi(x, y));
}
int getSize() {
int cnt = 0;
for(Particle p: plist){
cnt += p.type;
}
return cnt;
}
}