Dynamically controlled particle system with Toxiclibs

edited June 2017 in Library Questions

Hi all,

I'm working on modifying the Attraction2D example from the Toxiclibs library to be controlled by gestures from a Leap Motion sensor, as opposed to the mouse in the example.

I'm doing all my gesture recognition in an Open Frameworks app, and sending that over OSC.

When a "Gesture 0" event occurs, I call the method below to remove the gestureAttractor from the physics object:

void resetAttraction() {
  if (gestureAttractor != null){
      physics.removeBehavior(gestureAttractor);
      println("ATTRACTOR NULL");
     } else {
        println("not null");
     }
}

If a "Gesture 1" event occurs, I call this method to create a new gestureAttractor, and add it back to the physics object:

void addAttraction(){ if (gestureAttractor == null) { println("ATTRACTOR NULL"); position1.set(340, 191); gestureAttractor = new AttractionBehavior2D(position1, 250, 0.9f); physics.addBehavior(gestureAttractor); } else { println("not null"); } }

What seems to happen consistently is whenever the gesture state changes, I'll get a "Concurrent Modification Exception" crash at physics.update(); in the draw method.

I'm sure it has something to do with the way the lifecycle of these objects are handled, but I haven't been able to determine anything yet - anyone have any ideas?

Below is the entirety of the sketch:

import toxi.geom.*;
import toxi.physics2d.*;
import toxi.physics2d.behaviors.*;

import oscP5.*;
import netP5.*;

OscP5 oscP5;

int NUM_PARTICLES = 750;

VerletPhysics2D physics;
//AttractionBehavior2D mouseAttractor;
AttractionBehavior2D gestureAttractor;


//Vec2D mousePos;
Vec2D position1;


boolean isGestureAttractorAdded;

void setup() {
  size(680, 382,P3D);
  // setup physics with 10% drag
  physics = new VerletPhysics2D();
  physics.setDrag(0.05f);
  physics.setWorldBounds(new Rect(0, 0, width, height));
  // the NEW way to add gravity to the simulation, using behaviors
  physics.addBehavior(new GravityBehavior2D(new Vec2D(0, 0.15f)));

  // start oscP5, listening for incoming messages at port 12000 
  oscP5 = new OscP5(this, 6000);

  position1 = new Vec2D(340, 191);

  addAttraction();

  //gestureAttractor = new AttractionBehavior2D(position1, 250, 0.9f);
  //physics.addBehavior(gestureAttractor);
}

void addParticle() {
  VerletParticle2D p = new VerletParticle2D(Vec2D.randomVector().scale(5).addSelf(width / 2, 0));
  physics.addParticle(p);
  // add a negative attraction force field around the new particle
  physics.addBehavior(new AttractionBehavior2D(p, 20, -1.2f, 0.01f));
}

void draw() {
  background(255,0,0);
  noStroke();
  fill(255);
  if (physics.particles.size() < NUM_PARTICLES) {
    addParticle();
  }
  physics.update();
  for (VerletParticle2D p : physics.particles) {
    ellipse(p.x, p.y, 5, 5);
  }
}

void mousePressed() {
  //position1 = new Vec2D(mouseX, mouseY);
   //create a new positive attraction force field around the mouse position (radius=250px)
  //gestureAttractor = new AttractionBehavior2D(position1, 250, 0.9f);
  //physics.addBehavior(gestureAttractor);

  //println(physics.behaviors);
}

void mouseDragged() {
  // update mouse attraction focal point
  //position1.set(mouseX, mouseY);
}

void mouseReleased() {
  // remove the mouse attraction when button has been released
  //physics.removeBehavior(gestureAttractor);
}


///// OSC RECEIVING

void oscEvent(OscMessage theOscMessage) {
  /* check if theOscMessage has the address pattern we are looking for. */

  if (theOscMessage.checkAddrPattern("/gesture_classification") == true)  {
    /* check if the typetag is the right one. */
    if(theOscMessage.checkTypetag("i")) {
      /* parse theOscMessage and extract the values from the osc message arguments. */
      int gestureClassLabel = theOscMessage.get(0).intValue();  
      println(" Gesture is: ", gestureClassLabel);

      if (gestureClassLabel == 0){   
        resetAttraction();
      } else if (gestureClassLabel == 1) {       
        addAttraction();
      } else if (gestureClassLabel == 2) {
          //physics.removeBehavior(gestureAttractor);
      } 
    }  
  } 

}

//////METHODS FOR SETTING POSITION / REMOVAL OF ATTRACTORS...

void resetAttraction() {
  if (gestureAttractor != null){
      physics.removeBehavior(gestureAttractor);
      println("ATTRACTOR NULL");
     } else {
        println("not null");
     }
 }

void addAttraction(){
     if (gestureAttractor == null) {
         println("ATTRACTOR NULL");
         position1.set(340, 191);
         gestureAttractor = new AttractionBehavior2D(position1, 250, 0.9f);
         physics.addBehavior(gestureAttractor);
     } else {
       println("not null");
     }
}

