互动媒体编程习作
目录
实验要求
主题
创作一组编程习作,体现随机行为及牛顿运动学。
要求
- 报告:写一篇文章,发表为博文/推文等形式,描述运用的规律,若用到了数学/物理/化学等学科中的知识,要用平实易懂的语言介绍原理,尝试运用凝练的数学语言表达(公式、方程、推导等),特别要描述出这些原理如何在作品中呈现的。
第0章 随机行为
实验原理
柏林噪声
在80年代Ken Perlin发明了一种更加有用的噪音,称为Perlin噪音。Perlin噪音的构造思路非常简单,既然基本随机噪音因为离散性而没有意义,那就人为让噪音连续起来就好了啊。最简单的连续化处理就是插值,在离散数据中间用函数插值的方法把空隙填满空间就自然连续了。说到插值,学过数值分析的立刻就能想到七八种插值方法,只要能保持连续性不管是三角函数,正态分布,还是样条曲线都可以使用。我想当时Ken Perlin在发明Perlin噪音也是非常直觉地选择了一种类正态分布的函数。对简单随机噪音插值之后都得到了一种像霉菌表面的图像。要看清Perlin噪音的形态可以对噪音画出等高线图,这样就能看到噪音呈现出奇异的类似生物的特征。Perlin噪音的广泛使用就是和它类生物性有很大关系,如果只是用周期函数叠加很难达到这种效果。
实验原理
通过柏林噪声来模拟流场,柏林噪声随机生成粒子的角度和运动方向,粒子的颜色也是通过柏林噪声进行随机的。
实验效果
实验代码
//设置1000个粒子
int num = 1000;
//粒子数组
Particle[] particles = new Particle[num];
//初始化
void setup() {
size(1024, 728, P3D);
noStroke();
for (int i=0; i<num; i++) {
PVector loc = new PVector(random(width*1.2), random(height));
float rad = random(TWO_PI);
PVector speed = new PVector(0, 0);
PVector acc = new PVector(cos(rad), sin(rad));
particles[i]= new Particle(loc, speed, acc);
}
}
void draw() {
fill(0, 10);
noStroke();
rect(0, 0, width, height);
fill(255, 155);
for (int i=0; i<particles.length; i++) {
particles[i].run();
}
}
class Particle {
PVector loc, speed, acc;
color col;
float rad;
float maxVel = 1.0;
float w = 500.0;
float h = 500.0;
float f = 1000.0;
//三个参数的构造函数
Particle(PVector _loc, PVector _speed, PVector _acc) {
loc = _loc;
speed = _speed;
acc = _acc;
}
//空构造函数
Particle() {
loc = new PVector(random(width*1.2), random(height));
rad = random(TWO_PI);
speed = new PVector(0, 0);
acc = new PVector(cos(rad), sin(rad));
}
void run() {
move();
checkEdges();
render();
}
//例子移动
void move() {
//通过柏林噪声生成角度
float deg = 360.0*noise(
loc.x/w,
loc.y/h,
millis()/10000.0);
rad=radians(deg);
acc.set(cos(rad), sin(rad));
speed.add(acc);
if (speed.magSq()>maxVel) {
speed.normalize();
speed.mult(maxVel);
}
loc.add(speed);
}
//检测边缘
void checkEdges() {
if (loc.x<0 || loc.x>width || loc.y<0 || loc.y>height) {
loc.x = random(width*1.2);
loc.y = random(height);
}
}
//使用不同的颜色渲染粒子
void render() {
fill(int(noise(loc.x/w,loc.y/h,millis()/10000.0)*255),int(noise(loc.x/w,loc.y/h,millis()/1000.0)*255),int(noise(loc.x/w,loc.y/h,millis()/100.0)*255));
ellipse(loc.x, loc.y, 2, 2);
}
}
第1章 向量
实验原理
本次实验主要根据向量那章进行拓展,现在屏幕的一定位置范围内随即生成一堆例子,粒子在屏幕上进行随机的运动,当粒子碰到边界就进行相反方向的运动。同时加入了鼠标交互,起初当三个点的距离小于阈值,就会形成一个彩色的三角面,当点击鼠标后,就只有三个点的连线。有一种星空中星座的感觉。
实验效果
实验代码
//粒子群
ArrayList poop;
//粒子距离阈值
int distance =50;
//判断是否形成面
boolean flag=true;
//初始化
void setup()
{
size(500, 500);
smooth();
poop = new ArrayList();
for (int i=0;i<100;i++) {
PVector PD = new PVector(random(-150, 150), random(-150, 150));
Dots D = new Dots(PD);
poop.add(D);
}
}
void draw()
{
background(0);
translate(width/2, height/2);
pushStyle();
stroke(0,50);
popStyle();
//循环没一个粒子
for (int i=0;i<poop.size();i++) {
Dots dots1 = (Dots) poop.get(i);
dots1.display();
dots1.update();
//第二次循环粒子
for (int j=i+1;j<poop.size();j++) {
Dots dots2 = (Dots) poop.get(j);
dots2.update();
//粒子1的距离与粒子2的距离小于阈值
if (dist(dots1.location.x, dots1.location.y, dots2.location.x, dots2.location.y)<distance) {
for (int k=j+1;k<poop.size();k++) {
Dots dots3 = (Dots) poop.get(k);
dots3.update();
//填充
if (flag) {
fill(dots3.c, 50);
noStroke();
}
else
{
noFill();
stroke(255,50);
}
//判断粒子2和粒子3的距离是否小于阈值
if (dist(dots3.location.x, dots3.location.y, dots2.location.x, dots2.location.y)<distance) {
//进行连线
beginShape();
vertex(dots3.location.x, dots3.location.y);
vertex(dots2.location.x, dots2.location.y);
vertex(dots1.location.x, dots1.location.y);
endShape();
}
}
}
}
}
}
//鼠标点击更改flag
void mousePressed()
{
flag=!flag;
}
第2章 力
实验原理
物体通过流体或者气体时同样会受摩擦力的作用,这种摩擦力有很多名字,如粘滞力、阻力和流体阻力。流体阻力产生的效果和前面的摩擦力相同(物体会减速), 但是计算阻力的方式却有些不同。先来看看阻力公式:
F
d
=
−
1
2
ρ
v
2
A
C
d
v
⃗
F_d=-\frac{1}{2}\rho v^2AC_d \vec v
Fd=−21ρv2ACdv
让我们试着把这个公式分解,选出模拟过程中需要的部分,最后将整个公式简化,以便于用Processing模拟。
- Fd代表阻力,我们的最终目的就是计算这个阻力向量,将它传入applyForce ()函数。
- ρ是希腊字母rho,它代表流体的密度,我们分别设置四种流体的密度。
- v代表物体的移动速率。
- A代表物体前端推动流体( 或气体)流动部分的面积。举个例子,根据空气动力学设计的兰博基尼跑车所受的空气阻力肯定比四四方方的沃尔沃汽车小。为了方便模拟,我们假定物体都是球形的,因此,这个变量也将被我们忽略。
- C是阻力系数,它和摩擦系数p类似,是一个常量。我们可以根据阻力的强弱确定它的大小。
- v它代表速度的单位向量。和摩擦力一样,阻力的方向也和物体的运动方向。
本个实验习作模拟了相同物体在不同的液体中收到的阻力对物体运动产生的影响。
如图的四个液体分别为水,汞,油,液氮(在理想情况下)。
实验效果
实验代码
//定义小球
Mover[] movers = new Mover[4];
//定义液体密度
float[] p= new float[]{1.0,13.6,0.8,0.1252};
//液体对象
Liquid liquid;
//初始化
void setup() {
size(640, 360);
print(height);
randomSeed(1);
reset();
liquid = new Liquid(0, height/2+20, width, height/2+20, 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],p[i]);
movers[i].applyForce(dragForce);
}
PVector gravity = new PVector(0, 0.1*movers[i].mass);
movers[i].applyForce(gravity);
movers[i].update();
movers[i].display();
movers[i].checkEdges();
}
fill(255);
}
//点击鼠标重置
void mousePressed() {
reset();
}
void reset() {
for (int i = 0; i < movers.length; i++) {
movers[i] = new Mover(3*2.25, 20*4+i*160, 0);
}
}
第3章 振荡
实验原理
该习作主要模拟了一个柔软的表面,当鼠标运动时,这个表面的振动速率会随着鼠标位置的增大或减小。
主要以一定间隔绘制粒子,将鼠标的位置映射到x方向和y方向上的角度,最后计算出小球的抖动角度,进而模拟了一个运动的波面(水面,表面)。以小球的振荡,给人一种整个面在震荡的效果。
实验效果
实验代码
//时间变量
float t = 0;
//初始化
void setup() {
size(600, 600);
noStroke();
fill(40, 200, 40);
}
void draw() {
background(10, 10);
//间隔
for (float x = 0; x <= width; x = x + 30) {
for (float y = 0; y <= height; y = y + 30) {
//x角度
float xAngle = map(mouseX, 0, width, -4 * PI, 4 * PI);
//y角度
float yAngle = map(mouseY, 0, height, -4 * PI, 4 * PI);
float angle = xAngle * (x / width) + yAngle * (y / height);
//使小球绕一个圆运动
float myX = x + 20 * cos(2 * PI * t + angle);
float myY = y + 20 * sin(2 * PI * t + angle);
ellipse(myX, myY, 10,10);
}
}
t = t + 0.01;
}
第4章 粒子系统
实验原理
将粒子分为四种状态,扩张,收缩,运动,静止。前两种和后两种进行组合一共四种状态。当由收缩变为扩张或由扩张变为收缩时,运用插值使粒子具有拖影效果。通过按S键使粒子运动或者静止。通过鼠标和键盘与程序的交互使程序具有动态性,交互性。粒子同时也会随着鼠标的运动而一起运动。
实验效果
实验代码
//粒子数量
int num = 1000;
//最大运动速度
float mts = PI/24;
//圆的半径
int r = 100;
//范围
int rdtr = 5;
//小圆的半径
int rdu = 1;
PVector v[]=new PVector[num];
//标记是扩张还是收缩
boolean mv = true;
//标记是否运动
boolean mo = true;
//每个粒子的颜色
color c[] = new color[num];
//每个粒子的起始角度
float theta[] = new float[num];
//每个粒子的移动角度
float mtheta[] = new float[num];
//粒子的移动速度
float dtheta[] = new float[num];
float easing[] = new float[num];
int rdt[] = new int[num];
/初始化
void setup() {
colorMode(RGB,255,255,255);
size(500,500);
for(int i =0;i<num-1;i++){
c[i] = color(random(100,200),random(100,200),random(100,200));
v[i] = new PVector(random(width),random(height));
theta[i] = round(random(360));
dtheta[i] = random(mts);
mtheta[i] = theta[i]/180*PI;
rdt[i] = round(random(-rdtr,rdtr));
easing[i] = random(0.02,0.3);
}
frameRate(60);
}
void draw() {
fill(25,25,25,25);
rect(0,0,width,height);
pushMatrix();
noStroke();
//扩张状态
if(mv){
//运动状态
if(mo){
for(int i = 0;i<num-1;i++){
mtheta[i] += dtheta[i];
//对向量做插值
v[i].lerp(mouseX+cos(mtheta[i])*(rdt[i]+r), mouseY+sin(mtheta[i])*(rdt[i]+r),0,easing[i]);
fill(c[i]);
ellipse(v[i].x, v[i].y, rdu,rdu);
}
}
//静止状态
if(!mo){
for(int i = 0;i<num-1;i++){
v[i].lerp(mouseX+cos(mtheta[i])*(rdt[i]+r), mouseY+sin(mtheta[i])*(rdt[i]+r),0,easing[i]);
fill(c[i]);
ellipse(v[i].x, v[i].y, rdu,rdu);
}
}
}
//收缩状态
if(!mv){
//运动状态
if(mo){
for(int i = 0;i<num-1;i++){
mtheta[i] += dtheta[i];
v[i].lerp(mouseX+cos(mtheta[i])*rdt[i], mouseY+sin(mtheta[i])*rdt[i],0,easing[i]);
fill(c[i]);
ellipse(v[i].x, v[i].y, rdu,rdu);
}
}
//静止状态
if(!mo){
for(int i = 0;i<num-1;i++){
v[i].lerp(mouseX+cos(mtheta[i])*rdt[i], mouseY+sin(mtheta[i])*rdt[i],0,easing[i]);
fill(c[i]);
ellipse(v[i].x, v[i].y, rdu,rdu);
}
}
}
popMatrix();
fill(0);
rect(0,0,width,15);
fill(255);
if(mv){
fill(255,0,0);
}
if(!mv){
}
if(mo) {
fill(255,0,0);
}
if(!mo){
fill(255);
}
}
void mousePressed(){
mv = !mv;
}
void keyPressed(){
if(key == 's'||key == 'S'){
mo =!mo;
}
}
//鼠标滚轮滚动,增加半径或减少半径
void mouseWheel(MouseEvent event){
float e = event.getCount();
if(e == -1) r+=10;
if(e == 1) r-=10;
}