'p5 / React Boids integration

I am trying to add a p5.js sketch to my React App using React p5 wrapper. I have gone through the documentation on the wrapper npm page, but have come up short on more in-depth questions. At the moment I am able to display a simple sketch in my App() and have it render correctly, but as I try to scale to the boids flocking sim It breaks down. The boids sim runs successfully in CodePen and as a stand-alone in VS Code, so I assume the problem is in the integration. I can debug the logic and algorithms, but I just can't seem to figure out the integration. Is there further documentation available that may help? Any assistance would be greatly appreciated.

Where I got the code: https://p5js.org/examples/simulate-flocking.html

Initial Modifications: https://codepen.io/stephenleemorrow/pen/QWaxKqY

Vs Code:

export default function sketch(p5) {

  let flock;
  let img;
  let bg;

  /*p5.preload = () => {
    img = p5.loadImage("public\assets\pixle-cloud-landscape.webp");
    bg = p5.loadImage("public\assets\pixle-cloud-landscape.webp");
  }*/
  

  p5.setup = () => {
    p5.createCanvas(568, 320);
    flock = new Flock();
    // Add an initial set of boids into the system
    for (let i = 0; i < 20; i++) {
      let b = new Boid(p5.width / 2, p5.height / 2);
      flock.addBoid(b);
    }
  }

  p5.draw = () =>  {
    p5.background(51);
    flock.run();
    //p5.image(img, -23, -10, 610, 340);
  }

  // Add a new boid into the System
  p5.mouseDragged = () => {
    flock.addBoid(new Boid(p5.mouseX, p5.mouseY));
  }

  // The Nature of Code
  // Daniel Shiffman
  // http://natureofcode.com

  // Flock object
  // Does very little, simply manages the array of all the boids

  function Flock() {
    // An array for all the boids
    this.boids = []; // Initialize the array
  }

  Flock.prototype.run = function () {
    for (let i = 0; i < this.boids.length; i++) {
      this.boids[i].run(this.boids);  // Passing the entire list of boids to each boid individually
    }
  }

  Flock.prototype.addBoid = function (b) {
    this.boids.push(b);
  }

  // The Nature of Code
  // Daniel Shiffman
  // http://natureofcode.com

  // Boid class
  // Methods for Separation, Cohesion, Alignment added

  function Boid(x, y) {
    this.acceleration = p5.createVector(0, 0);
    this.velocity = p5.createVector(p5.random(-1, 1), p5.random(-1, 1));
    this.position = p5.createVector(x, y);
    this.r = 3.0;
    this.maxspeed = 3;    // Maximum speed
    this.maxforce = 0.05; // Maximum steering force
  }

  Boid.prototype.run = function (boids) {
    this.flock(boids);
    this.update();
    this.borders();
    this.render();
  }

  Boid.prototype.applyForce = function (force) {
    // We could add mass here if we want A = F / M
    this.acceleration.add(force);
  }

  // We accumulate a new acceleration each time based on three rules
  Boid.prototype.flock = function (boids) {
    let sep = this.separate(boids);   // Separation
    let ali = this.align(boids);      // Alignment
    let coh = this.cohesion(boids);   // Cohesion
    // Arbitrarily weight these forces
    sep.mult(1.5);
    ali.mult(1.0);
    coh.mult(1.0);
    // Add the force vectors to acceleration
    this.applyForce(sep);
    this.applyForce(ali);
    this.applyForce(coh);
  }

  // Method to update location
  Boid.prototype.update = function () {
    // Update velocity
    this.velocity.add(this.acceleration);
    // Limit speed
    this.velocity.limit(this.maxspeed);
    this.position.add(this.velocity);
    // Reset accelertion to 0 each cycle
    this.acceleration.mult(0);
  }

  // A method that calculates and applies a steering force towards a target
  // STEER = DESIRED MINUS VELOCITY
  Boid.prototype.seek = function (target) {
    let desired = p5.Vector.sub(target, this.position);  // A vector pointing from the location to the target
    // Normalize desired and scale to maximum speed
    desired.normalize();
    desired.mult(this.maxspeed);
    // Steering = Desired minus Velocity
    let steer = p5.Vector.sub(desired, this.velocity);
    steer.limit(this.maxforce);  // Limit to maximum steering force
    return steer;
  }

  Boid.prototype.render = function () {
    // Draw a triangle rotated in the direction of velocity
    let theta = this.velocity.heading() + p5.radians(90);
    p5.fill(50);
    p5.stroke(100);
    p5.push();
    p5.translate(this.position.x, this.position.y);
    p5.rotate(theta);
    p5.beginShape();
    p5.vertex(0, -this.r * .5);
    p5.vertex(-this.r, this.r * .5);
    p5.vertex(this.r, this.r * .5);
    p5.endShape(p5.CLOSE);
    p5.pop();
  }

  // Wraparound
  Boid.prototype.borders = function () {
    if (this.position.x < -this.r) this.position.x = p5.width + this.r;
    if (this.position.y < -this.r) this.position.y = p5.height + this.r;
    if (this.position.x > p5.width + this.r) this.position.x = -this.r;
    if (this.position.y > p5.height + this.r) this.position.y = -this.r;
  }

  // Separation
  // Method checks for nearby boids and steers away
  Boid.prototype.separate = function (boids) {
    let desiredseparation = 25.0;
    let steer = p5.createVector(0, 0);
    let count = 0;
    // For every boid in the system, check if it's too close
    for (let i = 0; i < boids.length; i++) {
      let d = p5.Vector.dist(this.position, boids[i].position);
      // If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself)
      if ((d > 0) && (d < desiredseparation)) {
        // Calculate vector pointing away from neighbor
        let diff = p5.Vector.sub(this.position, boids[i].position);
        diff.normalize();
        diff.div(d);        // Weight by distance
        steer.add(diff);
        count++;            // Keep track of how many
      }
    }
    // Average -- divide by how many
    if (count > 0) {
      steer.div(count);
    }

    // As long as the vector is greater than 0
    if (steer.mag() > 0) {
      // Implement Reynolds: Steering = Desired - Velocity
      steer.normalize();
      steer.mult(this.maxspeed);
      steer.sub(this.velocity);
      steer.limit(this.maxforce);
    }
    return steer;
  }

  // Alignment
  // For every nearby boid in the system, calculate the average velocity
  Boid.prototype.align = function (boids) {
    let neighbordist = 50;
    let sum = p5.createVector(0, 0);
    let count = 0;
    for (let i = 0; i < boids.length; i++) {
      let d = p5.Vector.dist(this.position, boids[i].position);
      if ((d > 0) && (d < neighbordist)) {
        sum.add(boids[i].velocity);
        count++;
      }
    }
    if (count > 0) {
      sum.div(count);
      sum.normalize();
      sum.mult(this.maxspeed);
      let steer = p5.Vector.sub(sum, this.velocity);
      steer.limit(this.maxforce);
      return steer;
    } else {
      return p5.createVector(0, 0);
    }
  }

  // Cohesion
  // For the average location (i.e. center) of all nearby boids, calculate steering vector towards that location
  Boid.prototype.cohesion = function (boids) {
    let neighbordist = 50;
    let sum = p5.createVector(0, 0);   // Start with empty vector to accumulate all locations
    let count = 0;
    for (let i = 0; i < boids.length; i++) {
      let d = p5.Vector.dist(this.position, boids[i].position);
      if ((d > 0) && (d < neighbordist)) {
        sum.add(boids[i].position); // Add location
        count++;
      }
    }
    if (count > 0) {
      sum.div(count);
      return this.seek(sum);  // Steer towards the location
    } else {
      return p5.createVector(0, 0);
    }
  }

}
import { ReactP5Wrapper } from "react-p5-wrapper"
import sketch from "./sketch"



