• fullscreen
  • ellipse.pde
  • point.pde
  • swarmOnSurface.pde
  • //############################## Ellipse #####################
    //Creates an ellipse and allows points to be projected onto it.
    //
    class Ellipse
    {
      float x;
      float y;
      float z;
      
      float radius;
      
      float scaleX;
      float scaleY;
      float scaleZ;
      
      //############################## CONSTRUCTORS #####################
      //-----------------------------------------
      //Constructs the ellipse - based off a distored sphere.
      //XYZ is the center.
      //Radius is the overall scale.
      //scaleXYZ is the amount to scale the sphere by. 
      //-----------------------------------------
      public Ellipse(float _x, float _y, float _z, float _radius, float _scaleX, float _scaleY, float _scaleZ)
      {
        x = _x;
        y = _y;
        z = _z;
        radius = _radius;
        scaleX = _scaleX;
        scaleY = _scaleY;
        scaleZ = _scaleZ;
      }
      
      //############################## SETS #####################
      //-----------------------------------------  
      // Use to resize ellipse.
      // Adds the passed number to the scale of the ellise. 
      //-----------------------------------------
      public void addX(float _x){scaleX += _x;}
      public void addY(float _y){scaleY += _y;}
      public void addZ(float _z){scaleZ += _z;}
      
      
      
      //############################## GETS #####################
      
      //-----------------------------------------  
      // Returns the radius of the ellipse, and the scales. 
      //-----------------------------------------  
      public float getRadius(){ return radius;}
      public float getX(){ return scaleX;}
      public float getY(){ return scaleY;}
      public float getZ(){ return scaleZ;}
      
      
      
      //-----------------------------------------
      //Projects a point in xyz space onto the ellipse. 
      //
      //Does this by transforming the ellipse into a sphere - also do this to the coordinates of the points.
      //
      //The point is then pojected onto the sphere by projecting it towards the center of the sphere.
      //The projected point is therefore the distance of the radius along the vector that joins 
      //the center of the sphere with the original point.
      //
      //This is a fairly nieve way to do this, but since the points wont be far from the ellipse it does not matter.
      //
      //If the point goes below the base plane (y=0) it is reflected.
      //Returns the new xyz coordinates of the projected point. 
      //-----------------------------------------
      public float[] projectPoint(float pX, float pY, float pZ)
      {
        //first transform point from eliptical space into spherical space
        pX /= scaleX;
        pY /= scaleY;
        pZ /= scaleZ;
        
        //find the vector between the point and the center of the sphere.
        float vX = pX - x;
        float vY = pY - y;
        float vZ = pZ - z;
        
        //normalise the vector.
        float d = sqrt(vX * vX + vY * vY + vZ * vZ);
        float factor = 1/d;
        vX *= factor;
        vY *= factor;
        vZ *= factor;
        
        //scale the vector to the same length of the radius.
        vX *= radius;
        vY *= radius;
        vZ *= radius;
        
        //Point is at end of this vector when it starts from the center of the sphere. 
        float newX = x + vX;
        float newY = y + vY;
        float newZ = z + vZ;
        
        //scale point back into elliptical space
        newX *= scaleX;
        newY *= scaleY;
        newZ *= scaleZ;
        
        //Use this to make sure no points drop below the base plane
        if(newY > 0) newY = -newY;
        
        //return new point
        return new float[]{newX,newY, newZ};
      }
      
      
      //############################## DRAWING #####################
        
      //-----------------------------------------
      // Draws ellipse as a sphere.
      // Only works if xyz scale is 1.
      //-----------------------------------------
      public void draw()
      {
         pushMatrix(); 
          fill(204, 102, 0, 150);
          translate(x, y, z); 
          sphere(radius);
        popMatrix();
      }
    }
    
    //############################## Point #####################
    //A point on the surface. 
    //Stores the xyz location of the point, and what other points are close.
    //Has functions to update location based on a flocking algorithm
    //And project the point back onto the surface.
    //Also draws the point
    
    
    class Point
    {
      private int id;                 //Id of point in global point array
      
      private float x;                //location of point
      private float y;
      private float z;
      
      private float updatedX;         //new location of point before it is projected.
      private float updatedY;
      private float updatedZ; 
      
      private ArrayList connected;    //Id's of points that are the ideal distance apart.
      private ArrayList pushing;      //points that are too close
      private ArrayList pulling;      ///points taht are almost ideal. 
      
      
      //############################## CONSTRUCTORS #####################
      
      //-----------------------------------------
      // Constructs the point from xyz data.
      // Sets the arrays and tells point to move onto the surface.
      //-----------------------------------------
      public Point(int _id, float _x, float _y, float _z)
      {
        id = _id;
        
        x = _x;
        y = _y;
        z = _z;
        
        updatedX = _x;
        updatedY = _y;
        updatedZ = _z;
        
        connected = new ArrayList();
        pushing = new ArrayList();
        pulling= new ArrayList();
        
        setPosition();
      }
      
      //-----------------------------------------
      // Constructs the point randomly in xyz space. 
      // As no functions can be called before 'this' all maths is done in the function.
      // Use the ellipse scale to set bounds of the random placement to ensure it is close to the sphere. 
      // Then passed to the normal constructor. 
      //-----------------------------------------
      public Point(int _id)
      {
        this(_id, 
        (random(ellipseScale) - ellipseScale/2) * ellipseX, 
        (random(ellipseScale/2) -ellipseScale/2) * ellipseY, 
        (random(ellipseScale) -ellipseScale/2) * ellipseZ);
      }
      
      
      //############################## SETS #####################
      
      //-----------------------------------------
      // Projects the updated position back onto the ellipse and 
      // sets the point position to this projected position. 
      //-----------------------------------------
      public void setPosition()
      {
        float[] newPoint = globalEllipse.projectPoint(updatedX,updatedY,updatedZ);
        x = newPoint[0];
        y = newPoint[1];
        z = newPoint[2];
      }
      
      //-----------------------------------------
      // Summary:
      // Updates the position of the point by looping through every point
      // and finding the average vector away from points that are too close. 
      // This algorithm is some sort of flocking / dynamic relaxation.
      // 
      //
      // Logic:
      // Loops through every point - This loop could be optimised but it works ok. 
      // Finds the vector between this point and the other point. 
      // If vector is too short (as defined by minLength) finds the vector away from this point.
      // Scales this vector so points that are very close create larger vectors. 
      // Adds this vector to the away vector. 
      // After the loop is complete average away vector, based on how many vectors were added to it.
      // Takes a fraction of that vector (as defined by speed) - Do not want to move all of it or the relaxation will not work.
      // And move the point along that vector.  
      //
      // Sets the updated values to this new point. 
      // Note that the updated values are not on the ellipse. setPostion() places them on the ellipse.
      // In this process, points that are too close, just the right distance, and just over the right distance
      // are stored in the arrayLists. 
      //
      //-----------------------------------------
      public void updatePosition()
      {
        connected = new ArrayList();
        pushing = new ArrayList();
        pulling= new ArrayList();
        
        int count = 0;
        
        float vectorAwayX = 0;
        float vectorAwayY = 0;
        float vectorAwayZ = 0;
        
        for( int i = 0; i < numPoints; i++)
        {
          if(i != id)
          {
            //vector from current point towards next point
            float diffX = points[i].getX() - x;
            float diffY = points[i].getY() - y;
            float diffZ = points[i].getZ() - z;
            
            //As we are only interested in points under 1.2 * maxLength, we can disgard
            //all the points who are orthogonally outside this. 
            //This saves calculating the distance between every point. By disgarding points early. 
            if(diffX < maxLength * 1.2 && diffX > maxLength * -1.2)
            {
              if(diffY < maxLength * 1.2 && diffY > maxLength * -1.2)
              {
                if(diffZ < maxLength * 1.2 && diffZ > maxLength * -1.2)
                {  
    
                  float distance = sqrt(sq(diffX) + sq(diffY) + sq(diffZ));
                  
                  if(distance < midPoint)
                  {
                    float factor = (distance - midPoint) / midPoint;
                    
                    vectorAwayX += factor * diffX;
                    vectorAwayY += factor * diffY;
                    vectorAwayZ += factor * diffZ;
                    count++;
                  } 
                  
                  
                  if(distance < minLength)
                  {
                    pushing.add(i);
                  }
                  else if(distance < maxLength)
                  {
                    connected.add(i);
                  }
                  else if(distance < maxLength * 1.2)
                  {
                    pulling.add(i);
                  }
                }
              }
            }
          }
          
        }
        
        //if the vector was changed.
        if(count > 0) {
          //average vector.
          vectorAwayX /= count;
          vectorAwayY /= count;
          vectorAwayZ /= count;
          
          //take a fraction of the vector. 
          vectorAwayX *= speed;
          vectorAwayY *= speed;
          vectorAwayZ *= speed;
          
          //move point along this vector.
          updatedX = x + vectorAwayX;
          updatedY = y + vectorAwayY;
          updatedZ = z + vectorAwayZ;
        }
      }
      
      
      
      //############################## GETS #####################
      
      public float getX() {return x;}
      public float getY() {return y;}
      public float getZ() {return z;}
      
      //returns number of nodes that are within the allowed distance from this node.
      public int getNumberOfNeighbours() 
      {
        return connected.size();
      }
      //-----------------------------------------
      // Returns point as CSV line. 
      // Note that z and y are swapped to match Rhino space.
      //-----------------------------------------
      public String getCSV()
      {
        return x + "," + -z + "," + -y;
      }
      
      
      
      //############################## DRAWING #####################
      
      //-----------------------------------------
      // Writes the point into the passed output stream in CSV format.
      //-----------------------------------------
      public void printPointCSV(PrintWriter output)
      {
        output.println(getCSV());
      }
      
      //-----------------------------------------
      // Writes the lines into the passed output stream in CSV format. 
      //-----------------------------------------
      public void printLinesCSV(PrintWriter output)
      {
        for(int i = 0; i < connected.size(); i ++)
        {
          int n = (Integer) connected.get(i); 
             
           //prevents printing the line twice
           if(n > id)
           {
             output.println(getCSV());
            output.println(points[n].getCSV());
           }
        }
      }
      
      //-----------------------------------------
      // Draws the point as a sphere.
      // If the text needs to be drawn, draws the ID centered above the point.
      // Then draws all the lines showing relationships between surrounding points.
      //-----------------------------------------
      public void draw()
      {
        //draws the point
        pushMatrix(); 
          fill(100, 100, 100, 255);
          translate(x, y, z); 
          sphere(pointDrawSize);
        popMatrix();
        
        if(drawText)
        {
          float tw = textWidth(str(id));
          text(id, x-tw/2, y-10, z);
        }
        
        //draw the lines from point
        stroke(255,130,130);
        drawLines(pushing);
        
        stroke(80);
        drawLines(connected);
        
        stroke(210);
        drawLines(pulling);
        
        noStroke();
      }
      
      //-----------------------------------------
      // Takes an array list of point ids.
      // Draws a line from this point, to each of the points in the array list.
      // Only draws to lines of a higher ID so that lines are not drawn twice
      // EG. P1 draws to P2, but P2 does not draw to P1
      //-----------------------------------------
      private void drawLines(ArrayList pointToConnectTo)
      {
        for(int i = 0; i < pointToConnectTo.size(); i ++)
        {
             int n = (Integer) pointToConnectTo.get(i); 
             
             //prevents drawing the line twice
             if(n > id)
             {
              beginShape(LINES);
              vertex(x, y, z);
              vertex(points[n].getX(), points[n].getY(),points[n].getZ());
              endShape();
             }
        }
      }
    }
    
     //##################################################
     // Daniel Davis - nzrchitecture.com
     //
     // Program to generate members of a specified length on an eliptical surface.
     // 
     // Uses a swarming type algorithm to ensure all the points are the 
     // specified distance away from eachother (minLength & maxLength).
     // 
     // Overall flow:
     // Generates an array of points. 
     // Every draw function:
     // Each point is moved away from its nearest neighbours. 
     // All the points are then projected back onto the eliptical surface.
     // Object is then rendered.
     // 
     // In the render:
     // White members are close to the maximum range.
     // Gray members are in the ideal range.
     // Red members are below the ideal range and are pushing the point.
     //
     // Keys:
     // r - randomly relocates a point that is not connected
     // t - draws the point number over each point.
     // 1 - Increase ellipse on x-axis.
     // 2 - Decrease ellipse on x-axis.
     // 3 - Increase ellipse on y-axis.
     // 4 - Decrease ellipse on y-axis.
     // 5 - Increase ellipse on z-axis.
     // 6 - Decrease ellipse on z-axis.
     // i - saves the frame as a tif image.
     // s - saves three files:
     //        - points.csv - the location of all the points.
     //        - lines.csv - the location of the start and end of the lines.
     //        - setup.csv - the size of the ellipse. 
     //##################################################
     
     
     
     //############################## GLOBAL #####################
     Point[] points;
     Ellipse globalEllipse;
     PFont myFont;
    
     
     //############################## SETTINGS #####################
     int numPoints = 205;              //number of points on surface
     float ellipseScale = 1000;         //scale of sphere in cm
     float ellipseX = 1.3;             //scale of spehre on x axis - normalised.
     float ellipseY = 0.6;             //scale of sphere on y axis (height) - normalised
     float ellipseZ = 0.75;            //scale of sphere on z axis - normalised.
     float speed = 0.8;                //how fast the points move
     float minLength = 160;            //the min length of a member
     float maxLength = 175;            //the max length of a member
     float midPoint = (minLength + maxLength) / 2; //the middle of the min and max length.
     float pointDrawSize = 3;          //how large a point is when it is drawn.
     boolean drawText = true;         //whether the points IDs should be written above them.  
     boolean printPointsEveryFrame = true; //whether the points should be written to file every frame.
    
    //-----------------------------------------
    // Creates the scene. Sets the global variables.
    //-----------------------------------------
    void setup() {
      size(1000, 1000, P3D);
      noStroke();
      
      myFont = createFont("FFScala", 16);
      textFont(myFont);
      
      globalEllipse = new Ellipse(0,0,0, ellipseScale, ellipseX, ellipseY, ellipseZ);
      
      points = new Point[numPoints];
      for(int i =0; i < numPoints; i++)
      {
        reSetPoint(i);
      }
      
      saveSetup();
    }
    
    //-----------------------------------------
    // Reconstructs the specified point by telling it to generate randomly. 
    //-----------------------------------------
    void reSetPoint(int pointId)
    {
      points[pointId] = new Point(pointId);
    }
    
    
    //-----------------------------------------
    // Called every frame. Calls the basic drawing setup.
    // Tells every point to update its location.
    // Then sets the location of every point to its updated postion
    // - We do this so points are not updating their location with the new location of points.
    // Then draws all the points.
    // Then checks for user interface commands. 
    //-----------------------------------------
    void draw() {
      setupDrawing();
    
      for(int i =0; i<points.length; i++)
      {
        points[i].updatePosition();
      }
      
      for(int i =0; i<points.length; i++)
      {
        points[i].setPosition();
      }
      
      for(int i =0; i<points.length; i++)
      {
        points[i].draw();
      }
      
      if(printPointsEveryFrame)
      {
        //savePoints();
        saveLines();
      }
      
      if(keyPressed) {
        checkKeys();
      }
    }
    
    //-----------------------------------------
    // If R is pressed, it randomly moves a point not connected.
    // If T is pressed, displays the node id above each point. 
    // If S is pressed, saves the point location to a text file.
    //-----------------------------------------
    void checkKeys()
    {
      if (key == 'r' || key == 'R') 
      {
        for(int i =0; i<points.length; i++)
        {
          if(points[i].getNumberOfNeighbours() == 0)
          {
            println("Removed " + i);
            reSetPoint(i);
            break;
          }
        }
      } 
      else if(key == 't' || key == 'T')
      {  
        if(drawText) drawText = false;
        else drawText = true;
      }
      //saves the point location to a text file.
      else if(key == 's' || key == 'S')
      {
        saveSetup();
        savePoints();
        saveLines();
      }
      //resize ellipse and save new setup.
      else if(key == '1')
      {
        globalEllipse.addX(0.025);
        if(printPointsEveryFrame){saveSetup();}
      }
      else if(key == '2')
      {
        globalEllipse.addX(-0.025);
        if(printPointsEveryFrame){saveSetup();}
      }
      else if(key == '3')
      {
        globalEllipse.addY(0.025);
        if(printPointsEveryFrame){saveSetup();}
      }
      else if(key == '4')
      {
        globalEllipse.addY(-0.025);
        if(printPointsEveryFrame){saveSetup();}
      }
      else if(key == '5')
      {
        globalEllipse.addZ(0.025);
        if(printPointsEveryFrame){saveSetup();}
      }
      else if(key == '6')
      {
        globalEllipse.addZ(-0.025);
        if(printPointsEveryFrame){saveSetup();}
      } 
      else if(key == 'i')
      {
        saveFrame("frame-####.tif");
      }
    }
    
    
    //-----------------------------------------
    // Sets up basic part of drawing like lights and camera. 
    // Based on the Processing example 3D/Camera/Perspective
    // Also draws the ground plane.
    //-----------------------------------------
    void setupDrawing()
    {
      lights();
      background(204);
      float cameraY = height/2.0;
      float fov = 0.9;//mouseX/float(width) * PI/2;
      float cameraZ = cameraY / tan(fov / 2.0);
      float aspect = float(width)/float(height);
      if (mousePressed) {
        aspect = aspect / 2.0;
      }
      perspective(fov, aspect, cameraZ/10.0, cameraZ*10.0);
      
      translate(width/2, height/2, -2000);
      rotateX(-PI/6);
      rotateY(PI + mouseX/float(width) * 2 * PI);
        
      //Ground plane
      beginShape();
        fill(255, 255, 255, 255);
        float squareSize = ellipseScale + 400;
        vertex(-squareSize, 0, squareSize);
        vertex(-squareSize, 0, -squareSize);
        vertex(squareSize, 0 ,-squareSize);
        vertex(squareSize, 0, squareSize);
      endShape(CLOSE);
    }
    
    
    //-----------------------------------------
    // Saves all the points as a CSV file called points.csv
    // Use try and catch because sometimes the file might be read as we try to write it. 
    // Format:
    // x, y, z
    //-----------------------------------------
    void savePoints()
    {
      PrintWriter output= null;
      
      try {
        output = createWriter("points.csv"); 
      } catch(Throwable t) {
        //could catch more gracefully. 
      }
      
      if(output != null )
      {
        for (int i = 0; i < points.length; i++) {
          points[i].printPointCSV(output);
        }
        output.flush();
        output.close();
      }
    }
    
    //-----------------------------------------
    // Saves all the lines as a CSV file called lines.csv
    // Use try and catch because sometimes the file might be read as we try to write it. 
    // Format (over two lines):
    // xStart, yStart, zStart, 
    // xEnd, yEnd, ZEnd
    //-----------------------------------------
    void saveLines()
    {
      PrintWriter output= null;
      
      try {
        output = createWriter("lines.csv"); 
      } catch(Throwable t) {
        //could catch more gracefully. 
      }
      
      if(output != null )
      {
        for (int i = 0; i < points.length; i++) {
          points[i].printLinesCSV(output);
        }
        output.flush();
        output.close();
      }
    }
    
    //-----------------------------------------
    // Saves the setup of the ellipse in setup.csv.
    // Format:
    // radius
    // scaleX
    // scaleY
    // scaleZ
    //-----------------------------------------
    void saveSetup()
    {
      PrintWriter output= null;
      
      try {
        output = createWriter("setup.csv"); 
      } catch(Throwable t) {
        //could catch more gracefully. 
      }
      
      if(output != null )
      {
        output.println(globalEllipse.getRadius());
        output.println(globalEllipse.getX());
        output.println(globalEllipse.getY());
        output.println(globalEllipse.getZ());
        output.flush();
        output.close();
      }
    }
    
    
    
    

    code

    tweaks (0)

    about this sketch

    This sketch is running as Java applet, exported from Processing.

    license

    advertisement

    nzarchitecture.com

    Dynamic relaxation on a surface

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

    Creates a mesh over a surface with members of a specified length. In this case it is an elliptical surface.

    Uses a swarming type algorithm to ensure all the points are the specified distance away from each-other (minLength & maxLength).

    Overall flow:
    1. Generates an array of points.
    2. Each point is moved away from its nearest neighbors.
    3. All the points are then projected back onto the eliptical surface.
    4. Object is then rendered.
    5. Return to 2.

    White members are close to the maximum range.
    Gray members are in the ideal range.
    Red members are below the ideal range and are

    John Harding
    6 Aug 2010
    Lovely stuff. I got a similar thing going in microstation vba recently because calculating the maths to project back onto a non-rational surface is very tricky as you probably know!

    Also had a crack at using more than one length of member (basically had 3 different 'charge' values for nodes giving 3! = 6 different member lengths.

    It didn't work very well! If you have any ideas on using more than one length of member or if you've got it to work then please let me know. I'm trying a method now which involves sliding edges around a surface and locking onto to each other at the ends if they come close... hopefully I'll have more success with this.

    I enjoy your blog by the way.

    Thanks,
    John Harding.
    Hi John,

    I think the code in this example might be one way of distributing members on a non-rational surface.

    In this example there is a function called projectPoint(x,y,z) in ellipse.pde which returns the closest point on an ellipse. You could modify this function to return the closest point on a non-rational surface and the members will grow over this surface. Finding the closest point on a surface is a little tricky. I am not sure how microstation works, but I know in Rhino you can call commands from a C# or VBA program. Alternatively you could simplify the surface into triangles, import them into processing and solve the intersections.

    The points in this example work by pushing away from each-other when they get too close together. (In updatePosition() under point.pde) You could modify this function to have the points push and attract towards your three member lengths. Although I would be interested to see if it could be solved with one member length.

    The only other problem would be solving intersections. You will need to think of some logic to solve how the nodes negotiate what connection wins.

    It is an interesting problem and I would be interested to see whether this works for you, or if you find another method of solving it.

    Daniel


    You need to login/register to comment.