互动媒体技术——代码本色
参考《代码本色》的第0~4章内容及其实例程序,针对这5章分别编写1个习作(一共5个),每个习作都有不少于2个案例参考,且必须有一定的拓展,体现随机行为及牛顿运动学;代码大部分参照书上的文档以及浏览网上的创意。
第0章 引言
标题Perlin噪声生成起伏地形
Perlin噪声算法表现出了一定的自然性,因为它能生成符合自然排序(“平滑”)的伪随机 数序列。图I-5展示了Perlin噪声的效果,x轴代表时间;请注意曲线的平滑性。图I-6展示 了纯随机数的效果。(生成图形的代码可以在本书的下载资料中找到。)
图0-5 噪声
图0-6 随机 Processing内置了Perlin噪声算法的实现:noise()函数。noise()函数可以有1~3个参 数,分别代表一维、二维和三维的随机数。我们先从一维的noise()函数开始了解。
int c,s,d=20,w=1000,h=1000;
float[][] t;
float p=0;
void setup()
{
size(800,600,P3D);
c=w/d;
s=h/d;
t=new float[c][s];
}
void draw()
{
background(0);
stroke(255);
fill(123);
translate(width/2,height/2+80);
rotateX(7*PI/18);
translate(-w/2,-h/2);
p+=0.01;
float n=p;
for(int y=0;y<s;y++)
{
float m=0;
for(int x=0;x<s;x++)
{
t[x][y]=map(noise(m,n),0,1,0,200);
m+=0.15;
}
n+=0.15;
}
for(int y=0;y<s-1;y++)
{
beginShape(TRIANGLE_STRIP);
for(int x=0;x<s;x++)
{
vertex(x*d,y*d,t[x][y]);
vertex(x*d,(y+1)*d,t[x][y+1]);
}
endShape();
}
}
主要函数map(),beginShape(TRIANGLE_STRIP); TRIANGLE_STRIP的运用画地形。
第 1 章 向量
繁星
以这个长方体的一个顶点为原点,建立世界坐标系。然后选取一个面作为Processing 程序的窗口,建立屏幕坐标系。然后在屏幕上任选一个点,作为消失点,该点在屏幕坐标系下的坐标为(x_endpoint,y_endpoint )。
接着在这个长方体里随机生成一系列的星星,每个星星由一个三维向量(x_world,y_world,z_world)表示,这是它在世界坐标系下的坐标。这个几乎是参考网上的东西。
接下来我们要做的就是把每一个星星的在世界坐标系的坐标,转换成在processing窗口里面的坐标(x_screen,y_screen),这样我们就可以在屏幕上把星星绘制出来。
首先需要计算星星在世界坐标系下相对于消失点(endpoint)x,y方向的偏移量,接下来将这个偏移量根据星星的z_world进行放缩,最后再将放缩完成的偏移量加回消失点的坐标,得到星星在屏幕上的坐标。
公式如下:
class Star{
final float MAX_DIAM = 16;
final float MAX_DEPTH = width / 2;
final float SCALE = MAX_DEPTH;
PVector worldPosition, screenPosition, viewPosition;
float diam;
Star(){
worldPosition = new PVector(random(0, width), random(0, height), random(0, MAX_DEPTH));
}
void transform(PVector endpoint){
viewPosition = PVector.sub(worldPosition, endpoint).div(worldPosition.z).mult(SCALE);
screenPosition = PVector.add(endpoint, viewPosition);
diam = map(worldPosition.z, 0, MAX_DEPTH, MAX_DIAM, 0);
}
void checkEdge(){
if(screenPosition.x <= 0 || screenPosition.x >= width || screenPosition.y <=0 || screenPosition.y >= height){
worldPosition.set(random(0, width), random(0, height), MAX_DEPTH);
}
}
void display(){
fill(255,255,0);
noStroke();
ellipse(screenPosition.x, screenPosition.y, diam, diam);
}
void move(float speed){
worldPosition.z -= speed;
worldPosition.z = constrain(worldPosition.z, 0, MAX_DEPTH);
}
class StarField{
final int STAR_COUNT = width / 2;
ArrayList<Star> stars;
PVector endpoint;
StarField(){
endpoint = new PVector(mouseX, mouseY);
stars = new ArrayList();
for(int i = 0; i < STAR_COUNT; i++){
stars.add(new Star());
}
}
void run(){
final int MAX_SPEED = 11, MIN_SPEED = 1;
final int SPEED_STEP = 1;
int speed;
speed = (MAX_SPEED + MIN_SPEED) / 2;
for(Star s : stars){
s.transform(endpoint);
s.checkEdge();
s.display();
s.move(speed);
}
StarField sf;
void setup(){
size(600, 600);
sf = new StarField();
}
void draw(){
background(0);
sf.run();
}
void mousePressed(){}
void mouseMoved(){}
}
}
}
第2章——力学
实验模拟了小球受到重力,向左的风的力量,和入睡时水的阻力,其中还包括触碰反弹。
class Ball {
float ma;
PVector xy;
PVector speedxy;
float speedmax;
PVector accel;
Ball(float m, float x, float y) {
ma = m;
xy = new PVector(x, y);
speedxy = new PVector(random(10,15), 15);
speedmax = 20;
accel = new PVector(0, 0);
}
void display() {
fill(255, 255,0);
noStroke();
ellipse(xy.x, xy.y, 10*ma, 10*ma);
}
void Force2(PVector force) {
PVector f = force.get();
f.div(ma);
accel.add(f);
}
void location() {
speedxy.add(accel);
speedxy.limit(speedmax);
xy.add(speedxy);
accel.mult(0);
}
void fanzhe() {
if ( xy.x < 0 ) {
xy.x = 0;
speedxy.x *= -1;
} else if ( xy.x > width ) {
xy.x = width;
speedxy.x *= -1;
}
if ( xy.y < 0 ) {
xy.y = 0;
speedxy.y *= -1;
} else if ( xy.y > height ) {
xy.y = height;
speedxy.y *= -1;
}
}
boolean inwater(Liquid w) {
if (w.lx<xy.x && xy.x<w.lx+w.lwidth && w.ly<xy.y && xy.y<w.ly+w.lheight) {
return true;
} else {
return false;
}
}
void drag(Liquid w) {
float lz = 0.024;
float v = speedxy.mag();
PVector drag = speedxy.get();
drag.normalize();
drag.mult(-1);
drag.mult(v * v * lz);
Force2(drag);
}
}
class Liquid {
float lx, ly, lwidth, lheight;
float lz;
Liquid(float lx_, float ly_, float lwidth_, float lheight_, float lz_){
lx = lx_;
ly = ly_;
lwidth = lwidth_;
lheight = lheight_;
lz = lz_;
}
void display() {
noStroke();
fill(150, 200, 255);
rect(lx, ly, lwidth, lheight);
}
}
Ball[] ball = new Ball[20];
Liquid water;
void setup() {
size(860, 640);
for (int i = 0; i < ball.length; i++) {
ball[i] = new Ball(random(2,3), random(0,width/2), random(0,height/2));
}
water = new Liquid(0, (height/3)*2, width, height/3, 0.8);
}
void draw() {
background(124,24,220);
water.display();
for (int i = 0; i < ball.length; i++) {
float m = ball[i].ma;
PVector gravity = new PVector(0, 0.001*m);
PVector wind = new PVector(0.03, 0);
if (ball[i].inwater(water)) {
ball[i].drag(water);
}
ball[i].Force2(gravity);
ball[i].Force2(wind);
ball[i].location();
ball[i].display();
ball[i].fanzhe();
}
}
第三章——振荡
球摆
由于摆锤运动是以轴点为圆心做运动,所以,我们引入角度,角速度,角加速度等几个概念来表达摆锤的运动。摆锤的长度是固定的,唯一变化的部分只是绳子的角度,我们可以用角速度与角加速度模拟钟摆的运动。小球只受到两个力,一是小球自身的重力,二是绳子对小球的拉力。因为合力和绳子互相垂直,通过绘图可知,最终angularAcceleration = gravity * sin(angle),与小球质量无关。但实际上,钟摆摆臂的长度对加速度的影响很大,摆臂越长,加速度越小。所以,角加速度angularAcceleration = -1 * gravity * sin(angle) / string。 而为了模拟现实世界,还会受到摩擦力和空气阻力,我们可以设置一种衰减方式,设置变量damping。
class Pendulum {
PVector location;
float angA, angV,angle,string, d;
PVector origin;
Pendulum(float string_, float angle_) {
location = new PVector(0, 0);
angle = angle_;
angV = 0;
angA = 0;
string = string_;
origin = new PVector(width/2, 50);
// origin = (mouseX,mouseY);
d = 0.995;
}
void update() {
float gravity = 0.98;
angA = -1 * sin(angle) * (gravity/string);
angV += angA;
angle += angV;
angA *= d;
}
void display() {
location = new PVector(string * sin(angle), string * cos(angle));
location.add(origin);
stroke(255,0,255);
fill(255,0,255);
line(origin.x, origin.y, location.x, location.y);
ellipse(location.x, location.y, 30, 30);
}
}
Pendulum[] ball = new Pendulum[20];
void setup() {
size(1280, 640);
for (int i=0; i<ball.length; i++) {
ball[i] = new Pendulum( 30*i, PI/4);
}
}
void draw() {
background(0);
for (int i=0; i<ball.length; i++) {
ball[i].update();
ball[i].display();
}
}
.
第4章——粒子系统
火焰
我们在setup函数中初始化了我们的粒子系统,然后在draw函数中不断更新背景的颜色、粒子的状态,以及监听按下鼠标左键事件,判断是否需要加入新的火焰。
注意这里颜色的模式不是RGB,而是HSB。并且按下一次鼠标左键会调用多次addFire函数,因为按下鼠标不是一瞬间的事情,这段时间会执行多次draw函数。
class Fire extends Particle {
ArrayList<PVector> plist;
Fire(float x, float y) {
super(x, y);
PVector v = new PVector(mouseX - pmouseX, mouseY - pmouseY);
v.mult(0.1);
v.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();
if (int(random(5)) == 0) {
int spawnCount = int(random(3))+1;
for (int i = 0; i < spawnCount; i++) {
spawn();
}
}
}
void spawn() {
float size = random(25, 50)*map(span, maxspan, 0, 1, 0);
if (size > 0) {
p.addSmoke(location.x + origin.x , location.y + origin.y, size);
}
}
void drawShape() {
for (PVector p : plist) {
stroke(p.z, 255, 255, 20);
strokeWeight(80);
point(p.x, p.y);
stroke(p.z, 255, 255, 100);
strokeWeight(30);
point(p.x, p.y);
}
}
}
class Particle {
PVector location, v, acceler, origin;
float angle, angV, angA;
float span, Rate, maxspan;
int type, h;
Particle(float x, float y) {
origin = new PVector(x, y);
location = new PVector();
acceler = new PVector(0, 0.05);
v = PVector.random2D();
span = maxspan = 50;
Rate = random(0.35, 1);
h = 20;
type = 1;
}
float getSpeed(float s){
float t = maxspan / Rate;
return s / t;
}
void run() {
update();
display();
}
void update() {
v.add(acceler);
location.add(v);
angV += angA;
angle += angV;
span -= Rate;
}
boolean isDead() {
if (span < 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(span, 0, maxspan, 0, 1));
drawShape();
popMatrix();
}
void drawShape() {
stroke(h, 255, 255);
strokeWeight(30);
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 addFire(float x, float y) {
plist.add(new Fire(x, y));
}
void addSmoke(float x, float y, float size) {
plist.add(new Smoke(x, y, size));
}
int getSize() {
int cnt = 0;
for(Particle p: plist){
cnt += p.type;
}
return cnt;
}
}
class Smoke extends Particle {
float size, alpha;
Smoke(float x, float y, float _size) {
super(x, y);
size = _size;
alpha = random(10, 150);
span = maxspan = 30;
Rate = random(0.4, 1.25);
angle = random(-45, 45);
angV = random(-2, 2);
v.set(0, getSpeed(random(-100, -100)));
acceler.mult(0);
type = 0;
}
void drawShape() {
stroke(0, 0, 0, alpha);
strokeWeight(size);
point(0, 0);
}
}