《代码本色:用编程模拟自然系统》习作——第3章:振荡

前言

近日拜读了Daniel Shiffman先生的著作——《代码本色:用编程模拟自然系统》,决定做一组习作,来对书中提到的随机行为及牛顿运动学进行理解,并对一些实例进行拓展学习,从而提升自己相关方面的知识水平和实践能力。

《代码本色》第3章概述

在第3章中,作者向我们介绍了三角函数。三角函数会给我们带来很多新工具。本章我们会学习角、角速度以及角加速度,期间还会涉及正弦函数和余弦函数,它们可以用来制作平滑的波形曲线。有了这些知识,我们就能计算更复杂的力,而这些力往往都涉及角度,比如钟摆的摆动和盒子从斜坡滑下时所受的力。作者通过一些实例深入浅出地讲解了这些力,并对这些力进行了分析和实践。

在这一章中,我印象比较深的点是对简谐运动的模拟,此外,还有对钟摆、弹簧的模拟。下面,我将介绍我根据本章内容而创作的Processing编程习作。

习作

成品

可以拉拽绿色点,每两个点之间都有一个弹簧效果的力支撑。此外,在其他地方点击鼠标还可以添加新的点。

创作过程

本次创作的关键在于——如何实现弹簧的效果。对此,书上花了很大篇幅进行介绍。
首先介绍下弹簧的弹力。弹簧的弹力可以根据胡克定律计算得到,胡克定律以英国物理学家罗伯特·胡克命名,他在1660年发明了这个公式。胡克最初是用拉丁文描述这个公式的——“Ut tensio,sic vis”, 这句话的意思是“力如伸长(那样变化)”。我们可以这么理解它:弹簧的弹力与弹簧的伸长量成正比。

弹簧图解1

换个说法,就是我们大家都熟悉的公式:F=-kx。这里的k就是弹性系数,是一个常数。而x是当前长度与静止长度的差值。


弹簧图解2

接下来,我开始编写Spring类(弹簧类)。
首先定义了这三个变量:

  PVector anchor; // 起始位置
  float len;// 静止长度 
  float k = 0.05;//弹性系数

然后,在update()函数里计算弹力:

  // 计算弹力
  void update() {
    PVector force = PVector.sub(a.position, b.position);//矢量指向从a点到b点
    float d = force.mag();//计算向量长度
    float stretch = d - len;//形变长度
    force.normalize();
    force.mult(-1 * k * stretch);  //F=-kx
    a.applyForce(force);
    force.mult(-1);//牛顿第三定律
    b.applyForce(force);
  }

代码中有详细的注释,就不一一解释了。
此外值得一提的是,在Circle类中添加了这样一个变量:

  //削减系数,模拟摩擦力
  float damping = 0.9;

这个变量的作用是为了模拟摩擦力和空气阻力的效果,如果没有这个变量,或者这个变量为1,那么这个运动将不会停止,这不符合我们对现实生活中运动的认知。在Circle类的update函数中,我们将速度向量乘上这个系数:

  void update() { 
...
    velocity.mult(damping);
...
  }

在主程序中,我定义了两个ArrayList,分别用来存储Circle类变量和Spring类变量:

ArrayList<Circle> circles=new ArrayList<Circle>();
ArrayList<Spring> springs=new ArrayList<Spring>();

然后在setup函数里编写了如下代码:

  Circle Circle1 = new Circle(width/2, height/2);
  Circle Circle2 = new Circle(width/2+50, height/2+50);
  circles.add(Circle1);
  circles.add(Circle2);
  Spring Spring = new Spring(circles.get(0), circles.get(1),200);
  springs.add(Spring);

简单来说就是两个Circle连接了一个弹簧,很好懂,这就是面向对象编程思想的优点。随后再进行一些力的应用,添加了鼠标拖拽事件,就可以跑了,我们看看效果:


两个球一个弹簧

其实一开始的效果不是那么理想,然后我对一些参数进行了调整,比如弹性系数k,削减系数damping 等,最后达成了很“Q弹”的效果。

然后我觉得两个球一根弹簧过于单调了,于是做了多个球,以及每个球都会与除自己之外的所有球连接一根弹簧。这样一来就会产生一个很神奇的效果,不管怎么拖拽,最终很可能会回到一个稳定的状态,比如五个的情况:

GIF3.gif

看起来就像一个魔法阵一样,如果用图论的知识来说,这是一个完全图。
最后,我添加了鼠标点击增加点的效果,这个和之前第0章做的相似:

