互动媒体——随及行为以及运动学

橙三吉。 提交于 2020-01-07 01:37:57

随机行为以及运动学

本学期参考《代码本色》,为了实现模拟一个生态系统的目标进行了循序渐进的学习。主要依照书本前五章的内容,按照书中提示的思路,拓展了书中的程序。

随机

多数仅仅涉及到数据计算以及逻辑问题的编程情况下,使用的随机数大多数是伪随机数。这类随机数的一个明显特点便是生成的数据不够平滑。在计算机图形学中,以及游戏设计领域,产生平滑的噪声是一个非常重要的课题。诸如游戏中的地形生成、水面生成,都需要用到平滑的随机数——Perlin噪声。
针对本章中利用Perlin噪声生成云以及生成树木的例子。根据分形艺术理论,编程实现了二维视角下的蒲公英和三维视角下的树木。
在这里插入图片描述
在这里插入图片描述

两种植物均采用了Perlin噪声来生成颜色,形成渐变而且不冲突的色彩。另外,对于树木,还采用了Perlin噪声来生成三个方向上不同角度的分支。生成颜色参考的是书中生成云彩的例子,即:
在这里插入图片描述
代码如下:

stroke(map(noise(r+=0.1),0,1,100,255),map(noise(r+=0.1),0,1,0,255),map(noise(r+=0.1),0,1,0,255));

两种植物均采用了分形艺术的手法,整体思路如下:
在这里插入图片描述实现旋转以及平移时,针对坐标进行操作,并没有采用Processing自带的函数。而是依照计算机图形学的相关知识,自定义了方法:


void MultiMatrix(float[][] T)
{
  float[][] pNew=new float[2][3];
  for(int i=0;i<2;i++)
  {
    for(int j=0;j<3;j++)
    {
      pNew[i][j]=pOld[i][j];
    }
  }
  for(int i=0;i<2;i++)
  {
    pOld[i][0]=pNew[i][0]*T[0][0]+pNew[i][1]*T[1][0]+pNew[i][2]*T[2][0];
    pOld[i][1]=pNew[i][0]*T[0][1]+pNew[i][1]*T[1][1]+pNew[i][2]*T[2][1];
    pOld[i][2]=1;
  }
}

void Translate(float dx,float dy)
{
  float[][] T=new float[3][2];
  T[0][0] = 1; T[0][1] = 0;
  T[1][0] = 0; T[1][1] = 1;
  T[2][0] = dx; T[2][1] = dy;
  MultiMatrix(T);
}

void Rotate(float thta)
{
  float[][] T=new float[3][2];
  T[0][0] = cos(thta*PI / 180); ; T[0][1] = sin(thta*PI / 180);
  T[1][0] = -sin(thta*PI / 180);  T[1][1] = cos(thta*PI / 180);
  T[2][0] = 0; T[2][1] = 0;
  MultiMatrix(T);
}

上述三个函数的作用分别是矩阵相乘、平移、旋转。有关计算机图形学的知识与本次的实验主旨相关性不大,不再赘述,有兴趣的读者可以翻阅相关资料。
上面提到了,依照向量的方式获得每一个新的分支的两个端点,图解如下:
在这里插入图片描述上图中,P1、P2为需要生长的分支的两个端点。P1、P3(未知)是生长出的分支的两个端点。可以看到,新分支和老分支共用一个端点。而且在进行图形的平移旋转之前,两个分支是在一条直线上的。因此,如果知道了新分支和老分支的长度比alpha,就可以利用向量计算出P3,P3=P1+alpha*(P1-P2)。

  float dx=L[1][0]-L[0][0];
  float dy=L[1][1]-L[0][1];
  pOld[0][0]=L[1][0];
  pOld[0][1]=L[1][1];
  pOld[0][2]=1;
  pOld[1][0]=L[1][0]+dx/2;
  pOld[1][1]=L[1][1]+dy/2;

为了能够向存储树的分支结点的数组中追加新的数据元素,采用了Processing的FloatList数据结构,计算出新的数据之后,利用其方法append向表中追加新的元素。

另外,编程过程中容易犯错的一点是,进行生长操作的每一个分支都必须是新产生的,所以需要记录追加进来的分支的索引:

