How can I control my particles in one window from another window via the mouse?

edited December 2016 in Library Questions

I am not sure if I am missing something specific to the toxi library, but I keep getting a ConcurrentModificationException when I try to control my particles in my secondary window via the mouse in my primary window. The crash seems to be random, sometimes it crashes on one click, sometimes I can drag the particles around for a second or two either way I can;t seem to figure it out. Can anyone help, please?

Particles p;
AttractionBehavior mouseAttractorP;
Vec2D mousePos;
FinalPanel tp;

void setup() {
  size(1024, 768, P3D);
  frameRate(60);
  tp = new FinalPanel(400,400);
  surface.setTitle("Touch Panel");

}

void draw() {
  background(0, 0, 0);
}



void mousePressed() {
  mousePos = new Vec2D(mouseX, mouseY);

  if (mouseButton == LEFT) {
    // create a new attraction force field around the mouse position
    mouseAttractorP = new AttractionBehavior(mousePos, 250, 7);
    p.physics.addBehavior(mouseAttractorP);
  }

  if (mouseButton == RIGHT) {
    // create a new repulsion force field around the mouse position
    mouseAttractorP = new AttractionBehavior(mousePos, -33, -1);
    p.physics.addBehavior(mouseAttractorP);
  }
}

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

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


import toxi.geom.*;
import toxi.physics2d.*;
import toxi.physics2d.behaviors.*;
import toxi.math.*;
import megamu.mesh.*;



class Particles {
  final PApplet g;
  int NUM_PARTICLES;
  VerletPhysics2D physics;
  AttractionBehavior mouseAttractor;
  float [][] pos;
  float eSize;
  float xOff;

  Delaunay delaunay;
  int [] polygons;


  Particles(PApplet _p) {
    g = _p;
    xOff = 0;


    NUM_PARTICLES = 666;
    eSize = 1;
    physics = new VerletPhysics2D();
    physics.setDrag(0.7);
    physics.setWorldBounds(new Rect(0, 0, width, height));
    physics.addBehavior(new GravityBehavior(new Vec2D(-0.00007, -0.0003f)));
    for (int i = 0; i<NUM_PARTICLES; i++) {
      addParticle();
    }
  }


  void addParticle() {
    VerletParticle2D p = new VerletParticle2D(Vec2D.randomVector().scale(133).addSelf(width / 2, height/2));
    physics.addParticle(p);
    // add a negative attraction force field around the new particle
    physics.addBehavior(new AttractionBehavior(p, 13, 0.3f, 3f));
  }

  void drawParticles() {
    // store particles delaunayitions to do delaunay triangulation
    pos = new float[NUM_PARTICLES][2];

    for ( int i=0; i<NUM_PARTICLES; i++) {
      // particle system using verlet integration
      VerletParticle2D p = physics.particles.get(i);
      g.pushStyle();
      g.fill(255, 33);
      g.ellipse(p.x, p.y, g.noise(xOff) * 13, g.noise(xOff) * 13);
      g.popStyle();

      pos[i][0] = physics.particles.get(i).x;
      pos[i][1] = physics.particles.get(i).y;
    }
  }


  // delaunay triangulation logic taken from here : 
  // http://www.openprocessing.org/sketch/43503
  void drawLines() {
    // delaunay triangulation
    delaunay = new Delaunay(pos);
    // getEdges returns a 2 dimensional array for the lines
    float[][] edges = delaunay.getEdges();
    for (int i=0; i<edges.length; i++)
    {
      // use the edges values to draw the lines
      float startX = edges[i][0];
      float startY = edges[i][1];
      float endX = edges[i][2];
      float endY = edges[i][3];
      float distance = dist(startX, startY, endX, endY);
      // remap the distance to opacity values
      float trans = 255-map(distance, 0, 33, 33, 0);
      // stroke weight based on distance
      // fast invert square root helps for performance
      float sw = 3f/sqrt(distance*2);
      g.ellipseMode(CENTER);

      g.pushStyle();
      g.strokeWeight(sw);
      g.stroke(g.noise(1)*233);
      g.fill(g.random(255), g.random(255), g.random(255));
      g.noFill();
      g.ellipse(startX, startY, eSize, eSize);
      g.popStyle();
      //pushStyle();
      //stroke(200, 3);
      //noFill();
      //bezier(startX, startY, endX, mouseY, width/2, mouseY, width/2, height/2);
      //popStyle();
    }
  }
}



class FinalPanel extends PApplet {
  int w, h;

  public FinalPanel(int _w, int _h) {
    super(); 
    w = _w;
    h = _h;
    PApplet.runSketch(new String[]{this.getClass().getName()}, this);
  }

  public void settings() {
    size(w, h, P3D);
  }

  public void setup() {
    blendMode(ADD);
    surface.setTitle("Final Panel");
    surface.setLocation(0, 0);
    p = new Particles(this);
  }

  void draw() {
    background(0);
    p.physics.update();
    p.drawParticles();
    p.drawLines();
  }
}

