Drawing and orbiting a shape with many vertices

Rendering p5.Geometry objects is indeed much more efficient than either drawing triangles with beginShape/endShape, or drawing lots of individual primitives (like box and plane). The reason for this is that p5.js takes p5.Geometry objects and turns them into vertex buffers which it caches and uses to render the model each time it is referenced.

One option is to create a 3d model file (such as a Wavefront .obj file, which you can create with Blender for example), load it with loadModel and render it with model. However if you want to create your model dynamically this is possible too. You just need to create a p5.Geometry instances and populate it’s vertices and faces arrays. Here’s an example of a sketch that demonstrates rendering an Icosahedron both with beginShape/endShape and with p5.Geometry (runnable version here):

const PHI = (1 + Math.sqrt(5)) / 2;

let vertices = [
  [0, 1, PHI],

  [PHI, 0, 1],
  [0, -1, PHI],
  [-PHI, 0, 1],
  [-1, PHI, 0],
  [1, PHI, 0],

  [PHI, 0, -1],
  [1, -PHI, 0],
  [-1, -PHI, 0],
  [-PHI, 0, -1],
  [0, 1, -PHI],

  [0, -1, -PHI],
];

let useModel;
let geom;

function setup() {
  createCanvas(windowWidth, windowHeight, WEBGL);
  useModel = createCheckbox("Use Model?");
  useModel.position(10, 10);
  useModel.style('color', 'white');
  
  geom = new p5.Geometry(1, 1, constructIcosahedron);
  // This is used as the key for caching vertex buffers. Make sure it is unique.
  geom.gid = 'icosahedron';
  // Automatically calculate normals based on faces.
  geom.computeNormals();
}

// This callback is invoked by the p5.Geometry constructor. It must populated
// the vertices and faces arrays. It can also populate the uvs array and
// calculate normals.
function constructIcosahedron() {
  for (let v of vertices) {
    this.vertices.push(createVector(...v));
  }
  for (let i = 0; i < 5; i++) {
    let n = (i + 1) % 5;
    this.faces.push([
      i + 1,
      i + 6,
      n + 6
    ]);
    
    this.faces.push([
      i + 1,
      n + 1,
      0
    ]);
  }
  for (let i = 0; i < 5; i++) {
    let n = (i - 1);
    if (n < 0) {
      n = 4;
    }
    this.faces.push([
      i + 6,
      i + 1,
      n + 1
    ]);

    this.faces.push([
      i + 6,
      n + 6,
      11
    ]);
  }
}

function draw() {
  background(0);
  orbitControl(2, 2, 0.01);
  directionalLight(200, 200, 200, 0.5, -0.5, -1);
  directionalLight(200, 50, 50, 1, -0.5, 0.5);
  directionalLight(50, 50, 200, -0.5, 1, -0.5);
  

  scale(100);

  if (useModel.checked()) {
    model(geom);
  } else {
    beginShape(TRIANGLES);
    for (let i = 0; i < 5; i++) {
      vertex(...vertices[i + 1]);
      vertex(...vertices[i + 6]);
      let n = (i + 1) % 5;
      vertex(...vertices[n + 6]);

      vertex(...vertices[i + 1]);
      vertex(...vertices[n + 1]);
      vertex(...vertices[0]);
    }
    for (let i = 0; i < 5; i++) {
      vertex(...vertices[i + 6]);
      vertex(...vertices[i + 1]);
      let n = (i - 1);
      if (n < 0) {
        n = 4;
      }
      vertex(...vertices[n + 1]);

      vertex(...vertices[i + 6]);
      vertex(...vertices[n + 6]);
      vertex(...vertices[11]);
    }
    endShape();

    push();
    strokeWeight(8);
    for (let vert of vertices) {
      stroke(...vert.map(c => map(c, -PHI, PHI, 0, 255)));
      point(vert[0], vert[1], vert[2]);
    }
    pop();
  }
}

2 Likes