void draw()
{
  camera(eyex++, eyey++, eyez++, 320.0, 120.0, 0.0, 
       0.0, 1.0, 0.0);
  Iteration++;
  background(255,255,255);
  stroke(0,0,0);
  for(int i=0;i<treex.size();i+=2)
  {
    stroke(map(noise(r+=0.1),0,1,100,255),map(noise(r+=0.1),0,1,0,255),map(noise(r+=0.1),0,1,0,255));
    line(treex.get(i),treey.get(i),treex.get(i+1),treey.get(i+1));
  }
  if(Iteration>4)
  {
    return;
  }
  int count=treex.size();
  for(int i=0;i<branch;i++)
  {
    ext[0][0]=treex.get(count-1-2*i);ext[0][1]=treey.get(count-1-2*i);ext[0][2]=1;
    ext[1][0]=treex.get(count-2-2*i);ext[1][1]=treey.get(count-2-2*i);ext[1][2]=1;
    for(int j=-bm;j<=bm;j+=bm_step)
    {
      Extend(ext,j);
    }
  }
  branch*=2*bm/bm_step+1;//记录加入了多少新的分支
}

可以看到,代码中指定的迭代次数为4次。因为按照程序中的约定,要对每一个分支生长出12个小分支。因此,迭代四次之后,分支总数就已经达到了1728个分支。继续计算数量级将会增加,绘制的效率会大大减小。因此需要约束,即迭代次数达到4次之后,就不再生长,只进行绘制。

向量

在第一个拓展实验中,实际上已经使用到了向量的有关知识,但是实际上是依照这数学原理进行的计算,Processing已经提供了PVector类,用于简单便捷地实现对向量的有关操作。

实验进行前,已经将书上的示例全部尝试了一边,从头到尾体会到了向量和编程艺术以及自然系统的紧密关系。结合向量的有关知识。编写了一个朝向鼠标移动,并且大小随着速度变化的简化池塘鱼群生态系统。

在这里插入图片描述

参考书中的Mover类以及代码1-11中物体朝向鼠标移动的函数,增加了鱼群的在运动的同时,围绕自身群体做圆周运动以及大小随着速度而变化的创新点。
主要函数和书中代码类似,叙述一下几个更改的地方:
(1)单条鱼的加速度:
为了实现鱼群朝向鼠标移动的效果,需要使得鱼群的加速度朝向鼠标方向。但是实际生活中,会存在少量鱼的游动方向始终相反,因此,需要参考第一章的程序,以一定的概率来决定某条鱼的游动方向。

在这里插入图片描述

  void update(){
    PVector mouse=new PVector(mouseX,mouseY);
    PVector dir=PVector.sub(mouse,location);
    dir.normalize();
    dir.mult(0.5);
    acceleration=dir;
    if(random(1)>0.05){
      acceleration.mult(random(1));
    }
    else{
      acceleration.mult(random(-1,0));
    }
    velocity.add(acceleration);
    velocity.limit(10);
    location.add(velocity);
    head.add(velocity);
  }

(2)种群的集体环状游动方向:
为了实现种群的集体环状游动方向,采用的方法是让每一条鱼都绕原点旋转一定的角度,而且这个旋转不会被推送。作用到整个种群上。这样会产生意想不到的效果。

  void display(float angle){
    stroke(0);
    fill(0,100,50);
    rotate(angle);
    ellipse(location.x,location.y,velocity.x,velocity.x);
  }

力与向量的关系比较相近,很多时候是在向量的角度上多考虑的质量的情况,而且在编程世界中,物理量不必严格遵守现实世界中的规范,可以有很多自己的发挥。
按照书中重力、流体阻力、摩擦力、引力的案例,给第二个拓展实验中的每一一条鱼增加了随即质量,并让其受到重力、摩擦力、流体阻力的作用,还在个体之间添加了相互之间的斥力。
当移动到液体区域中时,鱼的游动速度减慢。
在这里插入图片描述添加的各种作用力:‘


  for(int i=0;i<movers.length;i++){
    PVector friction=movers[i].velocity;
    friction.mult(-1);
    friction.normalize();
    friction.mult(c);
    
    float m=movers[i].mass;
    PVector gravity=new PVector(0,1.5*m);
    
    if(movers[i].isInside(liquid)){
      movers[i].drag(liquid);
    }
    movers[i].applyForce(gravity);
    movers[i].applyForce(friction);
    
    movers[i].update();
    movers[i].display();
    movers[i].checkEdges();
  }

添加流体阻力。流体类如下

class Liquid{
  float x,y,w,h;
  float c;
  
  Liquid(float x,float y,float w,float h,float c){
    this.x=x;
    this.y=y;
    this.w=w;
    this.h=h;
    this.c=c;
  }
  