function App() {

  return(

    <>
      <h2>Hellow World</h2>
      <ReactP5Wrapper sketch={sketch} />
    </>
    
    

  )
  
}

export default App;

I omitted the images for debugging purposes.



Solution 1:[1]

I believe the issue you are encountering is that you are using the argument name p5 for the p5 instance that is passed to your sketch() function. This shadows the globally exported p5 constructor which has the p5.Vector class on it. So all of your references to static p5.Vector methods were broken.

function sketch(p) {
  let flock;

  p.setup = () => {
    p.createCanvas(568, 320);
    flock = new Flock();
    // Add an initial set of boids into the system
    for (let i = 0; i < 20; i++) {
      let b = new Boid(p.width / 2, p.height / 2);
      flock.addBoid(b);
    }
  }

  p.draw = () =>  {
    p.background(51);
    flock.run();
  }

  // Add a new boid into the System
  p.mouseDragged = () => {
    flock.addBoid(new Boid(p.mouseX, p.mouseY));
  }

  // The Nature of Code
  // Daniel Shiffman
  // http://natureofcode.com

  // Flock object
  // Does very little, simply manages the array of all the boids

  function Flock() {
    // An array for all the boids
    this.boids = []; // Initialize the array
  }

  Flock.prototype.run = function () {
    for (let i = 0; i < this.boids.length; i++) {
      this.boids[i].run(this.boids);  // Passing the entire list of boids to each boid individually
    }
  }

  Flock.prototype.addBoid = function (b) {
    this.boids.push(b);
  }

  // The Nature of Code
  // Daniel Shiffman
  // http://natureofcode.com

  // Boid class
  // Methods for Separation, Cohesion, Alignment added

  function Boid(x, y) {
    this.acceleration = p.createVector(0, 0);
    this.velocity = p.createVector(p.random(-1, 1), p.random(-1, 1));
    this.position = p.createVector(x, y);
    this.r = 3.0;
    this.maxspeed = 3;    // Maximum speed
    this.maxforce = 0.05; // Maximum steering force
  }

  Boid.prototype.run = function (boids) {
    this.flock(boids);
    this.update();
    this.borders();
    this.render();
  }

  Boid.prototype.applyForce = function (force) {
    // We could add mass here if we want A = F / M
    this.acceleration.add(force);
  }

  // We accumulate a new acceleration each time based on three rules
  Boid.prototype.flock = function (boids) {
    let sep = this.separate(boids);   // Separation
    let ali = this.align(boids);      // Alignment
    let coh = this.cohesion(boids);   // Cohesion
    // Arbitrarily weight these forces
    sep.mult(1.5);
    ali.mult(1.0);
    coh.mult(1.0);
    // Add the force vectors to acceleration
    this.applyForce(sep);
    this.applyForce(ali);
    this.applyForce(coh);
  }

  // Method to update location
  Boid.prototype.update = function () {
    // Update velocity
    this.velocity.add(this.acceleration);
    // Limit speed
    this.velocity.limit(this.maxspeed);
    this.position.add(this.velocity);
    // Reset accelertion to 0 each cycle
    this.acceleration.mult(0);
  }

  // A method that calculates and applies a steering force towards a target
  // STEER = DESIRED MINUS VELOCITY
  Boid.prototype.seek = function (target) {
    let desired = p5.Vector.sub(target, this.position);  // A vector pointing from the location to the target
    // Normalize desired and scale to maximum speed
    desired.normalize();
    desired.mult(this.maxspeed);
    // Steering = Desired minus Velocity
    let steer = p5.Vector.sub(desired, this.velocity);
    steer.limit(this.maxforce);  // Limit to maximum steering force
    return steer;
  }

  Boid.prototype.render = function () {
    // Draw a triangle rotated in the direction of velocity
    let theta = this.velocity.heading() + p.radians(90);
    p.fill(50);
    p.stroke(100);
    p.push();
    p.translate(this.position.x, this.position.y);
    p.rotate(theta);
    p.beginShape();
    p.vertex(0, -this.r * .5);
    p.vertex(-this.r, this.r * .5);
    p.vertex(this.r, this.r * .5);
    p.endShape(p.CLOSE);
    p.pop();
  }

  // Wraparound
  Boid.prototype.borders = function () {
    if (this.position.x < -this.r) this.position.x = p.width + this.r;
    if (this.position.y < -this.r) this.position.y = p.height + this.r;
    if (this.position.x > p.width + this.r) this.position.x = -this.r;
    if (this.position.y > p.height + this.r) this.position.y = -this.r;
  }

  // Separation
  // Method checks for nearby boids and steers away
  Boid.prototype.separate = function (boids) {
    let desiredseparation = 25.0;
    let steer = p.createVector(0, 0);
    let count = 0;
    // For every boid in the system, check if it's too close
    for (let i = 0; i < boids.length; i++) {
      let d = p5.Vector.dist(this.position, boids[i].position);
      // If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself)
      if ((d > 0) && (d < desiredseparation)) {
        // Calculate vector pointing away from neighbor
        let diff = p5.Vector.sub(this.position, boids[i].position);
        diff.normalize();
        diff.div(d);        // Weight by distance
        steer.add(diff);
        count++;            // Keep track of how many
      }
    }
    // Average -- divide by how many
    if (count > 0) {
      steer.div(count);
    }

    // As long as the vector is greater than 0
    if (steer.mag() > 0) {
      // Implement Reynolds: Steering = Desired - Velocity
      steer.normalize();
      steer.mult(this.maxspeed);
      steer.sub(this.velocity);
      steer.limit(this.maxforce);
    }
    return steer;
  }

  // Alignment
  // For every nearby boid in the system, calculate the average velocity
  Boid.prototype.align = function (boids) {
    let neighbordist = 50;
    let sum = p.createVector(0, 0);
    let count = 0;
    for (let i = 0; i < boids.length; i++) {
      let d = p5.Vector.dist(this.position, boids[i].position);
      if ((d > 0) && (d < neighbordist)) {
        sum.add(boids[i].velocity);
        count++;
      }
    }
    if (count > 0) {
      sum.div(count);
      sum.normalize();
      sum.mult(this.maxspeed);
      let steer = p5.Vector.sub(sum, this.velocity);
      steer.limit(this.maxforce);
      return steer;
    } else {
      return p.createVector(0, 0);
    }
  }

  // Cohesion
  // For the average location (i.e. center) of all nearby boids, calculate steering vector towards that location
  Boid.prototype.cohesion = function (boids) {
    let neighbordist = 50;
    let sum = p.createVector(0, 0);   // Start with empty vector to accumulate all locations
    let count = 0;
    for (let i = 0; i < boids.length; i++) {
      let d = p5.Vector.dist(this.position, boids[i].position);
      if ((d > 0) && (d < neighbordist)) {
        sum.add(boids[i].position); // Add location
        count++;
      }
    }
    if (count > 0) {
      sum.div(count);
      return this.seek(sum);  // Steer towards the location
    } else {
      return p.createVector(0, 0);
    }
  }
}

