融入动画技术的交互媒体应用

本次基于processing创作了一个应用,主要参考书籍为《代码本色》

主要应用的技术为下面三部分:

  • 粒子系统
  • 分形

首先简单介绍一下这个系统

1. 整个主体是一棵随风摆动的树,你可以通过点击空白处来得到不同形状的独一无二的树

 

关于分形,首先需要了解,分形的两个重要特征:自相似性、递归。

首先比较简单的一种分型树规则是:

1. 画一段线段

2. 在线段末尾(a)向左旋转画一条更短的线段 (b)向右旋转画一条更短的线段

3. 不断地在新线段上重复步骤二

4.设置递归终止条件

这样那你就可以构建一棵树枝是线段的简单分型树。那么在分形树中加入一点点随机性就可以使它的外形更贴近自然。查看一棵树的外形,你 会发现每根树枝的角度和长度都不相同,此外,每根树枝的分支数量也不相同。简单地改变树枝的角度和长度很容易实现,只要在绘制时获取一个随机数即可。你也可以选择一个随机数作为树枝的数量,只需改变branch()函数的调用次数。

同时为了获得更逼真的树,请可以用Perlin噪声算法确定树枝的长度。随时间调整噪声值,同时可以用toxiclibs模拟树的物理效果,每一根树枝都是用弹簧相连的两个粒子,这样就可以获得树木被风吹动的效果。

其中Perlin噪声算法表现出了一定的自然性,因为它能生成符合自然排序(“平滑”)的伪随机数序列。Processing内置了Perlin噪声算法的实现:noise()函数。noise()函数可以有1~3个参数,分别代表一维、二维和三维的随机数。Processing的noise()函数告诉我们噪声是通过几个“八度”计算出来的。调用noiseDetail()函数会改变“八度”的数量以及各个八度的重要性,这反过来会影响noise()函数的行为。

关于Perlin噪声不作更多详谈,指路传送门:

不只是噪声,更是数学美 ---浅谈Perlin Noise

不只是噪音

 

代码如下:

class branch 
{
  PVector[] location;
  float[] thickness;
  int[][] baseIndex = new int[2][];
//  float[] baseDtheta;
  boolean isCandidate = false;
  float[] dTheta;
  
  branch(PVector loc, float thic, int id, int branchIndex)
  {
    location = new PVector[1];
    thickness = new float[1];
    location[0] = new PVector(loc.x, loc.y);
    thickness[0] = thic;
    
    baseIndex[0] = new int[1];
    baseIndex[1] = new int[1];
    baseIndex[0][0] = id;
    baseIndex[1][0] = branchIndex;
    
  }
  void branchRotate(int index, float theta, PVector reference)
  {
      location[index].sub(reference);
      rotate2D(location[index], theta);
      location[index].add(reference);
  }
  
  void rotate2D(PVector v, float theta)
  {
    float xTemp = v.x;
    v.x = v.x * cos(theta) - v.y * sin(theta);
    v.y = xTemp * sin(theta) + v.y * cos(theta);
  }
}
class frontier 
{
  PVector location;
  PVector velocity;
  float thickness;
  boolean finished;
  
  
  frontier(PVector startPoint, PVector direction)
  {
    location = new PVector(startPoint.x, startPoint.y);
    velocity = new PVector(direction.x, direction.y);
    thickness = random(10, 30);
    finished = false;
  }
  
  frontier(frontier parent) 
  {
    location = parent.location.get();
    velocity = parent.velocity.get();
    thickness = parent.thickness;
    parent.thickness = thickness;
    finished = parent.finished;
  }
  
  void update(float factor) 
  {
    if(  location.x > -10 
       & location.y > -10
       & location.x < width + 10
       & location.y < height + 10 
       & thickness > factor)  
    {
      velocity.normalize();
      PVector uncertain = new PVector(random(-1, 1), random(-1, 1));
      uncertain.normalize();
      uncertain.mult(0.2);
      velocity.mult(0.8);
      velocity.add(uncertain);
      velocity.mult(random(8, 15));
      location.add(velocity);
    }
    
    else
    {
      finished = true;
    }
   
  } // void update()
  
}
class tree 
{
  PVector[] map;
  branch[] twig;
  int treeSize;
  float BranchLengthFactor = 0.3;
  float BranchLocationFactor = 0.3;

  float dt = 0.025;
  float time = 0;
  float[] dtheta;
  
  int candNum = 3;
  int[] candidateIndex = new int[candNum];
  float[] amplitude = new float[candNum];
  float[] phaseFactor = new float[candNum];
  float freq;
  float period;
   