  void display(){
    noStroke();
    fill(175,175,175,100);
    rect(x,y,w,h);
  }
}

对于每一条鱼,都要添加方法,实现流体作用力。并在主函数中调用。


  void drag(Liquid l){
    float speed=velocity.mag();
    float drayMagnitude=l.c*speed*speed;
    
    PVector drag=velocity;
    drag.mult(-1);
    drag.normalize();
    
    drag.mult(drayMagnitude);
    
    applyForce(drag);//应用力
  }

振荡

提到振荡,首先想到的便是三角函数,三角函数反应现实世界,泰勒展开、欧拉公式可以将复杂的函数转化为三角函数。实验开始前尝试了书中的每一个代码。受到启发想到DNA的双螺旋结构,结合OOP思想,编写了一个DNA类,实现绘制DNA的效果。

在这里插入图片描述

代码思路是结合两个正弦波运动,在对应的位置,每隔一段步长,对应连线,DNA类包含三个更新方法,分别是两条正弦波的更新绘制和中间连线的绘制:

  public void update1(){
    //整体的正弦波运动
    float angle=startAngle1;
    float angleStep=0.2;
    float x=0;
    float xStep=10;
    for(int i=0;i<section;i++){
      //计算y
      float y=amplitude*sin(0.8*angle);
      //赋值
      location1[i]=new PVector(x,y);
      location1[i].add(Loc);
      //增加角度与x
      angle+=angleStep;
      x+=xStep;
      startAngle1+=0.0001;
    }
  }
  
    public void update2(){
    //整体的正弦波运动
    float angle=startAngle2;
    float angleStep=0.2;
    float x=0;
    float xStep=10;
    for(int i=0;i<section;i++){
      //计算y
      float y=amplitude*sin(0.8*angle);
      //赋值
      location2[i]=new PVector(x,y);
      location2[i].add(Loc);
      //增加角度与x
      angle+=angleStep;
      x+=xStep;
      startAngle2+=0.0001;
    }
  }
  
  void update3(){
    push();
    translate(Loc.x,Loc.y);
    rotate(rotation);
    scale(size);
    strokeWeight(10);
    float x1,x2,y1,y2;
    for(int i=0;i<section;i+=4){
      x1=location1[i].x;
      x2=location2[i].x;
      y1=location1[i].y;
      y2=location2[i].y;
      line(x1,y1,x2,y2);
    }
    pop();
  }

粒子系统

在本学期的Unity使用中,多次接触到了粒子系统,粒子系统是一个类的嵌套调用,包含了内部的单个粒子以及整体的粒子系统类。粒子系统广泛应用于模拟各种特效,包括烟雾、火焰等等,本次实验主要是针对前几章的经典物理学,对粒子系统添加了粒子之间的排斥力、粒子与鼠标之间的吸引力。实现了可交互式的粒子系统:

在这里插入图片描述主要改进的地方有两个:
(1)参照牛顿运动定律,使得任意两个粒子之间都存在斥力:

Particle类:

  PVector attract(Particle p){
    float G=0.4;
    PVector force=PVector.sub(location,p.location);
    float distance=force.mag();
    distance=constrain(distance,1,10);
    force.normalize();
    float strength=(G)/(distance*distance);
    force.mult(strength);
    return force;
  }

ParticleSystem类:

  void repulsion(){
      for(int i=0;i<particles.size();i++){
    for(int j=0;j<particles.size();j++){
      if(i!=j){
        PVector f=particles.get(j).attract(particles.get(i));
        f.mult(5);
        particles.get(j).applyForce(f);
      }
    }
  }
}

(2)鼠标吸引所有的粒子,而且粒子的质量极小,加速度很大,容易冲过鼠标的位置,然后返回,中途不受任何阻力,形成回旋效果:
如下图所示,左侧粒子为初始方向,粒子被鼠标吸引,超鼠标飞去,由于加速度过大,而且不受阻力,不会停留在中间的理想位置,而是冲到右侧的圆形位置,然后反向冲回……循环往复,形成回旋效果。
在这里插入图片描述


  PVector repel(Particle p) {
    float x=mouseX;
    float y=mouseY;
    PVector mouseLocation=new PVector(x,y);
    PVector dir =PVector.sub(mouseLocation, p.location);
    float d = dir.mag();
    d = constrain(d, 5, 100);
    dir.normalize();
    dir.mult(5);
    return dir;
  } 
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!