Bezier Surfaces

edited March 2016 in Share Your Work

Hello, everyone.

In the long time that I've been away, I've been working on loads of 3D code (in C++, which is why it took so long). I've decided that since everything is far from ready, I'd try to implement a Bezier surface algorithm in Processing, so I can be sure things will work.

It turned out pretty cool, so I thought I'd share the bit of code in which I got Bezier surfaces to work, in case someone might want to use this.

Before I get to the code, and some usage instructions, I'll just say that this is very rough code. It is in no way meant to be fast. With the above in mind, adjust line 34 to your needs. The less the subdivision number, the faster it will run on your computer, but will have less polygons. You will get the complete opposite if you turn that number up.

Right. The code. When you launch the code, you'll see a yellow surface spinning. At the top left, you will see a 3 by 3 grid. In the grid, will hover over a box, which represents an individual control point, and turn the scroll wheel.

Press space to toggle the light grey lines.

int grabbedX = -1, grabbedY = -1;
boolean showControls = true;

float angle = 0;

PVector[][] controlPoints;

BezierSurface surface;

void setup() {
  size(1280, 720, P3D);
  ellipseMode(CENTER);
  frameRate(100000);
  smooth(8);

  surface = new BezierSurface(3, 3);
  controlPoints = new PVector[3][3];
  for (int y = 0; y < 3; y++) {
    for (int x = 0; x < 3; x++) {
      controlPoints[y][x] = new PVector(x * 240 - 240, 0, y * 240 - 240);
    }
  }
}

void draw() {
  background(255);

  for (int y = 0; y < 3; y++) {
    for (int x = 0; x < 3; x++) {
      surface.setPoint(controlPoints[y][x], x, y);
    }
  }

  PVector[][] sPoints = surface.computeSurface(20);

  pushMatrix();
  translate(width/2, height/2, 0);
  rotateX(radians(-30));
  rotateY(angle * 10);

  if (showControls) surface.showControls();

  strokeWeight(0.5);
  fill(255, 255, 0);
  beginShape(QUADS);
  for (int y = 0; y < sPoints.length - 1; y++) {
    for (int x = 0; x < sPoints[y].length - 1; x++) {
      vertex(sPoints[y][x].x, sPoints[y][x].y, sPoints[y][x].z);
      vertex(sPoints[y][x + 1].x, sPoints[y][x + 1].y, sPoints[y][x + 1].z);
      vertex(sPoints[y + 1][x + 1].x, sPoints[y + 1][x + 1].y, sPoints[y + 1][x + 1].z);
      vertex(sPoints[y + 1][x].x, sPoints[y + 1][x].y, sPoints[y + 1][x].z);
    }
  }
  endShape();
  popMatrix();

  fill(128);
  for (int y = 0; y < 3; y++) {
    for (int x = 0; x < 3; x++) {
      rect(x * 30, y * 30, 30, 30);
    }
  }
  fill(255);
  strokeWeight(1);

  angle += 0.00005;

  println(frameRate);
}

void mouseReleased() {
  grabbedX = -1;
  grabbedY = -1;
}

void mouseWheel(MouseEvent e) {
  for (int y = 0; y < 3; y++) {
    for (int x = 0; x < 3; x++) {
      if (mouseX > x * 30 & mouseX <= (x + 1) * 30 & mouseY > y * 30 & mouseY < (y + 1) * 30) controlPoints[y][x].y += e.getCount() * 10f;
    }
  }
}

void keyReleased() {
  showControls ^= true;
}

/*
 * Bezier Surface:
 * - Constructor(int degreeX, int degreeY): Constructs a BezierSurface object with degreeX * degreeY control points.
 * - void setPoint(PVector p, int x, int y): Sets the control point at coords (x, y).
 * - void showControls(): Draws lines between adjacent control points.
 * - PVector[][] computeSurface(int subdiv): Computes the surface based on the control points, 
 *                                           then returns a 2D array of PVectors, each representing a vertex.
 * - PVector computePoint(PVector[] pts, float w): Internal utility function. You won't be using this outside of the BezierSurface class.
 */

public class BezierSurface {
  private PVector[][] cPoints;
  private PVector[][] output;
  private int degreeX, degreeY, activePoints;

  public BezierSurface(int degreeX, int degreeY) {
    cPoints = new PVector[degreeY][degreeX];
    this.degreeX = degreeX;
    this.degreeY = degreeY;
  }

  public void setPoint(PVector p, int x, int y) {
    if (x < 0 | x >= degreeX | y < 0 | y >= degreeY) {
      println("Index out of bounds!");
      exit();
    }

    if (cPoints[y][x] == null) activePoints++;
    cPoints[y][x] = p;
  }

  public void showControls() {
    stroke(128);
    noFill();

    beginShape(QUADS);
    for (int y = 0; y < cPoints.length - 1; y++) {
      for (int x = 0; x < cPoints.length - 1; x++) {
        vertex(cPoints[y][x].x, cPoints[y][x].y, cPoints[y][x].z);
        vertex(cPoints[y][x + 1].x, cPoints[y][x + 1].y, cPoints[y][x + 1].z);
        vertex(cPoints[y + 1][x + 1].x, cPoints[y + 1][x + 1].y, cPoints[y + 1][x + 1].z);
        vertex(cPoints[y + 1][x].x, cPoints[y + 1][x].y, cPoints[y + 1][x].z);
      }
    }
    endShape();

    stroke(0);
    fill(255);
  }

  public PVector[][] computeSurface(int subdiv) {
    if(activePoints < degreeX * degreeY) {
      println("Not all points were initialized!");
      exit();
    }
    output = new PVector[subdiv + 1][subdiv + 1];
    for (int y = 0; y <= subdiv; y++) {
      PVector[] bPoints = new PVector[cPoints.length];
      for (int i = 0; i < cPoints.length; i++) {
        bPoints[i] = computePoint(cPoints[i], ((float) y) / subdiv);
      }

      for (int x = 0; x <= subdiv; x++) {
        output[y][x] = computePoint(bPoints, ((float) x) / subdiv);
      }
    }

    return output;
  }

  private PVector computePoint(PVector[] pts, float w) {
    PVector out = new PVector(0, 0, 0);
    for (int i = pts.length - 1; i > 0; i--) {
      PVector[] lerped = new PVector[i];
      for (int p = 0; p < i; p++) {
        lerped[p] = PVector.lerp(pts[p], pts[p + 1], w);
      }

      pts = lerped;

      if (pts.length == 1) out = pts[0];
    }

    return out;
  }
}

Given how much code I've encapsulated into a class, you should be able to figure out how to use the BezierSurface class to your needs.

Enjoy!

Sign In or Register to comment.