Sunday, August 4, 2013

PoMan3D: a simple 3d line engine for processing.js

Processingjs is amazing, the voodoo it pulls to get Java code running as Javascript is a wonder. The support for Processing's (already limited) 3D is wonky across browsers, so just for kicks I decided to go to the old fashioned ways of making lines in a kind of fakey-3D space.

Here is the result:

(Here is the source code)

All my engine can do is make lines in 3D space, centered at around an origin at 0,0,0, and rotateable at that origin on the X and Y axis (unlike some folks who do this stuff more seriously, I keep thinking of X and Y as being on the screen, and Z into the screen). It is optimized for working in a cube from -100,-100,-100 to 100,100,100

In setup() the code is:
PoMan3D pm3d;
pm3d = new PoMan3D(500, 500); //constructor given the screen size

Then, for every call of draw() :
  pm3d.setScale(1.0); //optional call, 1 is default
  pm3d.rotateToMouse(); //convenience function, or call setRotateX()/setRotateY()
//add lines here

There are two ways of adding lines: you can start a single endpoint (with x,y,z and color), and then add in a series of (x,y,z) points to make line segments. You can also just add a line with a color and two x,y,z endpoints:


  pm3d.add3Dline(-100, 100, -100, -100, -100, -100, BLACK);

So that's it... pretty easy. Besides rotating and scaling, its painting the line in "painter's order", i.e. back to front. It's not perfect, because it's taking the depth of an average of the endpoint, but in practice it's not terrible. It shouldn't be hard to put in polygons, or make stroke weight a bit smarter...
Anyway, here's the class:

class PoMan3D {
  private float screenCenterX, screenCenterY;
  private float scaler = 1.0;
  private float rotateX,rotateY;
  private ArrayList<scaledLine> linesToDraw;// = new ArrayList<scaledLine>();
  PoMan3D(int w, int h) {
    screenCenterX = w / 2;
    screenCenterY = h / 2;

  void rotateToMouse(){

  void setRotateX(float a){
    rotateX = a;
  void setRotateY(float a){
    rotateY = a;

  void setScale(float s){
     scaler = s; 

  void startDraw() {
    linesToDraw = new ArrayList<scaledLine>();

  void addScaledLine(scaledLine s) {
    for (int i = 0; i < linesToDraw.size(); i++) {
      scaledLine c = linesToDraw.get(i);
      if (s.scaleval < c.scaleval) {
        linesToDraw.add(i, s);
  void endDraw() {
    translate(screenCenterX, screenCenterY);

    for (scaledLine s : linesToDraw) {
      line(s.s1x, s.s1y, s.s2x, s.s2y);

  private float lastX, lastY, lastZ;
  private color lastC; 
  void startLinePath(float x, float y, float z, color c) {
    lastX = x; 
    lastY = y; 
    lastZ = z;
    lastC = c;
  void addToLinePath(float x, float y, float z) {
    pm3d.add3Dline(lastX, lastY, lastZ, x, y, z, lastC);
    lastX = x; 
    lastY = y; 
    lastZ = z;
  void add3Dline(float x1, float y1, float z1, float x2, float y2, float z2, color c) {
    float f1x = x1, f2x = x2, f1y = y1, f2y = y2, f1z = z1, f2z = z2;

    f1x = rotation1(x1, z1, rotateY);
    f2x = rotation1(x2, z2, rotateY);
    f1z = rotation2(z1, x1, rotateY); 
    f2z = rotation2(z2, x2, rotateY);

    f1y = rotation1(y1, f1z, rotateX);
    f2y = rotation1(y2, f2z, rotateX);
    f1z = rotation2(f1z, y1, rotateX); 
    f2z = rotation2(f2z, y2, rotateX);

    float scale1 = map(f1z, -100, 100, .8 * scaler, 1.2  * scaler);
    float scale2 = map(f2z, -100, 100, .8 * scaler, 1.2 * scaler);
    f1x *= scale1;
    f1y *= scale1;
    f2x *= scale2;
    f2y *= scale2;

    addScaledLine(new scaledLine(f1x, f1y, f2x, f2y, scale1+scale2, c));
  private float rotation1(float a, float b, float ang) {
    return a*cos(ang) - b * sin(ang);
  private float rotation2(float a, float b, float ang) {
    return a*cos(ang) + b * sin(ang);
private class scaledLine {
  float s1x, s2x, s1y, s2y, scaleval;
  color c;
  scaledLine(float ps1x, float ps1y, float ps2x, float ps2y, float pscale, color pc) {
    c = pc;
    s1x = ps1x; 
    s1y = ps1y;
    s2x = ps2x;
    s2y = ps2y;
    scaleval = pscale;

  void draw() {
    line(s1x, s1y, s2x, s2y);


No comments:

Post a Comment