Spherical Flow field

edited April 2017 in Library Questions

How should I proceed to create a spherical flow field based on a perlin noise? I know how to create a 2d flo field, but how should I turn into a 3d flo field?! Do I need to determinate the angles (Y, Z) values according to a perlin noise before and then use the polar coordinates formula?! thanks in advance for your help!

Tagged:

Answers

  • "Spherical flow field"? What is that supposed to be? Do you simple mean a 3D flow field (that is simple, just use a third parameter to noise()), or something else?

  • Hi Lord of Galaxy, thanks for answering, no I mean to use a 3d flow field on a sphere (to give it polar coordinates), it would look like a sun with circumvolutions on its surface!

  • I see. This is more maths than programming though.

  • Not really since I ask myself if I could begin to apply a noise to my angle values and then draw my particles in polar coordinates or if I should do the opposite?! I can post a code to be more accurate...

  • I'd recommend polar.

    I really is more maths, if you have a mathematical model of what you want, programming it is simple enough.

  • I have the basic programm, but it's quite long, even if the problem is the mathematical model vs the noiseScale/noiseStrength size I guess. Can I post it ?

  • Sure, why not? If it's less than 1000 lines, it'll do.

  • edited April 2017

    like this ?!

    import processing.opengl.*;
    import controlP5.*;
    import java.util.Calendar;
    
    
    // ------ agents ------
    int agentsCount = 600;
    Agent[] agents = new Agent[1000];
    float noiseScale = 15000, noiseStrength = 0.1; 
    float agentAlpha = 76;
    color agentColor;
    
    
    // ------ ControlP5 ------
    ControlP5 controlP5;
    boolean showGUI = false;
    Slider[] sliders;
    Range[] ranges;
    
    
    // ------ mouse interaction ------
    int offsetX = 0, offsetY = 0, clickX = 0, clickY = 0, zoom = -100;
    float rotationX = 0, rotationY = 0, targetRotationX = 0, targetRotationY = 0, clickRotationX, clickRotationY; 
    int spaceSizeX = 200, spaceSizeY = 300, spaceSizeZ = 200;
    float radius = 300;
    boolean freeze = false;
    
    
    // ------ image output ------
    boolean saveOneFrame = false;
    int qualityFactor = 3;
    
    
    void setup() {
      size(800, 800, P3D);
      setupGUI(); 
      colorMode(HSB, 360, 100, 100);
      for (int i=0; i<agents.length; i++) agents[i]=new Agent();
    }
    
    void draw() {
      hint(ENABLE_DEPTH_TEST);
    
      background(0, 0, 100);
      smooth();
      lights();
    
      pushMatrix(); 
    
      // ------ set view ------
      translate(width/2, height/2, zoom); 
      println(mousePressed + ", " + mouseButton + ", " + RIGHT);
      if (mousePressed && mouseButton==RIGHT) {
        offsetX = mouseX-clickX;
        offsetY = mouseY-clickY;
        targetRotationX = min(max(clickRotationX + offsetY/float(width) * TWO_PI, -HALF_PI), HALF_PI);
        targetRotationY = clickRotationY + offsetX/float(height) * TWO_PI;
      }
      rotationX += (targetRotationX-rotationX)*0.25; 
      rotationY += (targetRotationY-rotationY)*0.25;  
      rotateX(-rotationX);
      rotateY(rotationY); 
    
      noFill();
      stroke(192, 100, 64);
      strokeWeight(1); 
      box(spaceSizeX*2, spaceSizeY*2, spaceSizeZ*2);
    
      // ------ update and draw agents ------
      agentColor = color(0, 0, 0, agentAlpha);
      for (int i=0; i<agentsCount; i++) {
        agents[i].update(); 
        agents[i].draw();
      }
      popMatrix();
    
      hint(DISABLE_DEPTH_TEST);
      noLights();
      drawGUI();
    }
    // ------ interactions ------
    void keyPressed() {
      if (keyCode == UP) zoom += 20;
      if (keyCode == DOWN) zoom -= 20;
    }
    
    void keyReleased() {
      if (key=='s' || key=='S') saveFrame(timestamp()+".png");
    
      if (key=='f' || key=='F') freeze = !freeze; 
      if (key=='m' || key=='M') {
        showGUI = controlP5.getGroup("menu").isOpen();
        showGUI = !showGUI;
      }
    
    
      if (showGUI) controlP5.getGroup("menu").open();
      else controlP5.getGroup("menu").close();
    }
    
    void mousePressed() {
      clickX = mouseX;
      clickY = mouseY;
      clickRotationX = rotationX;
      clickRotationY = rotationY;
    }
    
    String timestamp() {
      return String.format("%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS", Calendar.getInstance());
    }
    
    
    class Agent {
      PVector p;
      float offset, angleY, angleZ;
      Ribbon3d ribbon;
    
      Agent() {
        p = new PVector(0, 0, 0);
        setRandomPostition();
        offset = 10000;
        // how many points has the ribbon
        ribbon = new Ribbon3d(p, (int)random(50, 300));
      }
    
      void update() {
        angleY += noise(p.x/noiseScale, p.y/noiseScale, p.z/noiseScale) * noiseStrength; 
        angleZ += noise(p.x/noiseScale+offset, p.y/noiseScale, p.z/noiseScale) * noiseStrength; 
    
        /* convert polar to cartesian coordinates
         stepSize is distance of the point to the last point
         angleY is the angle for rotation around y-axis
         angleZ is the angle for rotation around z-axis
         */
        p.x = cos(angleZ) * cos(angleY) * radius;
        p.y = cos(angleZ) * sin (angleY) * radius;
        p.z = sin(angleZ) * radius;
    
        // boundingbox wrap
        if (p.x< 0) { 
          p.x += TWO_PI;
        }
        if (p.x > TWO_PI) { 
          p.x -= TWO_PI;
        }
        if (p.y< 0) { 
          p.y += TWO_PI;
        }
        if (p.y > TWO_PI) { 
          p.y -= TWO_PI;
        }
        if (p.z< 0) { 
          p.z += TWO_PI;
        }
        if (p.z > TWO_PI) { 
          p.z -= TWO_PI;
        }
        //setRandomPostition();
        // create ribbons
        ribbon.update(p);
      }
    
      void draw() {
        ribbon.drawLineRibbon(agentColor, 2.0);
        ribbon.drawMeshRibbon(agentColor, 10.0);
      }
    
      void setRandomPostition() {
        p.x=random(cos(angleZ) * cos(angleY) * radius);
        p.y=random(cos(angleZ) * sin (angleY) * radius);
        p.z=random(sin(angleZ) * radius);
      }
    }
    
    
    void setupGUI() {
      color activeColor = color(0, 130, 164);
      controlP5 = new ControlP5(this);
      //controlP5.setAutoDraw(false);
      controlP5.setColorActive(activeColor);
      controlP5.setColorBackground(color(170));
      controlP5.setColorForeground(color(50));
      controlP5.setColorCaptionLabel(color(50));
      controlP5.setColorValueLabel(color(255));
    
      ControlGroup ctrl = controlP5.addGroup("menu", 15, 25, 35);
      ctrl.setColorLabel(color(255));
      ctrl.close();
    
      sliders = new Slider[10];
      ranges = new Range[10];
    
      int left = 0;
      int top = 5;
      int len = 300;
    
      int si = 0;
      int ri = 0;
      int posY = 0;
    
      sliders[si++] = controlP5.addSlider("agentsCount", 1, 1000, left, top+posY+0, len, 15);
      posY += 30;
    
      sliders[si++] = controlP5.addSlider("noiseScale", 1, 1000, left, top+posY+0, len, 15);
      sliders[si++] = controlP5.addSlider("noiseStrength", 0, 1, left, top+posY+20, len, 15);
      posY += 50;
    
      sliders[si++] = controlP5.addSlider("agentAlpha", 0, 255, left, top+posY+0, len, 15);
      posY += 30;
    
      for (int i = 0; i < si; i++) {
        sliders[i].setGroup(ctrl);
        sliders[i].setId(i);
        sliders[i].getCaptionLabel().toUpperCase(true);
        sliders[i].getCaptionLabel().getStyle().padding(4, 3, 3, 3);
        sliders[i].getCaptionLabel().getStyle().marginTop = -4;
        sliders[i].getCaptionLabel().getStyle().marginLeft = 0;
        sliders[i].getCaptionLabel().getStyle().marginRight = -14;
        sliders[i].getCaptionLabel().setColorBackground(0x99ffffff);
      }
    }
    
    void drawGUI() {
      controlP5.show(); 
      controlP5.draw();
    }
    
    
    class Ribbon3d {
      int count; // how many points has the ribbon
      PVector[] p;
    
    
      Ribbon3d (PVector theP, int theCount) {
        count = theCount; 
        p = new PVector[count];
        for (int i=0; i<count; i++) {
          p[i] = new PVector(theP.x, theP.y, theP.z);
        }
      }
    
      void update(PVector theP) {
        // shift the values to the right side
        // simple queue
        for (int i=count-1; i>0; i--) {
          p[i].set(p[i-1]);
        }
        p[0].set(theP);
      }
    
      void drawMeshRibbon(color theMeshCol, float theWidth) {
        // draw the ribbons with meshes
        fill(theMeshCol);
        noStroke();
    
        beginShape(QUAD_STRIP);
        for (int i=0; i<count-1; i++) {
          // if the point was wraped -> finish the mesh an start a new one
    
          vertex(p[i].x, p[i].y, p[i].z);
          vertex(p[i].x, p[i].y, p[i].z);
          endShape();
          beginShape(QUAD_STRIP);
    
          PVector v1 = PVector.sub(p[i], p[i+1]);
          PVector v2 = PVector.add(p[i+1], p[i]);
          PVector v3 = v1.cross(v2);      
          v2 = v1.cross(v3);
          //v1.normalize();
          v2.normalize();
          //v3.normalize();
          //v1.mult(theWidth);
          v2.mult(theWidth);
          //v3.mult(theWidth);
          vertex(p[i].x+v2.x, p[i].y+v2.y, p[i].z+v2.z);
          vertex(p[i].x-v2.x, p[i].y-v2.y, p[i].z-v2.z);
    
        }
        endShape();
      }
    
    
    
      void drawLineRibbon(color theStrokeCol, float theWidth) {
        // draw the ribbons with lines
        noFill();
        strokeWeight(theWidth);
        stroke(theStrokeCol);
        for (int i=0; i<count-1; i++) {
          // if the point was wraped -> finish the line an start a new one
          beginShape(LINES);
          vertex(p[i].x, p[i].y, p[i].z);
          vertex(p[i+1].x, p[i+1].y, p[i+1].z);
          endShape();
        }
      }
    }
    
  • Hi kfrajer, are you still around?! Anyway when you have time please have a look at the code and keep me posted. I don't manage to find a way to get a fluid animation where particles move smoothly in different directions...

  • I have removed the comments with the un-formatted code and the comments regarding how to format the code to make the discussion easier to follow.

    Please note that it is possible to edit your comments to format your code rather than re-posting it. But if you do edit your comments please read this first.

  • ok, thanks quark, I don't use often the forum so I still make some msitakes...

  • @lolonulu

    You don't get many replies because you have a convoluted code. Right now you code is doing something and you want to change its behavior which you have explained initially. However, where to implement all these changes? You need to step back and come up with a simple sketch where you show what you can do, what you have done/tried and then what you would like to do next. A sketch of 30 lines is more manageable than a sketch of 300. Perlin noise is fun as well. I have seen it myself but I haven't explored it in full detail.

    Your question is two folded: spherical coordinates and integration with perlin noise. I have a feeling you can handle the second part so we can leave it out of the question for now.

    For the first part of the question, have a look at the following post: https://forum.processing.org/two/discussion/comment/92227/#Comment_92227 if you have any question, just continue here so to avoid disturbing disturb previous OPs 8-X


    You should consider adding peasycam: http://mrfeinberg.com/peasycam/

    It will slow down the animation but at least you will see the whole effect in 3D. Follow these instructions:

    Global scope:

    import peasy.*;
    PeasyCam camera;
    

    Add next lines in setup:

      camera = new PeasyCam(this, 400);
      camera.lookAt(width/2, height/2,0, 400);
    

    Modify this part of draw to reflect the next changes:

      camera.beginHUD();
      drawGUI();
      camera.endHUD();
    

    Finally, you need to set the next line in setupGUI:

    controlP5.setAutoDraw(false);

    Unfortunately, every time you drag any of the sliders, it will move your scene. You can always disable it if that is inconvenient.

    Kf

  • Dear Kf, Thank you very much for your message and help. I have added Peasy, thanks a lot. I think my problem with spherical coordinates is solved (but the link to the post was helpful anyway, thanks), now I have some trouble with the parameters of the perlin noise... Here in the agent class, what should I change to get particles moving in many different directions and not almost in the same direction like now ?! I hope I am clear enough. Not far from this :

    class Agent {
      PVector p;
      float offset, angleY, angleZ;
      Ribbon3d ribbon;
    
      Agent() {
        p = new PVector(0, 0, 0);
        setRandomPostition();
        offset = 10000;
        // how many points has the ribbon
        ribbon = new Ribbon3d(p, (int)random(50, 300));
      }
    
      void update() {
        angleY += noise(p.x/noiseScale, p.y/noiseScale, p.z/noiseScale) * noiseStrength; 
        angleZ += noise(p.x/noiseScale+offset, p.y/noiseScale, p.z/noiseScale) * noiseStrength; 
    
        /* convert polar to cartesian coordinates
         stepSize is distance of the point to the last point
         angleY is the angle for rotation around y-axis
         angleZ is the angle for rotation around z-axis
         */
        p.x = cos(angleZ) * cos(angleY) * radius;
        p.y = cos(angleZ) * sin (angleY) * radius;
        p.z = sin(angleZ) * radius;
    
        // boundingbox wrap
        if (p.x< 0) { 
          p.x += TWO_PI;
        }
        if (p.x > TWO_PI) { 
          p.x -= TWO_PI;
        }
        if (p.y< 0) { 
          p.y += TWO_PI;
        }
        if (p.y > TWO_PI) { 
          p.y -= TWO_PI;
        }
        if (p.z< 0) { 
          p.z += TWO_PI;
        }
        if (p.z > TWO_PI) { 
          p.z -= TWO_PI;
        }
        //setRandomPostition();
        // create ribbons
        ribbon.update(p);
      }
    
      void draw() {
        ribbon.drawLineRibbon(agentColor, 2.0);
        ribbon.drawMeshRibbon(agentColor, 10.0);
      }
    
      void setRandomPostition() {
        p.x=random(cos(angleZ) * cos(angleY) * radius);
        p.y=random(cos(angleZ) * sin (angleY) * radius);
        p.z=random(sin(angleZ) * radius);
      }
    }
    
  • Ooooops here is the link missing in the previous comment, it's an example not far from what I'd like to do :

    https://vimeo.com/11014356

  • Dear kfrajer, I hope my previous post is clear enough and the code short enough I still haven't found a solution :((

  • Answer ✓

    This is implementing perlin using the code quoted on my prev post above. Notice that there are two functions available at the end of draw(). Perlin based on xyz "Cartesian coordinates" and a spherical version. For both, the noise is fed along each dimension independently. Notice that the noise using xyz has the same behavior as the spherical.

    In spherical, if you fix zz, you get perlin on a spherical shell aka. movement on a surface of a sphere as defined by the fixed value zz. I believe you already implemented this behavior in your code.

    However, I am not sure how to implement the perlin noise as in the video, since it is working with points defining a surface. Would you create a variation of parameters for each point?

    Notice in the demo code below I have the ttime variable offset by a non-zero value in some spots. I did this on purpose to force the perlin noise to be "more" different along each dimension. In a long run, this could cause more of a semi-random noise (not truly random ) as variation along each dimensions will be correlated by these values. If I do not offset by these values, then the stepping along each dimension would vary very slow at the chosen running tstep of 0.002. In other words, movement will be along one dimension instead of shifting around the whole volume.

    Kf

    import peasy.*; 
    PeasyCam cam;
    
    Table csvTable;
    
    final float tstep=0.002;
    float ttime;
    float xx, yy, zz;
    
    FloatList fx=new FloatList();
    FloatList fy=new FloatList();
    FloatList fz=new FloatList();
    FloatList ftheta=new FloatList();
    FloatList fphi=new FloatList();
    int n=50;
    int cidx=0;
    
    void setup() 
    {
      csvTable = loadTable("cubePositions.txt", "csv"); // load csv file of positions to table 
      // ------------------------------------- CAM SETTINGS ----------------------------------------------------
      size(800, 600, P3D);
      cam = new PeasyCam(this, 0, 0, 0, 280);  
      cam.setMinimumDistance(100);
      cam.setMaximumDistance(width);
      cam.setYawRotationMode();  // cam will only rotate around the rigs(lookAt point) Y axis UNLESS shift is held
    
      for (int i=0; i<n; i++) {
        fx.set(i, 0);
        fy.set(i, 0);
        fz.set(i, 0);
        ftheta.set(i, 0);
        fphi.set(i, 0);
      }
    }
    
    
    
    
    void draw() 
    {
      background(0);
      lights(); 
    
      for (int i = 0; i <156; i++) {
        float X= csvTable.getFloat(i, 0)-128;
        float Y = (csvTable.getFloat(i, 1)-128)* -1;
        float Z = (csvTable.getFloat(i, 2)-128)* -1;
        float posZstate;
        if (Z > 0) {
          posZstate = -Z;
        } else {
          posZstate = Z;
        }
        float angleX = atan2(Y, posZstate);
        float angleY = atan2(-Z, X);
        pushMatrix();
        translate(X, Y, Z);
        float theta=atan2(Y, X);  //Restricte -180,180
        float phi=acos(Z/sqrt(X*X+Y*Y+Z*Z)); //Restricte 0,180
        rotateZ(theta);
        rotateY(phi);
        stroke(3);
        fill(255);
        box(4, 4, 60);
    
        popMatrix();
      }
    
      noStroke();
      //fill(255, 0, 0);
      //sphere(50);
    
      drawNoisyBallspherical();
      //drawNoisyBallxyz();
    }
    
    void drawNoisyBallspherical() {
    
      zz=noise(ttime+=tstep);
      zz=map(zz, 0, 1, -width/4, width/4);
      //  zz=width/4;   //ACTIVATE this line to cause particles to move only on a surface defined by a fix radius=width/4
    
      float theta=map(noise(ttime+=tstep), 0, 1, -PI, PI); //Restricte -180,180
      float phi=map(noise(ttime+100), 0, 1, 0, PI);   //Restricte 0,180
    
      for (int i=0; i<n; i++) {
        pushStyle();
        fill(250, 250, 0 );
        pushMatrix();
        rotateZ(ftheta.get(i));
        rotateY(fphi.get(i));
        translate(0, 0, fz.get(i));
        sphere(5);
        popMatrix();
        popStyle();
      }
    
      fz.set(cidx, zz);
      ftheta.set(cidx, theta);
      fphi.set(cidx, phi);
      cidx=(cidx+1)%n;
    }
    
    
    void drawNoisyBallxyz() {
    
      xx=noise(ttime+=tstep);
      xx=map(xx, 0, 1, -width/4, width/4);
      yy=noise(ttime+100);
      yy=map(yy, 0, 1, -width/4, width/4);
      zz=noise(ttime+1013);
      zz=map(zz, 0, 1, -width/4, width/4);
    
      for (int i=0; i<n; i++) {
        pushStyle();
        fill(250, 250, 0 );
        pushMatrix();
        translate(fx.get(i), fy.get(i), fz.get(i));
        sphere(5);
        popMatrix();
        popStyle();
      }
    
      fx.set(cidx, xx);
      fy.set(cidx, yy);
      fz.set(cidx, zz);
    
      cidx=(cidx+1)%n;
    }
    
  • edited April 2017 Answer ✓

    if you are trying to wrap two meshes or a spiral triangle strip into a sphere for mapping 2d noise, you might consider point spheres.

    Related videos:

    • vimeo.com/5295427
    • vimeo.com/5281011
    • vimeo.com/11014356

    Other languages:

    The Cinder Creative Coding Cookbook has a section (written in C++) on "Creating a spherical flow field with Perlin noise" if you are looking for a brief discussion of principles.

    There are also some discussions in Unity forums:

  • Dear KF, Thank you very much for your message. I miss time now to check out but what you wrote will really help me for sure! Will tell you.

  • Dear Jeremy, Thank you very much for your message and for the very clear code about point spheres. You are definitely true, I'll go back to the begining a point spheres and then apply the noise in 3D. By the way the 3rd video you've sent is really close to what I am looking for... Thanks

Sign In or Register to comment.