Answers

  • I'd say there's a good chance this has something to do with multiple windows and the OpenGL renderer - there's a whole load of stuff in there that assumes there's only window. It's a shame as it's fairly easily fixed.

  • It is not a problem withe the toxiclibs library it is caused because each window is running in its own thread but they are trying to share the same data p. So there will be times when both threads are trying to modify/use p at the same time, hence the exception. It has nothing

    One way to do it without having to worry about synchronizing threads is to move all the code that modifies p into the second window.

    Note:
    Unfortunately I can't run your program as it doesn't recognise
    import megamu.mesh.*;
    or the AttractionBehaviour class so what follows is un-tested. I am also assuming you are using PS3.

    Modify the FinalPanal class to. Notice that adding and removing behaviours are now performed in the pre method. This is called automatically just before each call to draw

    class FinalPanel extends PApplet {
      int w, h;
      AttractionBehaviour toAdd = null, toRemove = null;
    
      public FinalPanel(int _w, int _h) {
        super(); 
        w = _w;
        h = _h;
        PApplet.runSketch(new String[]{this.getClass().getName()}, this);
      }
    
      public void settings() {
        size(w, h, P3D);
      }
    
      public void setup() {
        blendMode(ADD);
        surface.setTitle("Final Panel");
        surface.setLocation(0, 0);
        p = new Particles(this);
        registerMethod("pre", this);
      }
    
      void pre() {
        if (toAdd != null) {
          p.physics.addBehavior(toAdd);
          toAdd = null;
        }
        if (toRemove != null) {
          p.physics.removeBehavior(toRemove);
          toRemove = null;
        }
      }
    
      void draw() {
        background(0);
        p.physics.update();
        p.drawParticles();
        p.drawLines();
      }
    }
    

    Now we need to modify the mouse event handlers in the main window.

    void mousePressed() {
      mousePos = new Vec2D(mouseX, mouseY);
    
      if (mouseButton == LEFT) {
        // create a new positive attraction force field around the mouse position (radius=250px)
        mouseAttractorP = new AttractionBehavior(mousePos, 250, 7);
        tp.toAdd = mouseAttractorP;
      }
    
      if (mouseButton == RIGHT) {
        // create a new positive attraction force field around the mouse position (radius=250px)
        mouseAttractorP = new AttractionBehavior(mousePos, -33, -1);
        tp.toAdd = mouseAttractorP;
      }
    }
    
    void mouseReleased() {
      // remove the mouse attraction when button has been released
      tp.toRemove = mouseAttractorP;
    }
    

    Notice that we are no longer modifying p in this window.

    As I said I have not been able to test this code so I can't guarantee that it will work.

  • @quark ... I see what you are saying. I stumbled across your G4P library and was thinking I needed to pass my data from one window to the other, was just unsure of how to do that.

    I tried your code (there is a slight typo in the FinalPanel class ... AttractionBehavior toAdd = null, toRemove = null; ... <---fixed) and I can get it to run, but the second window isn't recognizing the mouse event.

    I noticed your example Showcase allows you to control one window from the other, I am going to try to break down that program to see if I can figure this out for mine. Thanks for your help.

  • the second window isn't recognizing the mouse event.

    try changing line 24 to

    public void pre() {

    also put a println inside this method to confirm that it is being executed every frame.

  • making that public didn't help, I also went ahead and made the mouseEvents public as well ... no luck.

    doesn't seem to be executing. added println but see nothing in the console.

  • Answer ✓

    You also need to make the class public i.e.

    public class FinalPanel extends PApplet {

  • @quark ... that was it! I had my constructor set to public and failed to set my class to public also.

    At the moment tho, I am failing to receive my mouseEvent for the if (mouseButton == RIGHT) {} in the secondary window.

    I am at least making progress and I thank you greatly for your help!

  • Again put a println inside the if statement.

    println is your friend :)

  • @quark ... yes, something I need to be using more often!

    anyways, I got it working. My force amount was too low to notice. All is good now. Thank you, your help is much appreciated!

    working code below ...

    Particles p;
    AttractionBehavior mouseAttractorP;
    Vec2D mousePos;
    FinalPanel tp;
    
    
    void settings() {
      size(600, 400, P3D);
    }
    
    
    void setup() {
    
      frameRate(60);
      tp = new FinalPanel(600, 400);
      surface.setTitle("Touch Panel");
    }
    
    void draw() {
      background(0, 0, 0);
    }
    
    
    
    void mousePressed() {
      mousePos = new Vec2D(mouseX, mouseY);
    
      if (mouseButton == LEFT) {
        // create a new positive attraction force field around the mouse position
        mouseAttractorP = new AttractionBehavior(mousePos, 250, 7);
        tp.toAdd = mouseAttractorP;
    
        println("mousePressed_LEFT");
      }
    
      if (mouseButton == RIGHT) {
        // create a new negative attraction force field around the mouse position 
        mouseAttractorP = new AttractionBehavior(mousePos, -250, -7);
        tp.toAdd = mouseAttractorP;
    
        println("mousePressed_RIGHT");
      }
    }
    
    void mouseDragged() {
      // update mouse attraction focal point
      mousePos.set(mouseX, mouseY);
    
      println("mouseDragged");
    }
    
    void mouseReleased() {
      // remove the mouse attraction when button has been released
      tp.toRemove = mouseAttractorP;
    
      println("mouseReleased");
    }
    
    
    import toxi.geom.*;
    import toxi.physics2d.*;
    import toxi.physics2d.behaviors.*;
    import toxi.math.*;
    import megamu.mesh.*;
    
    
    
    class Particles {
      final PApplet g;
      int NUM_PARTICLES;
      VerletPhysics2D physics;
      AttractionBehavior mouseAttractor;
      float [][] pos;
      float eSize;
      float xOff;
    
      Delaunay delaunay;
      int [] polygons;
    
    
      Particles(PApplet _p) {
        g = _p;
        xOff = 0;
    
    
        NUM_PARTICLES = 666;
        eSize = 1;
        physics = new VerletPhysics2D();
        physics.setDrag(0.7);
        physics.setWorldBounds(new Rect(0, 0, width, height));
        physics.addBehavior(new GravityBehavior(new Vec2D(-0.00007, -0.0003f)));
        for (int i = 0; i<NUM_PARTICLES; i++) {
          addParticle();
        }
      }
    
    
      void addParticle() {
        VerletParticle2D p = new VerletParticle2D(Vec2D.randomVector().scale(133).addSelf(width / 2, height/2));
        physics.addParticle(p);
        // add a negative attraction force field around the new particle
        physics.addBehavior(new AttractionBehavior(p, 13, 0.3f, 3f));
      }
    
      void drawParticles() {
        // store particles delaunayitions to do delaunay triangulation
        pos = new float[NUM_PARTICLES][2];
    
        for ( int i=0; i<NUM_PARTICLES; i++) {
          // particle system using verlet integration
          VerletParticle2D p = physics.particles.get(i);
          g.pushStyle();
          g.fill(255, 33);
          g.ellipse(p.x, p.y, g.noise(xOff) * 13, g.noise(xOff) * 13);
          g.popStyle();
    
          pos[i][0] = physics.particles.get(i).x;
          pos[i][1] = physics.particles.get(i).y;
        }
      }
    
    
      // delaunay triangulation logic taken from here : 
      // <a href="http://www.openprocessing.org/sketch/43503" target="_blank" rel="nofollow">http://www.openprocessing.org/sketch/43503</a>;
      void drawLines() {
        // delaunay triangulation
        delaunay = new Delaunay(pos);
        // getEdges returns a 2 dimensional array for the lines
        float[][] edges = delaunay.getEdges();
        for (int i=0; i<edges.length; i++)
        {
          // use the edges values to draw the lines
          float startX = edges[i][0];
          float startY = edges[i][1];
          float endX = edges[i][2];
          float endY = edges[i][3];
          float distance = dist(startX, startY, endX, endY);
          // remap the distance to opacity values
          float trans = 255-map(distance, 0, 33, 33, 0);
          // stroke weight based on distance
          // fast invert square root helps for performance
          float sw = 3f/sqrt(distance*2);
          g.ellipseMode(CENTER);
    
          g.pushStyle();
          g.strokeWeight(sw);
          g.stroke(g.noise(1)*233);
          g.fill(g.random(255), g.random(255), g.random(255));
          g.noFill();
          g.ellipse(startX, startY, eSize, eSize);
          g.popStyle();
          //pushStyle();
          //stroke(200, 3);
          //noFill();
          //bezier(startX, startY, endX, mouseY, width/2, mouseY, width/2, height/2);
          //popStyle();
        }
      }
    }
    
    
    import processing.opengl.*;
    
    public class FinalPanel extends PApplet {
      int w, h;
      AttractionBehavior toAdd = null, toRemove = null;
    
    
      public FinalPanel(int _w, int _h) {
        super(); 
        w = _w;
        h = _h;
        PApplet.runSketch(new String[]{this.getClass().getName()}, this);
      }
    
      public void settings() {
        size(w, h, P3D);
      }
    
      public void setup() {
        blendMode(ADD);
        surface.setTitle("Final Panel");
        surface.setLocation(0, 0);
        p = new Particles(this);
        registerMethod("pre", this);
      }
    
      public void pre() {
        if (toAdd != null) {
          p.physics.addBehavior(toAdd);
          toAdd = null;
          println("mousePressed_LEFT_test");
        }
        if (toRemove != null) {
          p.physics.removeBehavior(toRemove);
          toRemove = null;
          println("mousePressed_RIGHT_test");
        }
      }
      public void draw() {
        background(0);
        p.physics.update();
        p.drawParticles();
        p.drawLines();
      }
    }
    
  • Glad I could help

Sign In or Register to comment.