void mouseClicked()
{
   Circle newCircle = new Circle(mouseX, mouseY);
  for(int i=0;i<circles.size();i++)
  {
    Spring newSpring = new Spring(newCircle,circles.get(i),200);
    springs.add(newSpring);
  }
  circles.add(newCircle);
}

以及添加了渐变轨迹的效果:

  fill(255,255,255,50);
  rect(0,0,width,height);

最终效果如下:


成品

最后附上所有代码。
Circle类:


class Circle { 
  PVector position;
  PVector velocity;
  PVector acceleration;
  float mass = 5;
  
  //削减系数,模拟摩擦力
  float damping = 0.9;
  
  PVector dragOffset;//拉动的距离向量
  boolean dragging = false;  // 鼠标是否按下

  Circle(float x, float y) {
    position = new PVector(x,y);
    velocity = new PVector();
    acceleration = new PVector();
    dragOffset = new PVector();
  } 


  void update() { 
    velocity.add(acceleration);
    velocity.mult(damping);
    position.add(velocity);
    acceleration.mult(0);
  }

  void applyForce(PVector force) {
    PVector f = force.get();//牛顿第二定律F=ma
    f.div(mass);
    acceleration.add(f);
  }


  void display() { 
    stroke(0);
    strokeWeight(2);
    fill(#98FFCC);
    if (dragging) {
      fill(#FF9898);
    }
    ellipse(position.x,position.y,mass*5,mass*5);
  } 


  void clicked(int mx, int my) {
    float d = dist(mx,my,position.x,position.y);
    if (d < mass) {
      dragging = true;
      dragOffset.x = position.x-mx;
      dragOffset.y = position.y-my;
    }
  }

  void stopDragging() {
    dragging = false;
  }

  void drag(int mx, int my) {
    if (dragging) {
      position.x = mx + dragOffset.x;
      position.y = my + dragOffset.y;
    }
  }
}

Spring类:


class Spring { 
  PVector anchor; // 起始位置
  float len;// 静止长度 
  float k = 0.05;//弹性系数
  
  Circle a;
  Circle b;

  Spring(Circle a_, Circle b_, int l) {
    a = a_;
    b = b_;
    len = l;
  } 

  // 计算弹力
  void update() {
    PVector force = PVector.sub(a.position, b.position);//矢量指向从a点到b点
    float d = force.mag();//计算向量长度
    float stretch = d - len;//形变长度
    force.normalize();
    force.mult(-1 * k * stretch);  //F=-kx
    a.applyForce(force);
    force.mult(-1);//牛顿第三定律
    b.applyForce(force);
  }


  void display() {
    strokeWeight(2);
    stroke(0);
    line(a.position.x, a.position.y, b.position.x, b.position.y);
  }
}

主程序类:

//Circle[] circle = new Circle[5];
//Spring[] springs = new Spring[20];
ArrayList<Circle> circles=new ArrayList<Circle>();
ArrayList<Spring> springs=new ArrayList<Spring>();

void setup() {
  size(600, 600);

  for (int i = 0; i < 5; i++) {
    Circle newCircle = new Circle(width/2+i*100-200, height/2+i*100-200);
    circles.add(newCircle);
  }
  for (int i = 0; i < circles.size(); i++) {
     for (int j = 0; j< circles.size(); j++) {
        if(i!=j) {
          Spring newSpring = new Spring(circles.get(i), circles.get(j),200);
          springs.add(newSpring);
       }  
   }
  }
  //Circle Circle1 = new Circle(width/2, height/2);
  //Circle Circle2 = new Circle(width/2+50, height/2+50);
  //circles.add(Circle1);
  //circles.add(Circle2);
  //Spring Spring = new Spring(circles.get(0), circles.get(1),200);
  //springs.add(Spring);
}

void draw() {
  fill(255,255,255,50);
  rect(0,0,width,height);
  for (Spring s : springs) {
    s.update();
    s.display();
  }


  for (Circle b : circles) {
    b.update();
    b.display();
    b.drag(mouseX, mouseY);
  }
  
 
}
void mouseClicked()
{
   Circle newCircle = new Circle(mouseX, mouseY);
  for(int i=0;i<circles.size();i++)
  {
    Spring newSpring = new Spring(newCircle,circles.get(i),200);
    springs.add(newSpring);
  }
  circles.add(newCircle);
}


void mousePressed() {
  for (Circle b : circles) {
    b.clicked(mouseX, mouseY);
  }
}

void mouseReleased() {
  for (Circle b : circles) {
    b.stopDragging();
  }
}

总结

本文介绍了《代码本色:用编程模拟自然系统》第3章的主要内容,并在示例的基础上进行了拓展性的创作,创作了弹簧的模型并进行了拓展。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350