• fullscreen
  • BoxFluidDemo.pde
  • GUI.pde
  • Mesh.pde
  • Physics.pde
  • /**
     * <p>BoxFLuid demo combining 3D physics particles with the IsoSurface class to
     * create an animated mesh with a fluid behaviour. The mesh is optionally created
     * within a boundary sphere, but other forms can be created using a custom
     * ParticleConstraint class.</p>
     * 
     * <p>Dependencies:</p>
     * <ul>
     * <li>toxiclibscore-0015 or newer package from <a href="http://toxiclibs.org">toxiclibs.org</a></li>
     * <li>verletphysics-0004 or newer package from <a href="http://toxiclibs.org">toxiclibs.org</a></li>
     * <li>volumeutils-0002 or newer package from <a href="http://toxiclibs.org">toxiclibs.org</a></li>
     * <li>controlP5 GUI library from <a href="http://sojamo.de">sojamo.de</a></li>
     * </ul>
     * 
     * <p>Key controls:</p>
     * <ul>
     * <li>w : wireframe on/off</li>
     * <li>c : close sides on/off</li>
     * <li>p : show particles only on/off</li>
     * <li>b : turn bounding sphere on/off</li>
     * <li>r : reset particles</li>
     * <li>s : save current mesh as OBJ & STL format</li>
     * <li>- / = : decrease/increase surface threshold/tightness</li>
     * </ul>
     */
    
    /* 
     * Copyright (c) 2009 Karsten Schmidt
     * 
     * This demo & library is free software; you can redistribute it and/or
     * modify it under the terms of the GNU Lesser General Public
     * License as published by the Free Software Foundation; either
     * version 2.1 of the License, or (at your option) any later version.
     * 
     * http://creativecommons.org/licenses/LGPL/2.1/
     * 
     * This library is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     * Lesser General Public License for more details.
     * 
     * You should have received a copy of the GNU Lesser General Public
     * License along with this library; if not, write to the Free Software
     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
     */
    
    import toxi.physics.constraints.*;
    import toxi.physics.*;
    import toxi.geom.*;
    import toxi.geom.mesh.*;
    import toxi.math.*;
    import toxi.volume.*;
    
    import controlP5.*;
    
    int NUM_PARTICLES = 200;
    float REST_LENGTH=375;
    int DIM=200;
    
    int GRID=18;
    float VS=2*DIM/GRID;
    Vec3D SCALE=new Vec3D(DIM,DIM,DIM).scale(2);
    float isoThreshold=3;
    
    int numP;
    VerletPhysics physics;
    ParticleConstraint boundingSphere;
    
    VolumetricSpace volume;
    IsoSurface surface;
    
    TriangleMesh mesh=new TriangleMesh("fluid");
    
    boolean showPhysics=false;
    boolean isWireFrame=false;
    boolean isClosed=true;
    boolean useBoundary=false;
    
    Vec3D colAmp=new Vec3D(400, 200, 200);
    
    void setup() {
      size(680,382,P3D);
      initPhysics();
      initGUI();
      volume=new VolumetricSpace(SCALE,GRID,GRID,GRID);
      surface=new IsoSurface(volume);
      textFont(createFont("SansSerif",12));
    }
    
    void draw() {
      updateParticles();
      computeVolume();
      background(224);
      pushMatrix();
      translate(width/2,height*0.5,0);
      rotateX(mouseY*0.01);
      rotateY(mouseX*0.01);
      scale(0.5);
      noFill();
      stroke(255,192);
      strokeWeight(1);
      box(physics.getWorldBounds().getExtent().x*2);
      if (showPhysics) {
        strokeWeight(4);
        stroke(0);
        for(Iterator i=physics.particles.iterator(); i.hasNext();) {
          VerletParticle p=(VerletParticle)i.next();
          Vec3D col=p.add(colAmp).scaleSelf(0.5);
          stroke(col.x,col.y,col.z);
          point(p.x,p.y,p.z);
        }
      } 
      else {
        ambientLight(216, 216, 216);
        directionalLight(255, 255, 255, 0, 1, 0);
        directionalLight(96, 96, 96, 1, 1, -1);
        if (isWireFrame) {
          stroke(255);
          noFill();
        } 
        else {
          noStroke();
          fill(224,0,51);
        }
        beginShape(TRIANGLES);
        if (!isWireFrame) {
          drawFilledMesh();
        } 
        else {
          drawWireMesh();
        }
        endShape();
      }
      popMatrix();
      noLights();
      fill(0);
      text("faces: "+mesh.getNumFaces(),20,600);
      text("vertices: "+mesh.getNumVertices(),20,615);
      text("particles: "+physics.particles.size(),20,630);
      text("springs: "+physics.springs.size(),20,645);
      text("fps: "+frameRate,20,690);
      ui.draw();
    }
    
    void keyPressed() {
      if (key=='r') initPhysics();
      if (key=='w') isWireFrame=!isWireFrame;
      if (key=='p') showPhysics=!showPhysics;
      if (key=='c') isClosed=!isClosed;
      if (key=='b') {
        toggleBoundary();
      }
      if (key=='-' || key=='_') {
        isoThreshold-=0.001;
      }
      if (key=='=' || key=='+') {
        isoThreshold+=0.001;
      }
      if (key=='s') {
        mesh.saveAsOBJ(sketchPath(mesh.name+".obj"));
        mesh.saveAsSTL(sketchPath(mesh.name+".stl"));
      }
    }
    
    void toggleBoundary() {
      useBoundary=!useBoundary;
      initPhysics();
    }
    
    
    ControlP5 ui;
    
    void initGUI() {
      ui = new ControlP5(this);
      ui.addSlider("isoThreshold",1,12,isoThreshold,20,20,100,14).setLabel("iso threshold");
    
      ui.addToggle("showPhysics",showPhysics,20,60,14,14).setLabel("show particles");
      ui.addToggle("isWireFrame",isWireFrame,20,100,14,14).setLabel("wireframe");
      ui.addToggle("isClosed",isClosed,20,140,14,14).setLabel("closed mesh");
      ui.addToggle("toggleBoundary",useBoundary,20,180,14,14).setLabel("use boundary");
    
      ui.addBang("initPhysics",20,240,28,28).setLabel("restart");
    }
    
    
    
    void computeVolume() {
      float cellSize=(float)DIM*2/GRID;
      Vec3D pos=new Vec3D();
      Vec3D offset=physics.getWorldBounds().getMin();
      float[] volumeData=volume.getData();
      for(int z=0,index=0; z<GRID; z++) {
        pos.z=z*cellSize+offset.z;
        for(int y=0; y<GRID; y++) {
          pos.y=y*cellSize+offset.y;
          for(int x=0; x<GRID; x++) {
            pos.x=x*cellSize+offset.x;
            float val=0;
            for(int i=0; i<numP; i++) {
              Vec3D p=(Vec3D)physics.particles.get(i);
              float mag=pos.distanceToSquared(p)+0.00001;
              val+=1/mag;
            }
            volumeData[index++]=val;
          }
        }
      }
      if (isClosed) {
        volume.closeSides();
      }
      surface.reset();
      surface.computeSurfaceMesh(mesh,isoThreshold*0.001);
    }
    
    void drawFilledMesh() {
      int num=mesh.getNumFaces();
      mesh.computeVertexNormals();
      for(int i=0; i<num; i++) {
        TriangleMesh.Face f=mesh.faces.get(i);
        Vec3D col=f.a.add(colAmp).scaleSelf(0.5);
        fill(col.x,col.y,col.z);
        normal(f.a.normal);
        vertex(f.a);
        col=f.b.add(colAmp).scaleSelf(0.5);
        fill(col.x,col.y,col.z);
        normal(f.b.normal);
        vertex(f.b);
        col=f.c.add(colAmp).scaleSelf(0.5);
        fill(col.x,col.y,col.z);
        normal(f.c.normal);
        vertex(f.c);
      }
    }
    
    void drawWireMesh() {
      noFill();
      int num=mesh.getNumFaces();
      for(int i=0; i<num; i++) {
        TriangleMesh.Face f=mesh.faces.get(i);
        Vec3D col=f.a.add(colAmp).scaleSelf(0.5);
        stroke(col.x,col.y,col.z);
        vertex(f.a);
        col=f.b.add(colAmp).scaleSelf(0.5);
        stroke(col.x,col.y,col.z);
        vertex(f.b);
        col=f.c.add(colAmp).scaleSelf(0.5);
        stroke(col.x,col.y,col.z);
        vertex(f.c);
      }
    }
    
    void normal(Vec3D v) {
      normal(v.x,v.y,v.z);
    }
    
    void vertex(Vec3D v) {
      vertex(v.x,v.y,v.z);
    }
    
    void initPhysics() {
      physics=new VerletPhysics();
      physics.setWorldBounds(new AABB(new Vec3D(),new Vec3D(DIM,DIM,DIM)));
      if (surface!=null) {
        surface.reset();
        mesh.clear();
      }
      boundingSphere=new SphereConstraint(new Sphere(new Vec3D(),DIM),SphereConstraint.INSIDE);
    }
    
    void updateParticles() {
      Vec3D grav=Vec3D.Y_AXIS.copy();
      grav.rotateX(mouseY*0.01);
      grav.rotateY(mouseX*0.01);
      physics.setGravity(grav.scaleSelf(2));
      numP=physics.particles.size();
      if (random(1)<0.8 && numP<NUM_PARTICLES) {
        VerletParticle p=new VerletParticle(new Vec3D(random(-1,1)*10,-DIM,random(-1,1)*10));
        if (useBoundary) p.addConstraint(boundingSphere);
        physics.addParticle(p);
      }
      if (numP>10 && physics.springs.size()<1400) {
        for(int i=0; i<60; i++) {
          if (random(1)<0.04) {
            VerletParticle q=physics.particles.get((int)random(numP));
            VerletParticle r=q;
            while(q==r) {
              r=physics.particles.get((int)random(numP));
            }
            physics.addSpring(new VerletSpring(q,r,REST_LENGTH, 0.0002));
          }
        }
      }
      float len=(float)numP/NUM_PARTICLES*REST_LENGTH;
      for(Iterator i=physics.springs.iterator(); i.hasNext();) {
        VerletSpring s=(VerletSpring)i.next();
        s.restLength=random(0.9,1.1)*len;
      }
      physics.update();
    }
    

    code

    tweaks (0)

    license

    advertisement

    toxiclibs

    BoxFluid

    Add to Faves Me Likey@! 62
    You must login/register to add this sketch to your favorites.

    BoxFluid demo is combining 3D physics particles with the IsoSurface class to create an animated mesh with a fluid behaviour. The mesh is optionally created within a boundary sphere, but other forms can be created using a custom ParticleConstraint class.

    Key controls:

    w : wireframe on/off
    c : close sides on/off
    p : show particles only on/off
    b : turn bounding sphere on/off
    r : reset particles
    s : save current mesh as OBJ & STL format
    - / = : decrease/increase surface threshold/tightness

    James Noeckel
    14 May 2010
    Holy carp! Metaball fluid physics? This is awesome!
    frankie zafe
    12 Jun 2010
    Impressive
    nice!
    Sean Thompson
    12 Nov 2010
    Nice work
    mga
    14 Mar 2011
    hi

    I am trying to make the demo work in Processing and I get an error "Cannot instantiate the type VolumetricSpace". I tried changing that new call to VolumetricSpaceArray and got "Cannot instantiate the type IsoSurface". I changed that to ArrayIsoSurface and got "function getData() does not exist" in volume.getData().

    Is there a ZIP file with this demo I can download? I just copy/pasted the code for each file and created tabs in Processing.

    Any help is appreciated.
    toxiclibs
    14 Mar 2011
    As mentioned on other demos of mine on this site: All of these examples are also bundled with the toxiclibs download package and you can find updated versions of each in the respective example folders (in this case the examples in volumeutils). There were some minor API changes with the last release (0020) and I simply don't have the capacity to update every single example already published here...

    Hth!
    mga
    14 Mar 2011
    Yay!

    Sorry I missed that. Didn't see the examples folder (and didn't read other demos here since I was just looking for this).

    I assumed something like that (API changes) had happened but wasn't sure how to fix it.

    Thanks!
    You need to login/register to comment.