// Flocklet by Jim White // Boid code based on SchoolView (Objective-C) for Next by David Lambert. import java.applet.Applet; import java.awt.*; import java.awt.image.*; import java.lang.Math; public class Flocklet extends Applet implements Runnable { Rectangle screenBounds; Image viewImage; Graphics viewGC; int viewWidth; int viewHeight; Thread workThread; int cycleCount; boolean wrap_around; int updateRate; // Boid flocking control variables. // Same as in SchoolView.m. int boidCount; int goalChangeFreq; float distExp; float momentum; float minRadius; float maxVel; float minVel; float accLimit; float avoidFact; float matchFact; float centerFact; float targetFact; int boid_shape; static final int boidPie = 0; static final int boidSquare = 1; static final int boidShip = 2; static final int boidBee = 3; static final int boidCustom = 4; int boid_size; int boid_radius; int boid_x_size; int boid_y_size; boolean boidChangesColor; Color boidColor = Color.black; boolean followMouse; long lastPaint = 0; boolean goal_is_mouse; int mouse_x; int mouse_y; // If the goal is a boid then boid[0] is the goal. boolean goal_is_boid; float goal_x; float goal_y; float boid_x[]; float boid_y[]; float boid_xv[]; float boid_yv[]; float boid_xa[]; float boid_ya[]; byte boid_frame[]; int imageAngleCount; int imageFrameCount; Image boidImages[][]; Image backgroundImage; Image goalImage; public String[][] getParameterInfo() { String[][] info = { {"count", "int", "number in flock"}, {"updaterate", "millis", "position update rate"}, {"boidsize", "pixels", "size of boid"}, {"boidshape", "string", "pie square"}, {"changecolor", "boolean", "boids change color"}, {"goalchange", "int", "goal change frequency"}, {"followmouse", "boolean", "flock follows the mouse"}, {"chaseboid", "boolean", "flock chases a boid"}, {"wraparound", "boolean", "boids wrap around the edges"}, {"distExp", "float", "distance exp"}, {"momentum", "float", "momentum"}, {"minRadius", "float", "minimum radius"}, {"maxVel", "float", "maximum velocity"}, {"minVel", "float", "minimum velocity"}, {"accelLimit", "float", "acceleration limit"}, {"avoidance", "float", "avoidance factor"}, {"matching", "float", "matching factor"}, {"centering", "float", "centering factor"}, {"targeting", "float", "targeting factor"}, }; return info; } float getFloatParameter(String name, float defaultValue) { String param = getParameter(name); float value; try { value = (param != null) ? Float.valueOf(param).floatValue() : defaultValue; } catch (NumberFormatException e) { value = defaultValue; } return value; } public void init() { { String param; param = getParameter("count"); boidCount = (param != null) ? Integer.parseInt(param) : 20; param = getParameter("updaterate"); updateRate = (param != null) ? Integer.parseInt(param) : 75; param = getParameter("goalchange"); goalChangeFreq = (param != null) ? Integer.parseInt(param) : 50; param = getParameter("boidsize"); boid_size = (param != null) ? Integer.parseInt(param) : 8; param = getParameter("changecolor"); boidChangesColor = (param == null) ? true : (param.equalsIgnoreCase("yes") || param.equalsIgnoreCase("true")); param = getParameter("followmouse"); followMouse = (param == null) ? true : (param.equalsIgnoreCase("yes") || param.equalsIgnoreCase("true")); param = getParameter("chaseboid"); goal_is_boid = (param == null) ? true : (param.equalsIgnoreCase("yes") || param.equalsIgnoreCase("true")); param = getParameter("wraparound"); wrap_around = (param == null) ? true : (param.equalsIgnoreCase("yes") || param.equalsIgnoreCase("true")); param = getParameter("boidshape"); if ((param != null) && (param.equalsIgnoreCase("square"))) { boid_shape = boidSquare; } else if ((param != null) && (param.equalsIgnoreCase("bee"))) { boid_shape = boidBee; } else if ((param != null) && (param.equalsIgnoreCase("ship"))) { boid_shape = boidShip; } else /* if ((param == null) || (param.equalsIgnoreCase("pie")) */ { boid_shape = boidPie; boid_size *= 2; } } distExp = getFloatParameter("distExp", 3.0f); momentum = getFloatParameter("momentum", 0.05f); minRadius = getFloatParameter("minRadius", 60.0f); maxVel = getFloatParameter("maxVel", 18.0f); minVel = getFloatParameter("minVel", 0.0f); accLimit = getFloatParameter("accelLimit", 5.0f); avoidFact = getFloatParameter("avoidance", 10.0f); matchFact = getFloatParameter("matching", 0.7f); centerFact = getFloatParameter("centering", 13.0f); targetFact = getFloatParameter("targeting", 10.0f); screenBounds = bounds(); viewImage = createImage(screenBounds.width, screenBounds.height); viewGC = viewImage.getGraphics(); viewWidth = screenBounds.width /* - boid_size */ ; viewHeight = screenBounds.height /* - boid_size */ ; boid_x_size = boid_size; boid_y_size = boid_size; boid_radius = boid_size / 2; boid_x = new float[boidCount]; boid_y = new float[boidCount]; boid_xv = new float[boidCount]; boid_yv = new float[boidCount]; boid_xa = new float[boidCount]; boid_ya = new float[boidCount]; boid_frame = new byte[boidCount]; for (int i = 0; i < boidCount; ++i) { boid_x[i] = (float) Math.random() * viewWidth; boid_y[i] = (float) Math.random() * viewHeight; boid_xv[i] = 2 * ((float) Math.random() - 0.5f) * maxVel; boid_yv[i] = 2 * ((float) Math.random() - 0.5f) * maxVel; boid_xa[i] = 0; boid_ya[i] = 0; boid_frame[i] = 0; } // The first call to computeAccelerations will choose a goal point. goal_x = 0; goal_y = 0; cycleCount = 0; goal_is_mouse = false; // init_shipImages(); // init_beeImages(); } void init_beeImages() { imageAngleCount = 16; imageFrameCount = 1; boidImages = new Image[imageAngleCount][imageFrameCount]; Image bees = getImage(getCodeBase(), "bees.gif"); backgroundImage = getImage(getCodeBase(), "sky.gif"); goalImage = getImage(getCodeBase(), "flower.gif"); try { { MediaTracker tracker = new MediaTracker(this); tracker.addImage(bees, 1); tracker.waitForID(1); } boid_x_size = bees.getWidth(this); boid_y_size = boid_x_size; boid_size = boid_x_size; boid_radius = boid_size / 2; MediaTracker tracker = new MediaTracker(this); tracker.addImage(backgroundImage, 2); tracker.addImage(goalImage, 2); ImageProducer im_source = bees.getSource(); for (int i = 0; i < imageAngleCount; ++i) { for (int j = 0; j < imageFrameCount; ++j) { int y_pos = (imageAngleCount - i) % imageAngleCount; // y_pos = (y_pos - (imageAngleCount / 4)) % imageAngleCount; CropImageFilter im_filter = new CropImageFilter (0, y_pos * boid_size, boid_size, boid_size); ImageProducer im_producer = new FilteredImageSource(im_source, im_filter); boidImages[i][j] = createImage(im_producer); tracker.addImage(boidImages[i][j], 3); } } tracker.waitForAll(); } catch (InterruptedException e) { System.out.println("No bees!"); return; } } void init_shipImages() { imageAngleCount = 32; imageFrameCount = 10; boidImages = new Image[imageAngleCount][imageFrameCount]; Image ships = getImage(getCodeBase(), "ships.gif"); // backgroundImage = getImage(getCodeBase(), "stars.gif"); // goalImage = getImage(getCodeBase(), "flower.gif"); try { { MediaTracker tracker = new MediaTracker(this); tracker.addImage(ships, 1); // tracker.addImage(backgroundImage, 2); // tracker.addImage(goalImage, 2); tracker.waitForID(1); } boid_x_size = ships.getWidth(this) / (imageAngleCount * imageFrameCount); boid_y_size = ships.getHeight(this); boid_size = boid_x_size; boid_radius = boid_size / 2; MediaTracker tracker = new MediaTracker(this); ImageProducer im_source = ships.getSource(); for (int i = 0; i < imageAngleCount; ++i) { for (int j = 0; j < imageFrameCount; ++j) { int x_pos = (imageAngleCount - i) % imageAngleCount; // x_pos = (x_pos + (imageAngleCount / 4)) % imageAngleCount; x_pos *= imageFrameCount; x_pos += j; x_pos = (x_pos + 1) % (imageAngleCount * imageFrameCount); CropImageFilter im_filter = new CropImageFilter( x_pos * boid_x_size, 0, boid_x_size, boid_y_size); ImageProducer im_producer = new FilteredImageSource(im_source, im_filter); boidImages[i][j] = createImage(im_producer); tracker.addImage(boidImages[i][j], 3); } } tracker.waitForAll(); } catch (InterruptedException e) { System.out.println("No bees!"); return; } for (int i = 0; i < boidCount; i++) boid_frame[i] = (byte) (i % imageFrameCount); } public boolean mouseEnter(Event evt, int x, int y) { if (followMouse) { mouse_x = x; mouse_y = y; goal_is_mouse = true; } return followMouse; } public boolean mouseMove(Event evt, int x, int y) { if (followMouse) { mouse_x = x; mouse_y = y; goal_is_mouse = true; } return followMouse; } public boolean mouseExit(Event evt, int x, int y) { goal_is_mouse = false; return followMouse; } public void update(Graphics g) { paint(g); } void draw_boid_line(int i) { double angle = Math.atan2((double) boid_xv[i], (double) boid_yv[i]); float dx = (float) (boid_radius * Math.cos(angle)); float dy = (float) (boid_radius * Math.sin(angle)); viewGC.drawLine( (int) (boid_x[i] + dx), (int) (boid_y[i] + dy) , (int) (boid_x[i] - dx), (int) (boid_y[i] - dy)); } void draw_boid_pie(int i) { int angle = ((int) (Math.atan2((double) boid_xv[i], (double) boid_yv[i]) * (180 / Math.PI))) + 90; viewGC.fillArc( ((int) boid_x[i]) - boid_size , ((int) boid_y[i]) - boid_size , boid_size * 2, boid_size * 2 , angle - 20, 40); } void draw_boid_square(int i) { viewGC.fillRect(((int) boid_x[i]) - boid_radius , ((int) boid_y[i]) - boid_radius , boid_size, boid_size); } void draw_boid_rect(int i) { double xv = ((double) boid_xv[i]) / ((double) maxVel); double yv = ((double) boid_yv[i]) / ((double) maxVel); viewGC.fillRect(((int) boid_x[i]) - boid_radius , ((int) boid_y[i]) - boid_radius , boid_size, boid_size); } void draw_boid_image(int i) { viewGC.drawImage( boidImages[((int) (Math.atan2((double) boid_xv[i] , (double) boid_yv[i]) * ((imageAngleCount / 2) / Math.PI))) + ((imageAngleCount / 2))][boid_frame[i]] , ((int) boid_x[i]) - boid_radius , ((int) boid_y[i]) - boid_radius , this); } void draw_boid_bee(int i) { viewGC.drawImage( boidImages[((int) (Math.atan2( (double) (goal_x - boid_x[i]) , (double) (goal_y - boid_y[i])) * ((imageAngleCount / 2) / Math.PI))) + ((imageAngleCount / 2))][boid_frame[i]] , ((int) boid_x[i]) - boid_radius , ((int) boid_y[i]) - boid_radius , this); } public void paint(Graphics g) { if (backgroundImage != null) { viewGC.drawImage(backgroundImage, 0, 0, this); } else { viewGC.clearRect(0, 0, screenBounds.width, screenBounds.height); viewGC.setColor(boidColor); } for (int i = (goal_is_boid ? 1 : 0); i < boidCount; ++i) { switch (boid_shape) { case boidSquare : draw_boid_square(i); break; case boidPie : draw_boid_pie(i); break; case boidBee : draw_boid_bee(i); break; case boidShip : case boidCustom : draw_boid_image(i); break; } } if (goalImage != null) { viewGC.drawImage(goalImage , (int) goal_x - (goalImage.getWidth(this) / 2) , (int) goal_y - (goalImage.getHeight(this) / 2) , this); } else { viewGC.setColor(Color.red); viewGC.fillOval(((int) goal_x) - boid_radius , ((int) goal_y) - boid_radius , boid_size, boid_size); } g.drawImage(viewImage, 0, 0, this); lastPaint = System.currentTimeMillis(); } public void start() { workThread = new Thread(this); workThread.start(); } public void run() { try { long updateLimit = 0; long lastUpdate = 0; float hue = 0.0f; while (workThread != null) { if (true) { if (boidChangesColor) { boidColor = Color.getHSBColor(hue, 1.0f, 1.0f); hue += 1.0f / 256f; if (hue >= 1.0f) hue = 0.0f; } computeAccelerations(); oneStep(); if (lastPaint < lastUpdate) { // Thread.sleep(100); } lastUpdate = System.currentTimeMillis(); repaint(); if (lastUpdate < updateLimit) Thread.sleep(updateLimit - lastUpdate); else Thread.yield(); updateLimit = lastUpdate + updateRate; } else { computeAccelerations(); for (int i = 1; i <= imageAngleCount; ++i) { boid_x[i] = i * 20; boid_y[i] = 40; boid_xv[i] = (float) Math.cos((i - 1) * Math.PI * 2 / imageAngleCount); boid_yv[i] = (float) Math.sin((i - 1) * Math.PI * 2 / imageAngleCount); } repaint(); Thread.sleep(4000); } } } catch (InterruptedException e) { } } public void stop() { if (workThread != null) { workThread.stop(); } } static float norm(float x1, float x2) { return ((float) Math.sqrt((x1*x1) + (x2*x2))); } void computeAccelerations() { float cAx; float cAy; float aVx; float aVy; float dist; float aMag; float xDiff; float yDiff; float adjDist; float adjDistSum; float mid_x = viewWidth / 2; float mid_y = viewHeight / 2; float max_x = viewWidth; float max_y = viewHeight; adjDist = 0; if (goal_is_mouse) { goal_x = mouse_x; goal_y = mouse_y; if (goal_x > viewWidth) goal_x = viewWidth; if (goal_y > viewHeight) goal_y = viewHeight; if (goal_x < 0) goal_x = 0; if (goal_y < 0) goal_y = 0; } else { if ((cycleCount++ % goalChangeFreq) == 0) { goal_x = mid_x + (((float) Math.random() - 0.5f) * 0.45f * viewWidth); goal_y = mid_y + (((float) Math.random() - 0.5f) * 0.45f * viewHeight); if (goal_is_boid) { boid_x[0] = goal_x; boid_y[0] = goal_y; boid_xv[0] = 0; boid_yv[0] = 0; } } else { if (goal_is_boid) { goal_x = boid_x[0]; goal_y = boid_y[0]; } } } /* other school avoidance */ for (int i = 0; i < boidCount; i++) { adjDistSum = 0; cAx = cAy = aVx = aVy = 0; boid_xa[i] = 0; boid_ya[i] = 0; for (int j = 0; j < boidCount; j++) { if (i == j) continue; xDiff = boid_x[i] - boid_x[j]; yDiff = boid_y[i] - boid_y[j]; if (xDiff > mid_x) xDiff = max_x - xDiff; if (yDiff > mid_y) yDiff = max_y - yDiff; dist = norm(xDiff, yDiff); if (dist > minRadius) continue; else if (dist <= 0) dist = 0.5f; adjDist = dist * dist; adjDistSum += (1 / adjDist); xDiff /= adjDist; yDiff /= adjDist; cAx -= xDiff; cAy -= yDiff; boid_xa[i] += xDiff; boid_xa[i] += yDiff; aVx += (boid_xv[j] / adjDist); aVy += (boid_yv[j] / adjDist); } xDiff = goal_x - boid_x[i]; yDiff = goal_y - boid_y[i]; boid_xa[i] *= avoidFact; boid_ya[i] *= avoidFact; aMag = norm(boid_xa[i], boid_ya[i]); /* velocity matching */ if ((adjDistSum != 0.0f) && (aMag < accLimit)) { aVx /= adjDistSum; aVy /= adjDistSum; boid_xa[i] += ((aVx - boid_xv[i]) * matchFact); boid_ya[i] += ((aVy - boid_yv[i]) * matchFact); aMag = norm(boid_xa[i], boid_ya[i]); /* flock centering */ if (aMag < accLimit) { boid_xa[i] += cAx * centerFact; boid_ya[i] += cAy * centerFact; aMag = norm(boid_xa[i], boid_ya[i]); /* target attraction */ if (aMag < accLimit) { boid_xa[i] += xDiff * targetFact / adjDist; boid_ya[i] += yDiff * targetFact / adjDist; } } } boid_xa[i] += ((float) Math.random() - 0.5f); boid_ya[i] += ((float) Math.random() - 0.5f); aMag = norm(boid_xa[i], boid_ya[i]); if (aMag > accLimit) { boid_xa[i] *= Math.sqrt(accLimit/aMag); boid_ya[i] *= Math.sqrt(accLimit/aMag); } } } void oneStep() { float avgIndex = 0; for (int i = 0; i < boidCount; ++i) { /* apply accelerations */ boid_xv[i] = boid_xa[i] + ((1 + momentum) * boid_xv[i]); boid_yv[i] = boid_ya[i] + ((1 + momentum) * boid_yv[i]); float vMag = 1.0e-6f + norm(boid_xv[i], boid_yv[i]); if (vMag > maxVel) { boid_xv[i] *= (maxVel / vMag); boid_yv[i] *= (maxVel / vMag); } else if (vMag < minVel) { boid_xv[i] *= (minVel / vMag); boid_yv[i] *= (minVel / vMag); } /* apply movements */ boid_x[i] += boid_xv[i]; boid_y[i] += boid_yv[i]; if (wrap_around) { if (boid_x[i] > viewWidth) { boid_x[i] -= viewWidth; } else if (boid_x[i] < 0) { boid_x[i] += viewWidth; } if (boid_y[i] > viewHeight) { boid_y[i] -= viewHeight; } else if (boid_y[i] < 0) { boid_y[i] += viewHeight; } } if ((boid_frame[i] += 1) >= imageFrameCount) boid_frame[i] = 0; } if (!wrap_around && goal_is_boid) { if (boid_x[0] > viewWidth) { boid_x[0] -= viewWidth; } else if (boid_x[0] < 0) { boid_x[0] += viewWidth; } if (boid_y[0] > viewHeight) { boid_y[0] -= viewHeight; } else if (boid_y[0] < 0) { boid_y[0] += viewHeight; } } } }