《代码本色》0-4章创意编程——互动媒体技术作业
1.第0章 随机游走
实现了矩形在二维空间中,朝着鼠标所在方向随机游走,颜色变化通过泊林噪声和映射噪声控制。
(1)路线的选择
设置一个[0,1]范围的随机数,根据随机数值的大小确定当前方块的走向。若随机数小于0.5,朝鼠标所在方向前进,即xdir=mouseX-x,ydir=mouseY-y;若随机数大于0.5,朝随机方向前进,利用random函数获得指定范围的随机数。
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;
}
x = constrain(x, 0, width-1);
y = constrain(y, 0, height-1);
}
(2)颜色变化
颜色R,G,B对应的值都是动态变化的,它们的参数都是根据泊林噪声和映射噪声计算而来。Perlin噪声的实现是利用noise()函数,random()函数的参数是目标随机数的最小值和最大值,但是noise()函数并非如此。noise()函数的结果范围是固定的,它总是会返回一个介于 0~1的结果。后面再通过map()函数来改变结果的范围。
观察运行时的图像发现,图像颜色具有一定规律,在渐变的基础上,当方块移动到左边时开始变黑,移动到右边时开始变红,且在不同区域的颜色都有一定规律。
void render() {
strokeWeight(2);
//point(x, y);
float n = noise(t);
float k = map(n,0,1,0,width);
float v = map(n,0,1,0,height);
t += 0.01;
fill(k,v,v);
stroke(x);
rect(x,y,50,10);
}
2.第1章 向量
利用了位移、速度、加速度等知识,实现鼠标引导,在二维平面中画出具有三维效果的图案。
物体的运动中,对于位置有position=position+velocity,对于速度有velocity=velocity+accelerate,我们利用这一知识,结合向量的加减乘除、模运算等实现随鼠标的加速运动。
向量计算不直接用运算符号,而要使用PVector类的函数,其使用方法有两种,一种是通过对象调用,例如mouse.sub(position)表示mouse向量减去position向量,另一种表达方法是通过调用静态方法PVector.sub(mouse,position)来计算。
为了便于实现,设置Mover类,变量包含位置、速度、加速度等信息,同时具有计算移动和绘制的函数。
class Mover{
PVector position; //位置
PVector velocity; //速度
PVector accelerate; //加速度
float maxspeed=7;
Mover(){
position=new PVector(width/2,height/2);
velocity=new PVector(0,0);
}
void update(){
PVector mouse=new PVector(mouseX,mouseY);
accelerate=PVector.sub(mouse,position);
accelerate.normalize(); //单位化,求出加速度方向
accelerate.mult(2); //乘以加速度大小
velocity.add(accelerate);
velocity.limit(maxspeed); //限制最大速度
position.add(velocity);
}
void display(){
float r=random(30,100);
fill(r*2,r,r+10);
ellipse(position.x,position.y,r,r);
}
}
Mover[] movers=new Mover[10];
void setup(){
background(255);
size(800,800);
float m,n;
for(int i=0;i<movers.length;i++){
movers[i]=new Mover();
}
}
void draw(){
for(int i=0;i<movers.length;i++){
movers[i].update();
movers[i].display();
}
}
3.第2章 力
结合了小球进入水中的浮力,挡板的弹力,小球在水面上向右的风力和进入水中向右的水流,四种力作用下,小球的运动。
流体阻力的模拟,原先物理中,浮力的公式为F=ρgV,但此处为了计算的方便,我们将阻力公式简化为如下形式:
其中相同速度进入液体中的物体,液体的阻力系数越大,物体的速度下降越快。
风力和水流的造成的效果,其实就是在水平方向施加力,由F=ma知,其作用相当于给球一个恒定的水平加速度。
Water类:
class Water {
// Liquid is a rectangle
float x, y, w, h;
// Coefficient of drag
float c;
Water(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);
}
}
实现:
Mover[] movers = new Mover[9];
// Liquid
Water water;
Water ban;
void setup() {
size(640, 360);
reset();
// Create liquid object
water = new Water(0, 2*height/3, width, height/3, 0.1);
ban=new Water(0,height/2,width/2,20,2.5);
}
void draw() {
background(255);
PVector wind=new PVector(0.01,0); //风
PVector flow=new PVector(0.02,0); //水流
// Draw water
fill(121,205,205);
water.display();
fill(0);
ban.display();
for (int i = 0; i < movers.length; i++) {
// Is the Mover in the liquid?
if (water.contains(movers[i])) {
// Calculate drag force
PVector dragForce = water.drag(movers[i]);
// Apply drag force to Mover
movers[i].applyForce(dragForce);
}
else{
movers[i].applyForce(wind);
}
if(movers[i].position.y+movers[i].mass*16>ban.y&&movers[i].position.y-movers[i].mass*16<ban.y+width&&movers[i].position.x<width/2+movers[i].mass*16){
PVector dragForce2 = ban.drag(movers[i]);
// Apply drag force to Move
movers[i].applyForce(dragForce2);
}
// Gravity is scaled by mass here!
PVector gravity = new PVector(0, 0.1*movers[i].mass);
// Apply gravity
movers[i].applyForce(gravity);
movers[i].applyForce(flow);
// Update and display
movers[i].update();
movers[i].display();
movers[i].checkEdges();
}
fill(0);
text("click mouse to reset", 10, 30);
}
void mousePressed() {
reset();
}
// 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);
}
}
4.第3章 振荡
标点击黄色圆球可以进行拖拽,改变绳子张开角度和绳子长度,整体的钟摆效果也随之变化。
构造两个类,分别为Pendulum类控制最中间绳子和黄色小球的钟摆运动,Oscillator类控制彩色小球围绕黄色小球做震荡。若不拖拽,黄色小球最终会竖直垂挂在绳子上,而周围小球一直围绕它震荡。
在模拟震荡中需要考虑到圆周运动的角速度、角加速度等,且震荡与三角函数紧密相关。在角运动中:
角度=角度+角速度
角速度=角速度+角加速度
且需注意角度与弧度的转换。
摆钟运动由于具有能量损失,所以一定会不断减速直到停止,摆动的最大高度也因此不断下降,最终竖直下垂。根据受力分析可知,钟摆运动时加速度与绳子与垂直方向夹角有关,且是与其sin值成一定比例,因此需要在update函数中,更新角加速度的值,同时更新速度与角度的值。
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
}
}
添加dragging判断条件,判断鼠标是否选中圆球,从而实现选择拖拽的功能,在拖拽时,需根据鼠标的位置重新计算绳子长度以及夹角。
//判断鼠标点击
void clicked(int mx, int my) {
float d = dist(mx, my, position.x, position.y);
if (d < ballr) {
dragging = true;
}
}
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)); //两点之间差值
angle = atan2(-1*diff.y, diff.x) - radians(90);
//拉动绳子的长度
PVector mouse=new PVector(mouseX,mouseY);
r=PVector.sub(mouse,origin).mag();
position=new PVector(mouse.x,mouse.y);
}
}
多个小球围绕中心小球震荡的实现:
void display() {
float x = sin(angle.x)*amplitude.x;
float y = sin(angle.y)*amplitude.y;
pushMatrix();
translate(width/2, height/2);
stroke(0);
strokeWeight(2);
fill(127,127);
line(center.x, center.y, x+center.x, y+center.y);
fill(random(0,255),random(0,255),random(0,255));
ellipse(x+center.x, y+center.y, 32, 32);
popMatrix();
}
5.第4章 粒子系统
模拟了水中环境,鼠标位置会不断产生上浮的气泡(泡泡的效果利用了png),且遇到障碍物时会产生作用力,产生粒子由于阻碍分开的效果。
定义三个类,分别为Particle类控制单个粒子的行为,ParticleSystem控制整个粒子系统,Repeller创造一个具有作用力的障碍物。
典型的粒子系统中都有一个发射器,发射器是粒子的源头,它控制粒子的初始属性, 包括位置、速度等。发射器发射的粒子一般是一股粒子或粒子流。关键的一点在于:在一个典型的粒子系统中,粒子在发射器中诞生,但并不会永远存在,应该伴随着新的粒子产生,旧的粒子消亡。因此我们需要为单个粒子设定生存时间,用一个lifespan变 量代表粒子的生存期,以一个较大值为初值,不断递减,当值变为0时粒子消亡。
为了便于管理这些粒子对象,我们使用ArrayList类,它与数组的作用类似。
for (Particle p : particles) {
p.run();
}
用for循环对每个粒子进行操作
Particle类中:
void update() {
vel.add(acc);
pos.add(vel);
acc.mult(0);
lifespan -= 2.0;
}
// 判断粒子是否死亡
boolean isDead() {
if (lifespan <= 0.0) {
return true;
}
else {
return false;
}
}
ParticleSystem类中:
class ParticleSystem {
ArrayList<Particle> particles; // An arraylist for all the particles
PImage textures;
ParticleSystem(PImage imgs, PVector v) {
textures = imgs;
particles = new ArrayList(); // Initialize the arraylist
}
void run() {
for (int i = particles.size()-1; i >= 0; i--) {
Particle p = particles.get(i);
p.run();
if (p.isDead()) {
particles.remove(i);
}
}
}
void addParticle(float x, float y) {
particles.add(new Particle(x,y,textures));
}
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 addParticle(Particle p) {
particles.add(p);
}
// A method to test if the particle system still has particles
boolean dead() {
if (particles.isEmpty()) {
return true;
}
else {
return false;
}
}
}
受力的例子系统将更加真实,我们为系统中添加了一个排斥对象,粒子在接触到它时,会排斥开来,它对每个粒子的作用都不相同,需要分别计算。
Repeller类:
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,0,0);
strokeWeight(2);
fill(255,0,0);
ellipse(position.x,position.y,60,60);
}
// Calculate a force to push particle away from repeller
PVector repel(Particle p) {
PVector dir = PVector.sub(position,p.pos);
float d = dir.mag();
dir.normalize();
d = constrain(d,5,100);
float force = -1 * G / (d * d);
dir.mult(force);
return dir;
}
}
总结
通过本次作业,接触到了用代码的方式模拟各种力的作用以及物体运动,有些情况下并不是完全按照其物理形式进行模拟,因为最终目标是用图像显示出较真实的物理效果,但核心思想与其物理原理是一致的,并且如何发挥创意,将多种技术点结合生成一幅有意思的作品,也需要不断思考。