Answers

  • I figured out a workaround - by adding in code to print the error exception, the crash didn't occur anymore:

          try
      {
          physics.update();
    
      }
       catch (Exception e)
      {
        e.printStackTrace();
      }
    

    For reference, the result of printing the stacktrace is:

    at java.util.TimerThread.mainLoop(Timer.java:555) at java.util.TimerThread.run(Timer.java:505)

  • @GoToLoop, any advice on handling concurrent modification in this scenario?

    Past related discussions:

  • @jeremydouglass: @GoToLoop's answers in this thread to answer my question:

    https://forum.processing.org/two/discussion/comment/90265/#Comment_90265

    I added protected volatile to my potentially unthread safe variables - that seems to do the trick, but I'm of course open to any other thoughts!

  • I think I was wrong - that really didn't do much when pushing the sketch further (i.e., adding more particles).

    @GoToLoop, any thoughts at all?

  • edited June 2017

    Like I've just advised on this recent forum thread below:
    https://forum.Processing.org/two/discussion/23063/how-to-run-a-segment-of-code-uninterrupted#Item_2

    You should transfer VerletPhysics2D::particles container so it is handled by 1 Thread only.

    Rather than calling functions resetAttraction() & addAttraction(), which mutates the VerletPhysics2D::particles btW, from within oscEvent(), which runs outside the "Animation" Thread, make gestureClassLabel global, and just set it within oscEvent().

    Within draw(), check gestureClassLabel within a switch () / case: block, calling the corresponding function according to the gestureClassLabel's current value.

    Don't forget to always reset it back to some value which represents no action, like -1 or something like that, right after the switch () / case: block. :P

  • Hey @GoToLoop, thank you for this - that helps a lot.

    I tried your suggestion, and seem to have resolved the concurrency errors I was experiencing.

    However, what seems to be happening now is that

    So, here's what the sketch looks like before I added your changes in, but with the concurrency errors (every time a new gesture occurs, the particles move).

    Here's what happens when I add your changes in - no concurrency errors, but it seems that the sketch is only processing the change from a single gesture...after one occurs, any additional gestures have no effect.

    As you suggested, I made gestureClassLabel a global variable: int gestureClassLabel;

    And inside my OSC message method, I set the value of that variable accordingly.

    Inside my drawmethod, I've added the switch/case statement as so:

    void draw() {
      background(255,0,0);
      noStroke();
      fill(255);
      if (physics.particles.size() < NUM_PARTICLES) {
        addParticle();
      }
    
      physics.update();
    
      switch(gestureClassLabel) {
        case 0: {
          println("GESTURE 0");
          addGestureZeroAttraction();
        }
    
        case 1: {
          println("GESTURE 1");
          addGestureOneAttraction();
        }
    
        case 2: {
          println("GESTURE 2");
          resetAttraction();
        }
      }
    
      gestureClassLabel = -1;
    
      for (VerletParticle2D p : physics.particles) {
        ellipse(p.x, p.y, 5, 5);
      }
    
    }
    
  • edited June 2017

    You've forgotten about keyword break to isolate each case:! #-o
    https://Processing.org/reference/switch.html

    Obviously the last case: doesn't need it. But most folks like the redundant break there too. :))

    P.S.: Curly {} brackets are also unnecessary for case:'s statements. ;;)

  • edited June 2017

    @GoToLoop Oh...well that was dumb of me :(

    So, I made the change you suggested here, but the same behavior still occurs...wonder if I'm going to have to rethink how I'm approaching all of this...

        void draw() {
          background(255,0,0);
          noStroke();
          fill(255);
          if (physics.particles.size() < NUM_PARTICLES) {
            addParticle();
          }
    
          physics.update();  
    
          switch(gestureClassLabel) {
            case 0: 
              println("GESTURE 0");
              addGestureZeroAttraction();
              break;
    
            case 1: 
              println("GESTURE 1");
              addGestureOneAttraction();
              break;
    
            case 2: 
              println("GESTURE 2");
              resetAttraction();
              break;
          }
    
          gestureClassLabel = -1;
    
          for (VerletParticle2D p : physics.particles) {
            ellipse(p.x, p.y, 5, 5);
         }
    }
    
    • There's some fault in 1 of my advises: Unconditionally assigning -1 to gestureClassLabel. b-(
    • Function draw() is called back at about 60 FPS.
    • So it's impressively easy to reset the value received from oscEvent() back to -1 before it had a chance to be processed later within draw(), given each function is concurrently executed by their own threads! #-o
    • Well, here's my proposed fix this time... 8-|

    if (gestureClassLabel >= 0) {
      switch (gestureClassLabel) {
        // ... all your case: conditions below:
      }
    
      gestureClassLabel = -1;
    }
    
  • @GoToLoop Also makes sense! Still the same :/ Wonder if adjusting the sketch's framerate will help...

  • So, it seems that swapping out switch/case for an if/else block seems to be doing the trick...

    physics.update();
    
      if (gestureClassLabel == 0) {
          println("GESTURE 0");
          addGestureZeroAttraction();
          gestureClassLabel = -1;
      } else if (gestureClassLabel == 1) {
          println("GESTURE 1");
          addGestureOneAttraction();
          gestureClassLabel = -1;
      } else if (gestureClassLabel == 2) {
          println("GESTURE 2");
          resetAttraction();
          gestureClassLabel = -1;
      }
    
  • edited June 2017 Answer ✓
    • Glad you've finally made it. Congrats! <:-P
    • But I'm puzzled why a chain of else if {} would work while switch () { case: } wouldn't! :-/
    • Then I've decided to refactor the whole code. #:-S
    • Given I can't have any inputs from OSC, I can only use mouse for such task here. 8-|
    • Maybe it will for ya this time if you run the refactored sketch I hope. O:-)

    /**
     * Attraction2D + OSC (v1.2.3)
     * Copyright © 2010 Karsten Schmidt
     * Mods Narner & GoToLoop (2017-Jun-14)
     *
     * GNU.org/licenses/lgpl-2.1.html
     *
     * forum.Processing.org/two/discussion/22940/
     * dynamically-controlled-particle-system-with-toxiclibs#Item_12
     */
    
    import toxi.geom.Vec2D;
    import toxi.geom.Rect;
    
    import toxi.physics2d.VerletPhysics2D;
    import toxi.physics2d.VerletParticle2D;
    
    import toxi.physics2d.behaviors.ConstantForceBehavior2D;
    import toxi.physics2d.behaviors.AttractionBehavior2D;
    
    import oscP5.OscP5;
    import oscP5.OscMessage;
    
    static final boolean MOUSE = true;
    static final float STRENGTH = .9, DRAGGING = .05, GRAVITY = .15;
    static final int PARTICLES = 600, DIAM = 5, AREA = 250;
    static final int SMOOTH = 2, FPS = 60, PORT = 6000;
    
    int oscGesture = -1;
    color bg = #008000;
    
    final VerletPhysics2D physics = new VerletPhysics2D();
    
    final AttractionBehavior2D attractor =
      new AttractionBehavior2D(new Vec2D(), AREA, 0);
    
    final StringBuilder sb = new StringBuilder(30);
    
    void setup() {
      size(680, 382, FX2D);
      smooth(SMOOTH);
      frameRate(FPS);
    
      colorMode(RGB);
      blendMode(REPLACE);
    
      stroke(-1);
      strokeWeight(DIAM);
    
      physics.particles.ensureCapacity(PARTICLES);
      ((ArrayList<?>) physics.behaviors).ensureCapacity(PARTICLES + 2);
    
      physics.setWorldBounds(new Rect(0, 0, width, height)).setDrag(DRAGGING);
      physics.addBehavior(new ConstantForceBehavior2D(new Vec2D(0, GRAVITY)));
      physics.addBehavior(attractor);
    
      new OscP5(this, PORT);
      registerMethod("pre", this);
    }
    
    void pre() {
      Vec2D v = Vec2D.randomVector().scaleSelf(DIAM).addSelf(width>>1, DIAM<<2);
      VerletParticle2D p = new VerletParticle2D(v);
    
      physics.addParticle(p)
        .addBehavior(new AttractionBehavior2D(p, DIAM<<2, -1.2, .01));
    
      if (physics.particles.size() == PARTICLES)  unregisterMethod("pre", this);
    }
    
    void draw() {
      processChosenGesture(oscGesture);
      //processChosenGesture((int) random(20));
    
      background(bg);
      for (final Vec2D p : physics.update().particles)  point(p.x, p.y);
    
      displayInfoTitle()
    }
    
    void mousePressed() {
      if (MOUSE) {
        attractor.getAttractor().set(mouseX, mouseY);
        attractor.setStrength(STRENGTH);
        bg = #800000;
      }
    }
    
    void mouseDragged() {
      if (MOUSE)  attractor.getAttractor().set(mouseX, mouseY);
    }
    
    void mouseReleased() {
      if (MOUSE)  disableGestureAttraction();
    }
    
    void oscEvent(final OscMessage msg) {
      oscGesture = msg.get(0).intValue();
      println("Received gesture was:", oscGesture);
    }
    
    void displayInfoTitle() {
      sb.setLength(0);
    
      sb.append("Particles: ").append(physics.particles.size())
        .append("  -  FPS: ").append(round(frameRate));
    
      getSurface().setTitle(sb.toString());
    }
    
    void processChosenGesture(final int gesture) {
      if (gesture < 0)  return;
    
      oscGesture = -1;
      println("Gesture to process is:", gesture);
    
      switch (gesture) {
      case 0:
        activateGestureAttraction(); 
        break;
      case 1:
        setRandomPositionForAttractor(); 
        break;
      case 2:
        disableGestureAttraction();
      }
    }
    
    void activateGestureAttraction() {
      attractor.getAttractor().set(width>>1, height>>1);
      attractor.setStrength(STRENGTH);
      bg = #FF0000;
    }
    
    void setRandomPositionForAttractor() {
      attractor.getAttractor()
        .set(random(DIAM, width - DIAM), random(DIAM, height - DIAM));
    }
    
    void disableGestureAttraction() {
      attractor.setStrength(0);
      bg = #0000FF;
    }
    
  • That worked amazingly @GoToLoop...thank you so much for the help!!!

Sign In or Register to comment.