Point Cloud Particle System

Published February 19, 2018

keyboard controls:

move: arrow keys
rotate: shift + arrow keys
zoom: option + arrow keys
gravity: ‘g’ key
home: ‘h’ key

Full code available here


For this week’s assignment, I was inspired by Karl Sims’ Particle Dreams animation from 1988. In particular, I wanted to recreate the cloud of particles at 20s which variously composes and destroys a large foreboding head shape in a number of unique ways. The particles acted both in tandem (as if acted upon by an external force) and one-at-a-time (as if acting on their individual desire).

I began by making Particle and ParticleSystem classes, modeled off of the many examples in the Nature of Code Github Repo, which I then modified for the unique requirement of accepting CSV (comma separated values) data as an input. Point clouds are the raw, unprocessed data from a variety of 3D scanning technologies, which represent 3D space as many (often millions) of points with X,Y,Z coordinate values. In creating a 3D model for any practical purpose, they are often processed into mesh surfaces, but for my purpose, I wanted the raw data so that I could treat each point as an individual Particle object. This data comes in many formats, but because I didn’t want to deal with the overhead of understanding the file formats, I stuck to CSV files, in which the data looks something like this:

4.04,5.95,-0.30,37,8,10
4.02,6.04,-0.30,17,11,11
4.26,6.08,-0.30,17,11,11

Each row represents one data point, and contains an X, Y, Z value as well as an R, G, B color value that I ignored. Depending which source this data comes from, it is likely not normalized in any way. This means that when I created a point cloud with it, the point cloud would not necessarily appear around the 0,0,0 point in 3D space, and so would rotate weirdly. To make future translations and rotations simpler, I decided to do the following:

  • import x,y,z points into a table using the P5.js loadTable() function
  • find the minimum and maximum x,y,z values so that I have a sense of the outer bounds of the shape (the bounding box)
  • map the x,y,z values around 0,0,0 using these min/max values

The code for that looked something like this:

function preload(){
  loadTable("rose-st-ground.csv","csv",createParticleSystem);
}

function createParticleSystem(table){
  let xMax = 0;
  let xMin = 0;
  let yMax = 0;
  let yMin = 0;
  let zMax = 0;
  let zMin = 0;

  // first, create a bounding box around all points in the cloud
  for (let i = 0; i < table.getRowCount(); i+= reductionFactor){
    // parsefloat necessary to ensure values from file aren't compared as strings
    let x = parseFloat(table.get(i,0));
    let y = parseFloat(table.get(i,1));
    let z = parseFloat(table.get(i,2));

    if (x > xMax){
      xMax = x;
    }
    if (x < xMin){
      xMin = x;
    }
    if (y > yMax){
      yMax = y;
    }
    if (y < yMin){
      yMin = y;
    }
    if (z > zMax){
      zMax = z;
    }
    if (z < zMin){
      zMin = z;
    }
  }
  console.log('xMax,xMin,yMax,yMin,zMax,zMin:',xMax,xMin,yMax,yMin,zMax,zMin);
  bbW = xMax-xMin;
  bbH = yMax-yMin;
  bbD = zMax-zMin;
  console.log('w/h/d of bounding box: ',bbW,'/',bbH,'/',bbD);

  // use min/max values from bounding box to map everything around 0,0
  for (let i = 0; i < table.getRowCount(); i+= reductionFactor){
    let x = parseFloat(table.get(i,0));
    let y = parseFloat(table.get(i,1));
    let z = parseFloat(table.get(i,2));

    // map around 0,0 without scaling values
    x = map(x, xMin,xMax,-bbW/2,bbW/2);
    y = map(y, yMin,yMax,-bbH/2,bbH/2);
    z = map(z, zMin,zMax,-bbD/2,bbD/2);

    table.setNum(i,0,x);
    table.setNum(i,1,y);
    table.setNum(i,2,z);
  }

  sys = new PointCloudParticleSystem(table,reductionFactor,scale);
}

Now I have a point cloud centered around 0,0,0 which I am able to transform and rotate independently. It looks something like this (I added a ‘floor’ to have a better sense of the 3D space):

It doesn’t look like much, partially because it is small, and partially because the test point cloud data I used was from a scan of a building’s courtyard found here. Next, I used the Stanford Bunny from the Stanford University Computer Graphics Laboratory. This data was in PLY format, but contained within PLY is an X,Y,Z point cloud, so it was just a matter of converting it to CSV in Excel.

This done, I was now able to act on the points individually to mimic the head in Particle Dreams. I added gravity control and a control to return the points home.

P5js WEBGL Mode

This was my first foray into using the p5.js webgl functionality, and a couple of things definitely helped me understand transformations and rotations in 3D space:

  • Adding a ground plane helped me understand the size of the space I was looking at in terms of pixels,
  • Adding a keyboard control function allowed me to move my object around in 3D space. I would prefer to be able to move the camera because it makes more sense to me to move around whatever I am seeing, rather than move the object itself, but that is a challenge for another day.