// Inlined below
// import { ReactP5Wrapper } from "react-p5-wrapper"

function App() {
  return(
    <div>
      <h2>Hellow World</h2>
      <ReactP5Wrapper sketch={sketch} />
    </div>
  );
}

(function declareReactP5Wrapper(exports) {
  /* Inlined react-p5-wrapper
    Copyright 2016 James Robb

    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    */
  const { createRef, FC, memo, useState, useLayoutEffect } = React;

  function createCanvas(sketch, wrapper) {
    return new p5(sketch, wrapper);
  }

  const ReactP5WrapperComponent = ({
    sketch,
    children,
    ...props
  }) => {
    const wrapperRef = createRef();
    const [instance, setInstance] = useState();

    useLayoutEffect(() => {
      if (wrapperRef.current === null) {
        return;
      }

      if (instance) instance.remove();
      const canvas = createCanvas(sketch, wrapperRef.current);
      setInstance(canvas);
    }, [sketch]);

    useLayoutEffect(() => {
      if (instance && instance.updateWithProps) instance.updateWithProps(props);
    }, [props, instance]);

    useLayoutEffect(() => () => instance && instance.remove(), []);

    return <div ref={wrapperRef}>{children}</div>;
  };

  function propsAreEqual(previous, next) {
    const differences = diff(previous, next);

    return differences.length === 0;
  }

  /* microdiff inlined
    MIT License

    Copyright (c) 2021 AsyncBanana

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.
  */
  const richTypes = { Date: true, RegExp: true, String: true, Number: true };
  function diff(
    obj,
    newObj,
    options = { cyclesFix: true },
    _stack = []
  ) {
    let diffs = [];
    const isObjArray = Array.isArray(obj);
    for (const key in obj) {
      const objKey = obj[key];
      const path = isObjArray ? +key : key;
      if (!(key in newObj)) {
        diffs.push({
          type: "REMOVE",
          path: [path],
          oldValue: obj[key],
        });
        continue;
      }
      const newObjKey = newObj[key];
      const areObjects =
        typeof objKey === "object" && typeof newObjKey === "object";
      if (
        objKey &&
        newObjKey &&
        areObjects &&
        !richTypes[Object.getPrototypeOf(objKey).constructor.name] &&
        (options.cyclesFix ? !_stack.includes(objKey) : true)
      ) {
        const nestedDiffs = diff(
          objKey,
          newObjKey,
          options,
          options.cyclesFix ? _stack.concat([objKey]) : []
        );
        diffs.push.apply(
          diffs,
          nestedDiffs.map((difference) => {
            difference.path.unshift(path);
            return difference;
          })
        );
      } else if (
        objKey !== newObjKey &&
        !(
          areObjects &&
          (isNaN(objKey)
            ? objKey + "" === newObjKey + ""
            : +objKey === +newObjKey)
        )
      ) {
        diffs.push({
          path: [path],
          type: "CHANGE",
          value: newObjKey,
          oldValue: objKey,
        });
      }
    }
    const isNewObjArray = Array.isArray(newObj);
    for (const key in newObj) {
      if (!(key in obj)) {
        diffs.push({
          type: "CREATE",
          path: [isNewObjArray ? +key : key],
          value: newObj[key],
        });
      }
    }
    return diffs;
  }

  exports.ReactP5Wrapper = memo(ReactP5WrapperComponent, propsAreEqual);


})(window);

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.min.js"></script>

<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/p5.min.js"></script>

<div id="root"></div>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Paul Wheeler