  tree(PVector startPoint, PVector direction)
  { 
    int id = 0;
    boolean growth = false;
    
    frontier[] fr = new frontier[1];
    fr[id] = new frontier(startPoint, direction);
    
    twig = new branch[1];    
    twig[id] = new branch(fr[id].location, fr[id].thickness, id, 0);
    
    map = new PVector[1];
    map[0] = new PVector(id, twig[id].location.length - 1);
    
    while(!growth)
    {
      int growthSum = 0;
      for(id = 0; id < fr.length; id++)
      {
        fr[id].update(BranchLocationFactor);
        if(!fr[id].finished)
        {
          twig[id].location = (PVector[]) append(twig[id].location, new PVector(fr[id].location.x, fr[id].location.y)); 
          twig[id].thickness = (float[]) append(twig[id].thickness, fr[id].thickness);
          map = (PVector[]) append(map, new PVector(id, twig[id].location.length - 1));
          
          if (random(0, 1) < BranchLengthFactor)  // control length of one branch  
          { 
            
            fr[id].thickness *= 0.65;
            twig[id].thickness[twig[id].thickness.length - 1] = fr[id].thickness;
            if( fr[id].thickness > BranchLocationFactor)  // control the number of the locations on all branches, i.e., treeSize.
            {
              fr = (frontier[]) append(fr, new frontier(fr[id]));           
              twig = (branch[]) append(twig, new branch(fr[id].location, fr[id].thickness, id, twig[id].location.length - 1));
              int _id = id;
              if(_id != 0)  for(int _i = 0; _i < 2; _i++)  twig[twig.length - 1].baseIndex[_i] = concat(twig[twig.length - 1].baseIndex[_i], twig[_id].baseIndex[_i]);
            }
            
          } // if (random(0, 1) < 0.2)
          
        }
        else  growthSum += 1;
      }
      if(growthSum == fr.length) 
      {
        dtheta = new float[twig.length];
        treeSize = map.length;
        growth = true;
      }
    } // while(!growth)
    
    ArrayList _candList = new ArrayList();
    float[] _candfloat = new float[twig.length];
    for(int i = 0; i < twig.length; i++)
    {
      _candfloat[i] = (float)twig[i].location.length;
      _candList.add(_candfloat[i]);
    }
    candidateIndex[0] = 0;
    twig[0].isCandidate = true;
    twig[0].dTheta = new float[twig[0].location.length];
    _candfloat[0] = -1.0;
    _candList.set(0, -1.0);
    for(int i = 1; i < candNum; i++) 
    {
      float _temp = max(_candfloat);
      candidateIndex[i] = _candList.indexOf(_temp);
      twig[candidateIndex[i]].isCandidate = true;
      twig[candidateIndex[i]].dTheta = new float[twig[candidateIndex[i]].location.length];
      _candfloat[candidateIndex[i]] = -1.0;
      _candList.set(candidateIndex[i], -1.0);
    }
//    println(candidateIndex);
      
    amplitude[0] = random(0.006, 0.012);
    phaseFactor[0] = random(0.6, 1.2);
    freq = random(0.5, 0.8);
    period = 1 / freq;
    for(int i = 1; i < candNum; i++)
    {
      amplitude[i] = amplitude[i-1] * random(0.9, 1.4);
      phaseFactor[i] = phaseFactor[i-1] * random(0.9, 1.4);
    }
  }
// shu de zhi gan  
  void swing()
  {
    for(int i = 0; i < candNum; i++)
    {
      int _num = twig[candidateIndex[i]].location.length;
      for(int j = 0; j < _num; j++)  twig[candidateIndex[i]].dTheta[j] = amplitude[i] * dt * TWO_PI * freq * cos(TWO_PI * freq * time - phaseFactor[i] * PI * (float)j / (float)_num);
    }

    for(int id = 0; id < twig.length; id++)
    {
      if(twig[id].isCandidate)  for(int _id = 1; _id < twig[id].location.length; _id++)  twig[id].branchRotate(_id, twig[id].dTheta[_id], twig[id].location[0]);
 
      for(int j = 0; j < twig[id].baseIndex[0].length; j++)
      {
        if(!twig[twig[id].baseIndex[0][j]].isCandidate | id == 0) continue;
        else
        {
          for(int k = (id == 0) ? 1 : 0; k < twig[id].location.length; k++)
          {
            twig[id].branchRotate(k, twig[twig[id].baseIndex[0][j]].dTheta[twig[id].baseIndex[1][j]], twig[twig[id].baseIndex[0][j]].location[0]);
          }
        }
      }

    } // for(int id = 0; id < twig.length; id++)

    time += dt;
    if(time >= period) time -= period;    
  }
  
}

2. 页面左下角设有三个按钮,第一个实现从白天向夜晚的转变

 融入动画技术的交互媒体应用_第1张图片

这里就是在draw函数中改变了background的值。

3. 点击第二个按钮在屏幕上绘制自动绽放的五瓣花

花花代码如下:

void flower(float r, float c, float petalCount, float circleCount, float  maxRad, float minRad, float frac, float rot) {  
  float rad = 0;
  noStroke();

  pushMatrix();
  translate(width/2, height/2);  
  for (int j = 0; j< petalCount; j++)       
    for (float i = c; i <= circleCount; i = i + 1) {
      float tt = i/circleCount;
      float x  = r*tt*cos( tt*rot + (2*PI*j)/petalCount-PI/2);
      float y  = r*tt*sin( tt*rot + (2*PI*j)/petalCount-PI/2);

      if (i < frac*circleCount) 
        rad = map(i, 0, frac*circleCount, minRad, maxRad);
      else 
      rad = map(i, frac*circleCount, circleCount, maxRad, minRad);
      fill(lerpColor(color(255*t, 255, 0, 50), color(250*t + 205, 127*(1-t), 0, 100), i/circleCount));      
      ellipse(x+200, y-150, 2*rad, 2*rad);
      ellipse(x-200, y-50, 2*rad, 2*rad);
    }
  popMatrix();
}

 

4. 点击第三个按钮实现“下雨”

关于下雨,其实就是一个最简单的粒子系统。关于粒子系统做简单描述。

“实现粒子系统,我们要先实现一个类,这个类用于表示单个粒子。粒子就是在屏 幕中移动的对象,它有位置、速度和加速度变量,有构造函数用于内部变量的初始 化,有display()函数用于绘制自身,还有update()函数用于更新位置。 ”

“在简单粒子的基础上,我们可以继续完善这个粒子类:可以在类中加 入applyForce()函数用于影响粒子的行为;可以加 入其他变量用于描述粒子的色彩和形状,或是用PImage对象绘制粒子。其中很关键的一点是我们需要设置粒子的生存期(lifespan)。 ”

因为如果不设置生命周期,那么粒子将会不断堆积然后会影响系统运行速度,因此我们需要新的粒子诞生也需要旧粒子消亡。

有了lifespan变量之后,我们还需要添加另一个函数,它返回一个布尔值,用于检 查粒子是否已经消亡。这个函数将在粒子系统类的实现中派上大用场。粒子系统的主 要职责是管理粒子的列表。这个函数的实现非常简单,我们只需要检查lifespan变 量是否小于0:如果小于0,就返回true;如果不小于0,就返回false。 

代码如下:

class Particle {

  PVector location;

  PVector velocity;

  PVector acceleration;

 

  float lifespan;

  Particle(PVector l) {

    // The acceleration

    acceleration = new PVector(0, 0.05);

    // circel's x and y ==> range

    velocity = new PVector(random(-1, 1), random(-2, 0));

    // apawn's position

    location = l.copy();

    // the circle life time

    lifespan = 255.0;

  }

  void run() {

    update();

    display();

  }

  void update() {

    velocity.add(acceleration);

    location.add(velocity);

    lifespan-=1.0;

  }

 

  boolean isDead() {

    if (lifespan <= 0) {

      return true;

    } else {

      return false;

    }

  }

  void display() {

    // border

    stroke(0, lifespan);

    // border's weight

    strokeWeight(1);

    float r = random(0,255);

    float g = random(0,255);

    float b = random(0,255);

    // random the circle's color

    fill(125,209,240, lifespan);

    // draw circle

    ellipse(location.x, location.y-70, 5, 5);
    ellipse(location.x-250, location.y-70, 5, 5);
    ellipse(location.x+250, location.y-70, 5, 5);
  }

}
// A class to describe a group of Particles

// An ArrayList is used to manage the list of Particles 

 

class ParticleSystem {

  ArrayList particles;

  PVector origin;

 

  ParticleSystem(PVector position) {

    origin = position.copy();

    particles = new ArrayList();

  }

 

  void addParticle() {

    particles.add(new Particle(origin));

  }

 

  void run() {

    for (int i = particles.size()-1; i >= 0; i--) {

      Particle p = particles.get(i);

      p.run();

      if (p.isDead()) {

        particles.remove(i);

      }

    }

  }

}

 

推荐:

1.第一个推荐是用Processing制作一个「生态瓶」

创作者讲解的比较详细,并且界面具有美观性,也比较有趣味,主要运用了随机分布、向量、粒子系统和细胞自动机的相关知识。在一个【容器内】,你可以充当上帝的角色,对这一区域内的生态环境进行模拟。

2.第二个推荐是https://blog.csdn.net/qq_42123360/article/details/89893547

本篇博文灵感来源于新媒体艺术展览,展示了7中视觉效果展示图,分别是旋转万花筒、广袤星河、记忆碎片、方格眩晕、迷幻绳索、电子冲击、塌缩空间。并且可以给这些效果添加不同的滤镜。设计的动态效果是富有创意的。主要运用了噪声、角运动和随机的内容。

3.第三个推荐是《Gravity》——融入动画技术的交互应用

这个交互作品是一个太空模拟驾驶游戏的原型,使用openFrameworks配合XInput库,并使用C++编写。在游戏中,玩家使用XBOX One游戏手柄控制一架抽象为箭头的小飞机,在行星之间飞行,收集燃料以维持飞船的动力。玩家通过推动手柄的左摇杆来控制飞船的动力和航向,随着玩家推动摇杆,动力逐渐提升,并推动飞船加速或者减速。具体请移步链接。

 

你可能感兴趣的:(交互媒体)