Ichthyarchy: The Domain of Fishes

ichthyarchy: The domain of fishes; the fish-world in all of its orders.

Reference:

Saved .png image:

Twelve swimming Fish objects are instantiated when the sketch is loaded. For each Fish, the initial location, direction of swim, and drawing order are randomly calculated. Small random factors modify the horizontal velocity and vertical drift of each Fish as it swims.

Click the sketch to stop and start the animation.

p5.js code:

new p5((p) => {
  let school = [];
  let anim = true;
  p.setup = () => {
    p.createCanvas(p.windowWidth, p.windowHeight);
    p.noStroke();
    p.ellipseMode(p.CENTER);
    p.textFont("Verdana");
    p.textSize(48);
    p.textAlign(p.CENTER, p.CENTER);
    school.push(
      new Fish(
        p.random(p.width),
        p.random(p.height),
        200,
        60,
        p.color(128, 255, 128),
        p.random(["left", "right"])
      )
    ); // green
    school.push(
      new Fish(
        p.random(p.width),
        p.random(p.height),
        160,
        80,
        p.color(128, 128, 255),
        p.random(["left", "right"])
      )
    ); // blue
    school.push(
      new Fish(
        p.random(p.width),
        p.random(p.height),
        200,
        60,
        p.color(255, 128, 128),
        p.random(["left", "right"])
      )
    ); // red
    school.push(
      new Fish(
        p.random(p.width),
        p.random(p.height),
        160,
        80,
        p.color(255, 255, 128),
        p.random(["left", "right"])
      )
    ); // yellow
    school.push(
      new Fish(
        p.random(p.width),
        p.random(p.height),
        200,
        60,
        p.color(255, 128, 255),
        p.random(["left", "right"])
      )
    ); // magenta
    school.push(
      new Fish(
        p.random(p.width),
        p.random(p.height),
        160,
        80,
        p.color(128, 255, 255),
        p.random(["left", "right"])
      )
    ); // cyan
    school.push(
      new Fish(
        p.random(p.width),
        p.random(p.height),
        240,
        40,
        p.color(184, 115, 51),
        p.random(["left", "right"])
      )
    ); // copper
    school.push(
      new Fish(
        p.random(p.width),
        p.random(p.height),
        280,
        40,
        p.color(192, 192, 192),
        p.random(["left", "right"])
      )
    ); // silver
    school.push(
      new Fish(
        p.random(p.width),
        p.random(p.height),
        240,
        40,
        p.color(255, 215, 0),
        p.random(["left", "right"])
      )
    ); // gold
    school.push(
      new Fish(
        p.random(p.width),
        p.random(p.height),
        240,
        40,
        p.color(255, 165, 0),
        p.random(["left", "right"])
      )
    ); // orange

    school.push(
      new Fish(
        p.random(p.width),
        p.random(p.height),
        280,
        40,
        p.color(166, 10, 61),
        p.random(["left", "right"])
      )
    ); // cranberry

    school.push(
      new Fish(
        p.random(p.width),
        p.random(p.height),
        240,
        40,
        p.color(128, 128, 0),
        p.random(["left", "right"])
      )
    ); // olive

    p.shuffle(school, true);
    p.background(187, 221, 255);
    for (let i = 0; i < school.length; i++) {
      school[i].render();
    }

    p.fill(0, 64);
    p.text("ichthyarchy", p.width / 2, 48);
  };

  p.draw = () => {
    p.background(187, 221, 255);
    if (anim) {
      for (let i = 0; i < school.length; i++) {
        school[i].swim();
      }
    } else {
      for (let i = 0; i < school.length; i++) {
        school[i].render();
      }
    }
    p.fill(0, 64);
    p.text("ichthyarchy", p.width / 2, 48);
  };

  p.windowResized = () => {
    p.resizeCanvas(p.windowWidth, p.windowHeight);
  };

  class Fish {
    constructor(x, y, bodyLength, bodyHeight, c, chirality) {
      this.x = x;
      this.y = y;
      this.bodyLength = bodyLength;
      this.bodyHeight = bodyHeight;
      this.c = c;
      if (chirality == "right") {
        this.chirality = 1;
      } else {
        this.chirality = -1;
      }
    } // end constructor

    swim() {
      let dx;
      let dy;
      dx = (p.abs(p.randomGaussian() * 2) + 2) * this.chirality;

      dy = p.randomGaussian() / 2;
      // (p.height / 2 - this.y) / 100;
      this.x += dx;
      this.y += dy;
      // horizontal correction
      if (this.x > p.width + this.bodyLength / 2) {
        this.x = -this.bodyLength / 2;
      }
      if (this.x < -this.bodyLength / 2) {
        this.x = p.width + this.bodyLength / 2;
      }
      // vertical correction
      if (this.y > p.height - this.bodyHeight / 2) {
        this.y = p.height - this.bodyHeight / 2 - 1;
      }
      if (this.y < this.bodyHeight / 2) {
        this.y = this.bodyHeight / 2 + 1;
      }
      this.render();
    } // end swim

    render() {
      // Body
      p.noStroke();
      p.fill(this.c);
      p.ellipse(this.x, this.y, this.bodyLength, this.bodyHeight);
      p.triangle(
        this.x - (this.chirality * this.bodyLength) / 16,
        this.y,
        this.x - (this.chirality * this.bodyLength) / 2,
        this.y + this.bodyHeight / 2,
        this.x - (this.chirality * this.bodyLength) / 2,
        this.y - this.bodyHeight / 2
      );
      p.triangle(
        this.x,
        this.y,
        this.x - this.bodyLength / 4,
        this.y - this.bodyHeight / 2,
        this.x + this.bodyLength / 4,
        this.y - this.bodyHeight / 2
      );
      p.triangle(
        this.x,
        this.y,
        this.x - this.bodyLength / 4,
        this.y + this.bodyHeight / 2,
        this.x + this.bodyLength / 4,
        this.y + this.bodyHeight / 2
      );

      p.fill(32);
      p.circle(
        this.x + (this.chirality * this.bodyLength) / 4,
        this.y,
        this.bodyHeight / 8
      );
    } // end render
  } // end class Fish

  const toggleAnimation = () => {
    anim = !anim;
  };
  document.addEventListener("click", toggleAnimation, true);
});
1 Like

