Working with Toxiclibs


Hey everyone! Working on some projects, more soon! Right now I’d like to share two Toxiclibs code examples I made to learn this excellent library by Karsten Schmidt. Both were made in response to questions posted in the Processing forum. The first example deals with picking and dragging shapes, the second with making shapes explode. As it turns out, Toxiclibs has many useful functions to realise these things quickly, clearly and efficiently. The first example makes use of the basic functionalities. The second example uses more adventurous options such as physics-based particles and Voronoi tessellation. Creating these examples also allowed me to brush up on my vector skills since Toxiclibs has an advanced Vec2D class with many additional options for vector math manipulations. So I definitely learned a few things from making these examples and I hope they’re useful for others as well.

Example 1: Creating, Picking & Dragging Shapes
This is a pretty basic example but it contains a few interesting facets. With the Toxiclibs library, specifically the containsPoint function, it’s very easy to check if a certain point is within a shape. This is useful for many different things, but picking is definitely one of them. This function exists for all the Shape2D shapes, so it can be used quite broadly. In this example you can create, pick and drag Polygon2D shapes. When creating a shape, the left mouse button only adds a point, while the right mouse button finalizes the shape once there are more than two points. So if you only want to make triangles, just keep using the right mouse button. With regard to keys: space clears everything, d deletes the shape under the mouse and x deletes the last point. There are a lot of little tricks and solutions to make everything work as intended, check the full commented code to find out. You can copy-paste and run it in Processing’s pde directly.

import toxi.geom.*;
import toxi.processing.*;

ArrayList <Polygon2D> polygons = new ArrayList <Polygon2D> ();
ArrayList <Vec2D> points = new ArrayList <Vec2D> ();
int draggedPolygon = -1;
ToxiclibsSupport gfx;
boolean onPolygon;
Vec2D mouse;

void setup() {
  size(1280,720);
  gfx = new ToxiclibsSupport(this);
  noStroke();
  smooth();
}

void draw() {
  background(255);
  mouse = new Vec2D(mouseX,mouseY);

  // (re)set onPolygon to false
  onPolygon = false;

  // draw all the polygons
  for (Polygon2D p : polygons) {
    if (p.containsPoint(mouse)) {
      // if the mouse is over a polygon...
      // set onPolygon to true and color it red
      onPolygon = true;
      fill(255,0,0);
    } else {
      fill(0);
    }
    gfx.polygon2D(p);
  }

  // draw all the points
  fill(0);
  for (Vec2D p : points) {
    ellipse(p.x,p.y,5,5);
  }
}

void mousePressed() {
  // if the mouse is NOT on a polygon
  if (!onPolygon) {
    // add a point at mouseX,mouseY
    points.add(mouse);
    // if the right mouse button is pressed
    // and there are more than 2 points
    if (mouseButton == RIGHT && points.size() > 2) {
      // create a polygon from the points
      polygons.add(new Polygon2D(points));
      // clear the points
      points.clear();
    }
  }
}

void mouseDragged() {
  // if no polygon is selected
  if (draggedPolygon == -1) {
    // check if the mouse is on a polygon
    for (int i=0; i<polygons.size(); i++) {
      if (polygons.get(i).containsPoint(mouse)) {
        // if so, set this to be the selected polygon
        draggedPolygon = i;
      }
    }
  // if a polygon is selected
  } else {
    // set change amount to the movement of the mouse
    Vec2D change = new Vec2D(mouseX-pmouseX,mouseY-pmouseY);
    // get the selected polygon
    Polygon2D p = polygons.get(draggedPolygon);
    // add the change to all of it's individual points
    for (Vec2D v : p.vertices) {
      v.addSelf(change);
    }
  }
}

void mouseReleased() {
  // on mouse release reset to 'no polygon selected'
  draggedPolygon = -1;
}

void keyPressed() {
  // clear all points and polygons
  if (key == ' ' && !mousePressed) {
    points.clear();
    polygons.clear();
  }
  // delete the polygon under the mouse
  if (key == 'd' && !mousePressed) {
    for (int i=polygons.size()-1; i>=0; i--) {
      if (polygons.get(i).containsPoint(mouse)) {
        polygons.remove(i);
      }
    }
  }
  // remove the last point (if points > 0)
  if (key == 'x') {
    if (points.size() > 0) {
      points.remove(points.size()-1);
    }
  }
}

