17.3 复杂性理论

下面要介绍的程序的前身是由Larry O’Brien原创的一些代码,并以由Craig Reynolds于1986年编制的“Boids”程序为基础,当时是为了演示复杂性理论的一个特殊问题,名为“凸显”(Emergence)。

这儿要达到的目标是通过为每种动物都规定少许简单的规则,从而逼真地再现动物的群聚行为。每个动物都能看到看到整个环境以及环境中的其他动物,但它只与一系列附近的“群聚伙伴”打交道。动物的移动基于三个简单的引导行为:

(1) 分隔:避免本地群聚伙伴过于拥挤。

(2) 方向:遵从本地群聚伙伴的普遍方向。

(3) 聚合:朝本地群聚伙伴组的中心移动。

更复杂的模型甚至可以包括障碍物的因素,动物能预知和避免与障碍冲突的能力,所以它们能围绕环境中的固定物体自由活动。除此以外,动物也可能有自己的特殊目标,这也许会造成群体按特定的路径前进。为简化讨论,避免障碍以及目标搜寻的因素并未包括到这里建立的模型中。

尽管计算机本身比较简陋,而且采用的规则也相当简单,但结果看起来是真实的。也就是说,相当逼真的行为从这个简单的模型中“凸显”出来了。

程序以组合到一起的应用程序/程序片的形式提供:

  1. //: FieldOBeasts.java
  2. // Demonstration of complexity theory; simulates
  3. // herding behavior in animals. Adapted from
  4. // a program by Larry O'Brien lobrien@msn.com
  5. import java.awt.*;
  6. import java.awt.event.*;
  7. import java.applet.*;
  8. import java.util.*;
  9. class Beast {
  10. int
  11. x, y, // Screen position
  12. currentSpeed; // Pixels per second
  13. float currentDirection; // Radians
  14. Color color; // Fill color
  15. FieldOBeasts field; // Where the Beast roams
  16. static final int GSIZE = 10; // Graphic size
  17. public Beast(FieldOBeasts f, int x, int y,
  18. float cD, int cS, Color c) {
  19. field = f;
  20. this.x = x;
  21. this.y = y;
  22. currentDirection = cD;
  23. currentSpeed = cS;
  24. color = c;
  25. }
  26. public void step() {
  27. // You move based on those within your sight:
  28. Vector seen = field.beastListInSector(this);
  29. // If you're not out in front
  30. if(seen.size() > 0) {
  31. // Gather data on those you see
  32. int totalSpeed = 0;
  33. float totalBearing = 0.0f;
  34. float distanceToNearest = 100000.0f;
  35. Beast nearestBeast =
  36. (Beast)seen.elementAt(0);
  37. Enumeration e = seen.elements();
  38. while(e.hasMoreElements()) {
  39. Beast aBeast = (Beast) e.nextElement();
  40. totalSpeed += aBeast.currentSpeed;
  41. float bearing =
  42. aBeast.bearingFromPointAlongAxis(
  43. x, y, currentDirection);
  44. totalBearing += bearing;
  45. float distanceToBeast =
  46. aBeast.distanceFromPoint(x, y);
  47. if(distanceToBeast < distanceToNearest) {
  48. nearestBeast = aBeast;
  49. distanceToNearest = distanceToBeast;
  50. }
  51. }
  52. // Rule 1: Match average speed of those
  53. // in the list:
  54. currentSpeed = totalSpeed / seen.size();
  55. // Rule 2: Move towards the perceived
  56. // center of gravity of the herd:
  57. currentDirection =
  58. totalBearing / seen.size();
  59. // Rule 3: Maintain a minimum distance
  60. // from those around you:
  61. if(distanceToNearest <=
  62. field.minimumDistance) {
  63. currentDirection =
  64. nearestBeast.currentDirection;
  65. currentSpeed = nearestBeast.currentSpeed;
  66. if(currentSpeed > field.maxSpeed) {
  67. currentSpeed = field.maxSpeed;
  68. }
  69. }
  70. }
  71. else { // You are in front, so slow down
  72. currentSpeed =
  73. (int)(currentSpeed * field.decayRate);
  74. }
  75. // Make the beast move:
  76. x += (int)(Math.cos(currentDirection)
  77. * currentSpeed);
  78. y += (int)(Math.sin(currentDirection)
  79. * currentSpeed);
  80. x %= field.xExtent;
  81. y %= field.yExtent;
  82. if(x < 0)
  83. x += field.xExtent;
  84. if(y < 0)
  85. y += field.yExtent;
  86. }
  87. public float bearingFromPointAlongAxis (
  88. int originX, int originY, float axis) {
  89. // Returns bearing angle of the current Beast
  90. // in the world coordiante system
  91. try {
  92. double bearingInRadians =
  93. Math.atan(
  94. (this.y - originY) /
  95. (this.x - originX));
  96. // Inverse tan has two solutions, so you
  97. // have to correct for other quarters:
  98. if(x < originX) {
  99. if(y < originY) {
  100. bearingInRadians += - (float)Math.PI;
  101. }
  102. else {
  103. bearingInRadians =
  104. (float)Math.PI - bearingInRadians;
  105. }
  106. }
  107. // Just subtract the axis (in radians):
  108. return (float) (axis - bearingInRadians);
  109. } catch(ArithmeticException aE) {
  110. // Divide by 0 error possible on this
  111. if(x > originX) {
  112. return 0;
  113. }
  114. else
  115. return (float) Math.PI;
  116. }
  117. }
  118. public float distanceFromPoint(int x1, int y1){
  119. return (float) Math.sqrt(
  120. Math.pow(x1 - x, 2) +
  121. Math.pow(y1 - y, 2));
  122. }
  123. public Point position() {
  124. return new Point(x, y);
  125. }
  126. // Beasts know how to draw themselves:
  127. public void draw(Graphics g) {
  128. g.setColor(color);
  129. int directionInDegrees = (int)(
  130. (currentDirection * 360) / (2 * Math.PI));
  131. int startAngle = directionInDegrees -
  132. FieldOBeasts.halfFieldOfView;
  133. int endAngle = 90;
  134. g.fillArc(x, y, GSIZE, GSIZE,
  135. startAngle, endAngle);
  136. }
  137. }
  138. public class FieldOBeasts extends Applet
  139. implements Runnable {
  140. private Vector beasts;
  141. static float
  142. fieldOfView =
  143. (float) (Math.PI / 4), // In radians
  144. // Deceleration % per second:
  145. decayRate = 1.0f,
  146. minimumDistance = 10f; // In pixels
  147. static int
  148. halfFieldOfView = (int)(
  149. (fieldOfView * 360) / (2 * Math.PI)),
  150. xExtent = 0,
  151. yExtent = 0,
  152. numBeasts = 50,
  153. maxSpeed = 20; // Pixels/second
  154. boolean uniqueColors = true;
  155. Thread thisThread;
  156. int delay = 25;
  157. public void init() {
  158. if (xExtent == 0 && yExtent == 0) {
  159. xExtent = Integer.parseInt(
  160. getParameter("xExtent"));
  161. yExtent = Integer.parseInt(
  162. getParameter("yExtent"));
  163. }
  164. beasts =
  165. makeBeastVector(numBeasts, uniqueColors);
  166. // Now start the beasts a-rovin':
  167. thisThread = new Thread(this);
  168. thisThread.start();
  169. }
  170. public void run() {
  171. while(true) {
  172. for(int i = 0; i < beasts.size(); i++){
  173. Beast b = (Beast) beasts.elementAt(i);
  174. b.step();
  175. }
  176. try {
  177. thisThread.sleep(delay);
  178. } catch(InterruptedException ex){}
  179. repaint(); // Otherwise it won't update
  180. }
  181. }
  182. Vector makeBeastVector(
  183. int quantity, boolean uniqueColors) {
  184. Vector newBeasts = new Vector();
  185. Random generator = new Random();
  186. // Used only if uniqueColors is on:
  187. double cubeRootOfBeastNumber =
  188. Math.pow((double)numBeasts, 1.0 / 3.0);
  189. float colorCubeStepSize =
  190. (float) (1.0 / cubeRootOfBeastNumber);
  191. float r = 0.0f;
  192. float g = 0.0f;
  193. float b = 0.0f;
  194. for(int i = 0; i < quantity; i++) {
  195. int x =
  196. (int) (generator.nextFloat() * xExtent);
  197. if(x > xExtent - Beast.GSIZE)
  198. x -= Beast.GSIZE;
  199. int y =
  200. (int) (generator.nextFloat() * yExtent);
  201. if(y > yExtent - Beast.GSIZE)
  202. y -= Beast.GSIZE;
  203. float direction = (float)(
  204. generator.nextFloat() * 2 * Math.PI);
  205. int speed = (int)(
  206. generator.nextFloat() * (float)maxSpeed);
  207. if(uniqueColors) {
  208. r += colorCubeStepSize;
  209. if(r > 1.0) {
  210. r -= 1.0f;
  211. g += colorCubeStepSize;
  212. if( g > 1.0) {
  213. g -= 1.0f;
  214. b += colorCubeStepSize;
  215. if(b > 1.0)
  216. b -= 1.0f;
  217. }
  218. }
  219. }
  220. newBeasts.addElement(
  221. new Beast(this, x, y, direction, speed,
  222. new Color(r,g,b)));
  223. }
  224. return newBeasts;
  225. }
  226. public Vector beastListInSector(Beast viewer) {
  227. Vector output = new Vector();
  228. Enumeration e = beasts.elements();
  229. Beast aBeast = (Beast)beasts.elementAt(0);
  230. int counter = 0;
  231. while(e.hasMoreElements()) {
  232. aBeast = (Beast) e.nextElement();
  233. if(aBeast != viewer) {
  234. Point p = aBeast.position();
  235. Point v = viewer.position();
  236. float bearing =
  237. aBeast.bearingFromPointAlongAxis(
  238. v.x, v.y, viewer.currentDirection);
  239. if(Math.abs(bearing) < fieldOfView / 2)
  240. output.addElement(aBeast);
  241. }
  242. }
  243. return output;
  244. }
  245. public void paint(Graphics g) {
  246. Enumeration e = beasts.elements();
  247. while(e.hasMoreElements()) {
  248. ((Beast)e.nextElement()).draw(g);
  249. }
  250. }
  251. public static void main(String[] args) {
  252. FieldOBeasts field = new FieldOBeasts();
  253. field.xExtent = 640;
  254. field.yExtent = 480;
  255. Frame frame = new Frame("Field 'O Beasts");
  256. // Optionally use a command-line argument
  257. // for the sleep time:
  258. if(args.length >= 1)
  259. field.delay = Integer.parseInt(args[0]);
  260. frame.addWindowListener(
  261. new WindowAdapter() {
  262. public void windowClosing(WindowEvent e) {
  263. System.exit(0);
  264. }
  265. });
  266. frame.add(field, BorderLayout.CENTER);
  267. frame.setSize(640,480);
  268. field.init();
  269. field.start();
  270. frame.setVisible(true);
  271. }
  272. } ///:~

尽管这并非对Craig Reynold的“Boids”例子中的行为完美重现,但它却展现出了自己独有的迷人之外。通过对数字进行调整,即可进行全面的修改。至于与这种群聚行为有关的更多的情况,大家可以访问Craig Reynold的主页——在那个地方,甚至还提供了Boids一个公开的3D展示版本:

http://www.hmt.com/cwr/boids.html

为了将这个程序作为一个程序片运行,请在HTML文件中设置下述程序片标志:

  1. <applet
  2. code=FieldOBeasts
  3. width=640
  4. height=480>
  5. <param name=xExtent value = "640">
  6. <param name=yExtent value = "480">
  7. </applet>