Ahah funny one! :wink:

Is there a reason you are using p5 instance mode instead of directly using them in the global namespace? (which would remove all the p. attribute accesses)

2 Likes

The advantage of using instance mode is that you can have multiple sketches on the same webpage.

If you change the canvas size to 800x600 and copy all the sketch code then paste it below the existing sketch code and run it you will end up with 2 sketches. The only problem is a mouse click anywhere on the webpage will pause the animation in both sketches.

The solution is to add the event handler to the canvas rather than the document and a couple of minor changes to the setup function achieves this

  p.setup = () => {
    let p5canvas = p.createCanvas(800,600);
    p5canvas.canvas.addEventListener("click", toggleAnimation, true);
    p.noStroke();
    ...

Now delete the line

document.addEventListener("click", toggleAnimation, true);```

Now the sketch will only respond to mouse clicks on its own canvas and the two sketches work independently.

1 Like

For completeness’ sake we can also have multiple global mode sketches as long as they live in separate <iframe>:
https://developer.Mozilla.org/en-US/docs/Web/HTML/Element/iframe

Using a more p5js style, we can call p5.Element::mousePressed() method over the p5.Renderer object returned by createCanvas(), and then apply noLoop() or loop() to the sketch inside its callback:


Global Mode Version:

var paused;

function setup() {
  createCanvas(800, 600).mousePressed(togglePause);
  frameRate(2);
}

function draw() {
  background('#' + hex(~~random(0x1000), 3));
}

function togglePause() {
  if (paused ^= true)  noLoop();
  else                 loop();
}

Instance Mode Version:

new p5(p => {
  var paused;

  p.setup = function () {
    p.createCanvas(800, 600).mousePressed(togglePause);
    p.frameRate(2);
  };

  p.draw = function () {
    p.background('#' + p.hex(~~p.random(0x1000), 3));
  };

  function togglePause() {
    if (paused ^= true)  p.noLoop();
    else                 p.loop();
  }
});

2 Likes

Besides the typical instance mode style, we can also add ES6 modules into the mix:

“index.html”:

<script defer src=https://cdn.JsDelivr.net/npm/p5></script>
<script type=module src=index.mjs></script>

“index.mjs”:

import sketch from './sketch.mjs';
new p5(sketch);

“sketch.mjs”:

export default function sketch(p) {
  p.setup = setup;
  p.draw = draw;

  p.togglePause = togglePause.bind(p);
}

function setup() {
  this.createCanvas(800, 600).mousePressed(this.togglePause);
  this.frameRate(2);
}

function draw() {
  this.background('#' + this.hex(~~this.random(0x1000), 3));
}

function togglePause() {
  if (this.paused ^= true)  this.noLoop();
  else                      this.loop();
}

Here’s a more complete online example of using modularized files that displays 12 instances of the same sketch:

https://Self-Avoiding-Walk-II-CoffeeScript.Glitch.me/pages/4pjs-4q5js-4p5js.html

Each 1 can be paused/resumed individually. Besides changing background colors & resetting.

2 Likes

Thanks to you all for the great comments and information about instance mode.

Some time ago, @micuat got me quite interested in the merits of instance mode. Subsequently, I took it upon myself as a challenge to write some sketches that localized names as much as possible. Such is the case with the current sketch.

In particular, note the first line of the instance mode example in this documentation of p5():

That line is:

const s = p => {

It creates s as a global reference to a constant to be passed later to a new p5 object.

In contrast, for the current sketch I decided to instantiate the value to be passed to the p5 object anonymously, in addition to the other efforts at name localization. Here’s the first line of that sketch:

new p5((p) => {

I am not suggesting that we must always make every effort to avoid creating global names. But that was one of my goals with the present sketch.

1 Like