Example 2: Exploding Circles (Voronoi style)
The next example is in many ways more advanced. First, it deals with physics and verletParticles to create a lot of different-sized circles all within a system of counteracting forces, where the force of each particle is in line with the radius of that circle. Most of this is handled internally by the Toxiclibs library. So that’s step one. The next step is the creative destruction of a circle. Once the breaking commences, the “circle” is divided in many segments using Voronoi tessellation. Currently the only polygon clipping available in the Toxiclibs library, is rectangular. Therefore I’ve used some vector tricks to clip points outside the radius of the circle. The different fragments are loaded into an arrayList of Polygon2D shapes that move away from their initial position. To make things visually more interesting, the fragments move away at different speeds and more importantly their direction depends on the relative angle to the point-of-impact. In addition, the point-of-impact influences the Voronoi tessellation. Close to the impact there are multiple, smaller fragments, while further away the fragments get bigger. All in all this creates a fairly realistic and interesting destruction.

A few things keep the sketch running at a decent framerate. First of all, only breaking shapes are made out of Voronoi fragments. Secondly, the transparency of the breaking shape starts decreasing after a few frames. Once it is completely invisible, it’s removed entirely. This makes the code fairly efficient, although it is always possible to further optimize of course. For convenience, I’ve placed everything in one class. The showcase sketch starts with a single shape in the center of the screen. Click somewehere on the circle to make it explode. As stated before, the point-of-impact determines the tessellation and the direction of the moving fragments. For every shape that is removed, two shapes return! So things will get crowdy real soon. Therefore I’ve maximized the amount of circles on the screen. Below is the full commented code for both the main program and the BreakCircle class. Copy-paste them both in Processing’s pde and you will be able to run this example.

Main Program

import toxi.geom.*;
import toxi.geom.mesh2d.*;
import toxi.physics2d.*;
import toxi.physics2d.behaviors.*;
import toxi.util.datatypes.*;
import toxi.processing.*;

ArrayList <BreakCircle> circles = new ArrayList <BreakCircle> ();
VerletPhysics2D physics;
ToxiclibsSupport gfx;
FloatRange radius;
Vec2D origin, mouse;

int maxCircles = 90; // maximum amount of circles on the screen
int numPoints = 50;  // number of voronoi points / segments
int minSpeed = 2;    // minimum speed of a voronoi segment
int maxSpeed = 14;   // maximum speed of a voronoi segment

void setup() {
  size(1280,720);
  smooth();
  noStroke();
  gfx = new ToxiclibsSupport(this);
  physics = new VerletPhysics2D();
  physics.setDrag(0.05f);
  physics.setWorldBounds(new Rect(0,0,width,height));
  radius = new BiasedFloatRange(30, 100, 30, 0.6f);
  origin = new Vec2D(width/2,height/2);
  reset();
}

void draw() {
  removeAddCircles();
  background(255,0,0);
  physics.update();

  mouse = new Vec2D(mouseX,mouseY);
  for (BreakCircle bc : circles) {
    bc.run();
  }
}

void removeAddCircles() {
  for (int i=circles.size()-1; i>=0; i--) {
    // if a circle is invisible, remove it...
    if (circles.get(i).transparency < 0) {
      circles.remove(i);
      // and add two new circles (if there are less than maxCircles)
      if (circles.size() < maxCircles) {
        circles.add(new BreakCircle(origin,radius.pickRandom()));
        circles.add(new BreakCircle(origin,radius.pickRandom()));
      }
    }
  }
}

void keyPressed() {
  if (key == ' ') { reset(); }
}

void reset() {
  // remove all physics elements
  for (BreakCircle bc : circles) {
    physics.removeParticle(bc.vp);
    physics.removeBehavior(bc.abh);
  }
  // remove all circles
  circles.clear();
  // add one circle of radius 200 at the origin
  circles.add(new BreakCircle(origin,200));
}

BreakCircle class

class BreakCircle {
  ArrayList <Polygon2D> polygons = new ArrayList <Polygon2D> ();
  Voronoi voronoi;
  FloatRange xpos, ypos;
  PolygonClipper2D clip;
  float[] moveSpeeds;
  Vec2D pos, impact;
  float radius;
  int transparency;
  int start;
  VerletParticle2D vp;
  AttractionBehavior abh;
  boolean broken;

  BreakCircle(Vec2D pos, float radius) {
    this.pos = pos;
    this.radius = radius;
    vp = new VerletParticle2D(pos);
    abh = new AttractionBehavior(vp, radius*2.5 + max(0,50-radius), -1.2f, 0.01f);
    physics.addParticle(vp);
    physics.addBehavior(abh);
  }

  void run() {
    // for regular (not broken) circles
    if (!broken) {
      moveVerlet();
      displayVerlet();
      checkBreak();
    // if the circle is broken
    } else {
      moveBreak();
      displayBreak();
    }
  }

  // set position based on the particle in the physics system
  void moveVerlet() {
    pos = vp;
  }

  // display circle
  void displayVerlet() {
    fill(255);
    gfx.circle(pos,radius*2);
  }

  // if the mouse is pressed on a circle, it will be broken
  void checkBreak() {
    if (mouse.isInCircle(pos,radius) && mousePressed) {
      // remove particle + behavior in the physics system
      physics.removeParticle(vp);
      physics.removeBehavior(abh);
      // point of impact is set to mouseX,mouseY
      impact = mouse;
      initiateBreak();
    }
  }

  void initiateBreak() {
    broken = true;
    transparency = 255;
    start = frameCount;
    // create a voronoi shape
    voronoi = new Voronoi();
    // set biased float ranges based on circle position, radius and point of impact
    xpos = new BiasedFloatRange(pos.x-radius, pos.x+radius, impact.x, 0.333f);
    ypos = new BiasedFloatRange(pos.y-radius, pos.y+radius, impact.y, 0.5f);
    // set clipping based on circle position and radius
    clip = new SutherlandHodgemanClipper(new Rect(pos.x-radius, pos.y-radius, radius*2, radius*2));
    addPolygons();
    addSpeeds();
  }

  void addPolygons() {
    // add random points (biased towards point of impact) to the voronoi
    for (int i=0; i<numPoints; i++) {
      voronoi.addPoint(new Vec2D(xpos.pickRandom(), ypos.pickRandom()));
    }
    // generate polygons from voronoi segments
    for (Polygon2D poly : voronoi.getRegions()) {
      // clip them based on the rectangular clipping
      poly = clip.clipPolygon(poly);
      for (Vec2D v : poly.vertices) {
        // if a point is outside the circle
        if (!v.isInCircle(pos,radius)) {
          // scale it's distance from the center to the radius
          clipPoint(v);
        }
      }
      polygons.add(new Polygon2D(poly.vertices));
    }
  }

  void addSpeeds() {
    // generate random speeds for all polygons
    moveSpeeds = new float[polygons.size()];
    for (int i=0; i<moveSpeeds.length; i++) {
      moveSpeeds[i] = random(minSpeed,maxSpeed);
    }
  }

  // move polygons away from the point of impact at their respective speeds
  void moveBreak() {
    for (int i=0; i<polygons.size(); i++) {
      Polygon2D poly = polygons.get(i);
      Vec2D centroid = poly.getCentroid();
      Vec2D targetDir = centroid.sub(impact).normalize();
      targetDir.scaleSelf(moveSpeeds[i]);
      for (Vec2D v : poly.vertices) {
        v.set(v.addSelf(targetDir));
      }
    }
  }

  // draw the polygons
  void displayBreak() {
    // after 12 frames, start decreasing the transparency
    if (frameCount-start > 12) { transparency -= 7; }
    fill(255,transparency);
    for (Polygon2D poly : polygons) {
      gfx.polygon2D(poly);
    }
  }

  void clipPoint(Vec2D v) {
    v.subSelf(pos);
    v.normalize();
    v.scaleSelf(radius);
    v.addSelf(pos);
  }
}
About these ads
Comments
7 Responses to “Working with Toxiclibs”
  1. toxi says:

    That’s my Easter present! :) Can’t thank you enough for doing this, Amnon! 10^3 thanks! Both are very good & clear examples and it’d be amazing if I could bundle them (fully credited) with the libs starting with the next release…

    • Amnon says:

      Absolutely, that would be great. Can’t wait for the next release. Hopefully I can make some more cool stuff with it in the future, be it code examples, interesting visuals or both. Thank YOU for all the hard work on the libs! :)

  2. felix says:

    The Voronoi explosions are great!

    • Amnon says:

      Thanks Felix! In the next release of Toxiclibs it should be even easier, cause there will be custom polyon clipping. I’ll probably do some more destruction then. ;-)

Trackbacks
Check out what others are saying...
  1. [...] 2). The full source code and a more detailed explanation of those two examples can be found HERE. In this follow-up I will share the source code for the two brand new examples you see in the [...]



Follow

Get every new post delivered to your Inbox.

Join 108 other followers

%d bloggers like this: