  protected final void add_triangle_with_clip(int a, int b, int c) {
    boolean aClipped = false;
    boolean bClipped = false;
    int     clippedCount = 0;
    cameraNear = -8;
    if (vertices[a][VZ] > cameraNear) {
      aClipped = true;
      clippedCount++;
    }
    if (vertices[b][VZ] > cameraNear) {
      bClipped = true;
      clippedCount++;
    }
    if (vertices[c][VZ] > cameraNear) {
      clippedCount++;
    }
    if (clippedCount == 0) {
      add_triangle_no_clip(a, b, c);
      return;
    }
    else if (clippedCount == 3) {
      return;
    }
    else if (clippedCount == 2) {
      int ca, cb, cc, cd, ce;
      if (!aClipped) {
        ca = a;
        cb = b;
        cc = c;
      }
      else if (!bClipped) {
        ca = b;
        cb = a;
        cc = c;
      }
      else { 
        ca = c;
        cb = b;
        cc = a;
      }
      cd = interpolate_clip_vertex(ca, cb);
      ce = interpolate_clip_vertex(ca, cc);
      add_triangle_no_clip(ca, cd, ce);
      return;
    }
    else { 
      int ca, cb, cc, cd, ce;
      if (aClipped) {
        ca = c;
        cb = b;
        cc = a;
      }
      else if (bClipped) {
        ca = a;
        cb = c;
        cc = b;
      }
      else { 
        ca = a;
        cb = b;
        cc = c;
      }
      cd = interpolate_clip_vertex(ca, cc);
      ce = interpolate_clip_vertex(cb, cc);
      add_triangle_no_clip(ca, cd, cb);
      add_triangle_no_clip(cb, cd, ce);
      return;
    }
  }
  private final int interpolate_clip_vertex(int a, int b) {
    float[] va;
    float[] vb;
    if (vertices[a][VZ] < vertices[b][VZ]) {
      va = vertices[b];
      vb = vertices[a];
    }
    else {
      va = vertices[a];
      vb = vertices[b];
    }
    float az = va[VZ];
    float bz = vb[VZ];
    float dz = az - bz;
    if (dz == 0) {
      return a;
    }
    float pa = (cameraNear - bz) / dz;
    float pb = 1 - pa;
    vertex(pa * va[MX] + pb * vb[MX],
           pa * va[MY] + pb * vb[MY],
           pa * va[MZ] + pb * vb[MZ]);
    int irv = vertexCount - 1;
    vertex_end_including_clip_verts++;
    float[] rv = vertices[irv];
    rv[X] = pa * va[X] + pb * vb[X];
    rv[Y] = pa * va[Y] + pb * vb[Y];
    rv[Z] = pa * va[Z] + pb * vb[Z];
    rv[VX] = pa * va[VX] + pb * vb[VX];
    rv[VY] = pa * va[VY] + pb * vb[VY];
    rv[VZ] = pa * va[VZ] + pb * vb[VZ];
    rv[VW] = pa * va[VW] + pb * vb[VW];
    rv[R] = pa * va[R] + pb * vb[R];
    rv[G] = pa * va[G] + pb * vb[G];
    rv[B] = pa * va[B] + pb * vb[B];
    rv[A] = pa * va[A] + pb * vb[A];
    rv[U] = pa * va[U] + pb * vb[U];
    rv[V] = pa * va[V] + pb * vb[V];
    rv[SR] = pa * va[SR] + pb * vb[SR];
    rv[SG] = pa * va[SG] + pb * vb[SG];
    rv[SB] = pa * va[SB] + pb * vb[SB];
    rv[SA] = pa * va[SA] + pb * vb[SA];
    rv[NX] = pa * va[NX] + pb * vb[NX];
    rv[NY] = pa * va[NY] + pb * vb[NY];
    rv[NZ] = pa * va[NZ] + pb * vb[NZ];
    rv[SW] = pa * va[SW] + pb * vb[SW];
    rv[AR] = pa * va[AR] + pb * vb[AR];
    rv[AG] = pa * va[AG] + pb * vb[AG];
    rv[AB] = pa * va[AB] + pb * vb[AB];
    rv[SPR] = pa * va[SPR] + pb * vb[SPR];
    rv[SPG] = pa * va[SPG] + pb * vb[SPG];
    rv[SPB] = pa * va[SPB] + pb * vb[SPB];
    rv[SPA] = pa * va[SPA] + pb * vb[SPA];
    rv[ER] = pa * va[ER] + pb * vb[ER];
    rv[EG] = pa * va[EG] + pb * vb[EG];
    rv[EB] = pa * va[EB] + pb * vb[EB];
    rv[SHINE] = pa * va[SHINE] + pb * vb[SHINE];
    rv[BEEN_LIT] = 0;
    return irv;
  }
  protected final void add_triangle_no_clip(int a, int b, int c) {
    if (triangleCount == triangles.length) {
      int temp[][] = new int[triangleCount<<1][TRIANGLE_FIELD_COUNT];
      System.arraycopy(triangles, 0, temp, 0, triangleCount);
      triangles = temp;
      float ftemp[][][] = new float[triangleCount<<1][3][TRIANGLE_COLOR_COUNT];
      System.arraycopy(triangleColors, 0, ftemp, 0, triangleCount);
      triangleColors = ftemp;
    }
    triangles[triangleCount][VERTEX1] = a;
    triangles[triangleCount][VERTEX2] = b;
    triangles[triangleCount][VERTEX3] = c;
    if (textureImage == null) {
      triangles[triangleCount][TEXTURE_INDEX] = -1;
    } else {
      triangles[triangleCount][TEXTURE_INDEX] = texture_index;
    }
    triangles[triangleCount][INDEX] = shape_index;
    triangleCount++;
  }
  protected void depth_sort_triangles() {
    depth_sort_triangles_internal(0, triangleCount-1);
  }
  protected void depth_sort_triangles_internal(int i, int j) {
    int pivotIndex = (i+j)/2;
    depth_sort_triangles_swap(pivotIndex, j);
    int k = depth_sort_triangles_partition(i-1, j);
    depth_sort_triangles_swap(k, j);
    if ((k-i) > 1) depth_sort_triangles_internal(i, k-1);
    if ((j-k) > 1) depth_sort_triangles_internal(k+1, j);
  }
  protected int depth_sort_triangles_partition(int left, int right) {
    int pivot = right;
    do {
      while (depth_sort_triangles_compare(++left, pivot) < 0) { }
      while ((right != 0) &&
             (depth_sort_triangles_compare(--right, pivot) > 0)) { }
      depth_sort_triangles_swap(left, right);
    } while (left < right);
    depth_sort_triangles_swap(left, right);
    return left;
  }
  protected void depth_sort_triangles_swap(int a, int b) {
    int tempi[] = triangles[a];
    triangles[a] = triangles[b];
    triangles[b] = tempi;
    float tempf[][] = triangleColors[a];
    triangleColors[a] = triangleColors[b];
    triangleColors[b] = tempf;
  }
  protected float depth_sort_triangles_compare(int a, int b) {
    if (Float.isNaN(vertices[triangles[a][VERTEX1]][Z]) ||
        Float.isNaN(vertices[triangles[a][VERTEX2]][Z]) || 
        Float.isNaN(vertices[triangles[a][VERTEX3]][Z]) || 
        Float.isNaN(vertices[triangles[b][VERTEX1]][Z]) || 
        Float.isNaN(vertices[triangles[b][VERTEX2]][Z]) || 
        Float.isNaN(vertices[triangles[b][VERTEX3]][Z])) {
      throw new RuntimeException("nan triangle");
    }
    return
      (vertices[triangles[b][VERTEX1]][Z] +
       vertices[triangles[b][VERTEX2]][Z] +
       vertices[triangles[b][VERTEX3]][Z]) -
      (vertices[triangles[a][VERTEX1]][Z] +
       vertices[triangles[a][VERTEX2]][Z] +
       vertices[triangles[a][VERTEX3]][Z]);
  }
  protected void render_triangles() {
    if (raw != null) {
      raw.colorMode(RGB, 1);
      raw.noStroke();
      raw.beginShape(TRIANGLES);
    }
    for (int i = 0; i < triangleCount; i ++) {
      float a[] = vertices[triangles[i][VERTEX1]];
      float b[] = vertices[triangles[i][VERTEX2]];
      float c[] = vertices[triangles[i][VERTEX3]];
      int tex = triangles[i][TEXTURE_INDEX];
      int index = triangles[i][INDEX];
      triangle.reset();
      float ar = min(1, triangleColors[i][0][TRI_DIFFUSE_R] +
                     triangleColors[i][0][TRI_SPECULAR_R]);
      float ag = min(1, triangleColors[i][0][TRI_DIFFUSE_G] +
                     triangleColors[i][0][TRI_SPECULAR_G]);
      float ab = min(1, triangleColors[i][0][TRI_DIFFUSE_B] +
                     triangleColors[i][0][TRI_SPECULAR_B]);
      float br = min(1, triangleColors[i][1][TRI_DIFFUSE_R] +
                     triangleColors[i][1][TRI_SPECULAR_R]);
      float bg = min(1, triangleColors[i][1][TRI_DIFFUSE_G] +
                     triangleColors[i][1][TRI_SPECULAR_G]);
      float bb = min(1, triangleColors[i][1][TRI_DIFFUSE_B] +
                     triangleColors[i][1][TRI_SPECULAR_B]);
      float cr = min(1, triangleColors[i][2][TRI_DIFFUSE_R] +
                     triangleColors[i][2][TRI_SPECULAR_R]);
      float cg = min(1, triangleColors[i][2][TRI_DIFFUSE_G] +
                     triangleColors[i][2][TRI_SPECULAR_G]);
      float cb = min(1, triangleColors[i][2][TRI_DIFFUSE_B] +
                     triangleColors[i][2][TRI_SPECULAR_B]);
      if (tex > -1 && textures[tex] != null) {
        triangle.setTexture(textures[tex]);
        triangle.setUV(a[U], a[V], b[U], b[V], c[U], c[V]);
      }
      triangle.setIntensities(ar, ag, ab, a[A],
                              br, bg, bb, b[A],
                              cr, cg, cb, c[A]);
      triangle.setVertices(a[X], a[Y], a[Z],
                           b[X], b[Y], b[Z],
                           c[X], c[Y], c[Z]);
      triangle.setIndex(index);
      triangle.render();
      if (raw != null) {
        if (raw instanceof PGraphics3D) {
          if ((a[VW] != 0) && (b[VW] != 0) && (c[VW] != 0)) {
            raw.fill(ar, ag, ab, a[A]);
            raw.vertex(a[VX] / a[VW], a[VY] / a[VW], a[VZ] / a[VW]);
            raw.fill(br, bg, bb, b[A]);
            raw.vertex(b[VX] / b[VW], b[VY] / b[VW], b[VZ] / b[VW]);
            raw.fill(cr, cg, cb, c[A]);
            raw.vertex(c[VX] / c[VW], c[VY] / c[VW], c[VZ] / c[VW]);
          }
        } else {
          raw.fill(ar, ag, ab, a[A]);
          raw.vertex(a[X], a[Y]);
          raw.fill(br, bg, bb, b[A]);
          raw.vertex(b[X], b[Y]);
          raw.fill(cr, cg, cb, c[A]);
          raw.vertex(c[X], c[Y]);
        }
      }
    }
    if (raw != null) {
      raw.endShape();
    }
  }
  protected void depth_sort_lines() {
  }
  protected void render_lines() {
    if (raw != null) {
      raw.colorMode(RGB, 1);
      raw.noFill();
      raw.beginShape(LINES);
    }
    for (int i = 0; i < lineCount; i ++) {
      float a[] = vertices[lines[i][VERTEX1]];
      float b[] = vertices[lines[i][VERTEX2]];
      int index = lines[i][INDEX];
      line.reset();
      line.setIntensities(a[SR], a[SG], a[SB], a[SA],
                          b[SR], b[SG], b[SB], b[SA]);
      line.setVertices(a[X], a[Y], a[Z],
                       b[X], b[Y], b[Z]);
      if (raw != null) {
        if (raw instanceof PGraphics3D) {
          if ((a[VW] != 0) && (b[VW] != 0)) {
            raw.stroke(a[SR], a[SG], a[SB], a[SA]);
            raw.vertex(a[VX] / a[VW], a[VY] / a[VW], a[VZ] / a[VW]);
            raw.stroke(b[SR], b[SG], b[SB], b[SA]);
            raw.vertex(b[VX] / b[VW], b[VY] / b[VW], b[VZ] / b[VW]);
          }
        } else {
          raw.stroke(a[SR], a[SG], a[SB], a[SA]);
          raw.vertex(a[X], a[Y]);
          raw.stroke(b[SR], b[SG], b[SB], b[SA]);
          raw.vertex(b[X], b[Y]);
        }
      }
      line.setIndex(index);
      line.draw();
    }
    if (raw != null) {
      raw.endShape();
    }
  }
  private void triangulate_polygon() {
    int d1 = MX;
    int d2 = MY;
    float area = 0;
    for (int p = vertex_end - 1, q = vertex_start; q < vertex_end; p = q++) {
      area += (vertices[q][d1] * vertices[p][d2] -
               vertices[p][d1] * vertices[q][d2]);
    }
    if (area == 0) {
      boolean foundValidX = false;
      boolean foundValidY = false;
      for (int i = vertex_start; i < vertex_end; i++) {
        if (vertices[i][MX] != 0) foundValidX = true;
        if (vertices[i][MY] != 0) foundValidY = true;
      }
      if (foundValidX && !foundValidY) {
        d2 = MZ;
      } else if (!foundValidX && foundValidY) {
        d1 = MY;
        d2 = MZ;
      } else {
        return;
      }
      for (int p = vertex_end - 1, q = vertex_start; q < vertex_end; p = q++) {
        area += (vertices[q][d1] * vertices[p][d2] -
                 vertices[p][d1] * vertices[q][d2]);
      }
    }
    float vfirst[] = vertices[vertex_start];
    float vlast[] = vertices[vertex_end-1];
    if ((abs(vfirst[MX] - vlast[MX]) < EPSILON) &&
        (abs(vfirst[MY] - vlast[MY]) < EPSILON) &&
        (abs(vfirst[MZ] - vlast[MZ]) < EPSILON)) {
      vertex_end--;
    }
    int j = 0;
    if (area > 0) {
      for (int i = vertex_start; i < vertex_end; i++) {
        j = i - vertex_start;
        vertex_order[j] = i;
      }
    } else {
      for (int i = vertex_start; i < vertex_end; i++) {
        j = i - vertex_start;
        vertex_order[j] = (vertex_end - 1) - j;
      }
    }
    int vc = vertex_end - vertex_start;
    int count = 2*vc;  
    for (int m = 0, v = vc - 1; vc > 2; ) {
      boolean snip = true;
      if (0 >= (count--)) {
        break; 
      }
      int u = v ; if (vc <= u) u = 0;    
      v = u + 1; if (vc <= v) v = 0;     
      int w = v + 1; if (vc <= w) w = 0; 
      float Ax = -vertices[vertex_order[u]][d1];
      float Ay =  vertices[vertex_order[u]][d2];
      float Bx = -vertices[vertex_order[v]][d1];
      float By =  vertices[vertex_order[v]][d2];
      float Cx = -vertices[vertex_order[w]][d1];
      float Cy =  vertices[vertex_order[w]][d2];
      if (EPSILON > (((Bx-Ax) * (Cy-Ay)) - ((By-Ay) * (Cx-Ax)))) {
        continue;
      }
      for (int p = 0; p < vc; p++) {
        if( (p == u) || (p == v) || (p == w) ) {
          continue;
        }
        float Px = -vertices[vertex_order[p]][d1];
        float Py =  vertices[vertex_order[p]][d2];
        float ax  = Cx - Bx;  float ay  = Cy - By;
        float bx  = Ax - Cx;  float by  = Ay - Cy;
        float cx  = Bx - Ax;  float cy  = By - Ay;
        float apx = Px - Ax;  float apy = Py - Ay;
        float bpx = Px - Bx;  float bpy = Py - By;
        float cpx = Px - Cx;  float cpy = Py - Cy;
        float aCROSSbp = ax * bpy - ay * bpx;
        float cCROSSap = cx * apy - cy * apx;
        float bCROSScp = bx * cpy - by * cpx;
        if ((aCROSSbp >= 0.0f) && (bCROSScp >= 0.0f) && (cCROSSap >= 0.0f)) {
          snip = false;
        }
      }
      if (snip) {
        add_triangle(vertex_order[u], vertex_order[v], vertex_order[w]);
        m++;
        for (int s = v, t = v + 1; t < vc; s++, t++) {
          vertex_order[s] = vertex_order[t];
        }
        vc--;
        count = 2 * vc;
      }
    }
  }
  private void toWorldNormal(float nx, float ny, float nz, float[] out) {
    out[0] =
      modelviewInv.m00*nx + modelviewInv.m10*ny +
      modelviewInv.m20*nz + modelviewInv.m30;
    out[1] =
      modelviewInv.m01*nx + modelviewInv.m11*ny +
      modelviewInv.m21*nz + modelviewInv.m31;
    out[2] =
      modelviewInv.m02*nx + modelviewInv.m12*ny +
      modelviewInv.m22*nz + modelviewInv.m32;
    out[3] =
      modelviewInv.m03*nx + modelviewInv.m13*ny +
      modelviewInv.m23*nz + modelviewInv.m33;
    if (out[3] != 0 && out[3] != ONE) {
      out[0] /= out[3]; out[1] /= out[3]; out[2] /= out[3];
    }
    out[3] = 1;
    float nlen = mag(out[0], out[1], out[2]);  
    if (nlen != 0 && nlen != ONE) {
      out[0] /= nlen; out[1] /= nlen; out[2] /= nlen;
    }
  }
  private void calc_lighting_contribution(int vIndex,
                                          float[] contribution) {
    calc_lighting_contribution(vIndex, contribution, false);
  }
  private void calc_lighting_contribution(int vIndex,
                                          float[] contribution,
                                          boolean normalIsWorld) {
    float[] v = vertices[vIndex];
    float sr = v[SPR];
    float sg = v[SPG];
    float sb = v[SPB];
    float wx = v[VX];
    float wy = v[VY];
    float wz = v[VZ];
    float shine = v[SHINE];
    float nx;
    float ny;
    float nz;
    if (!normalIsWorld) {
      toWorldNormal(v[NX], v[NY], v[NZ], worldNormal);
      nx = worldNormal[X];
      ny = worldNormal[Y];
      nz = worldNormal[Z];
    }
    else {
      nx = v[NX];
      ny = v[NY];
      nz = v[NZ];
    }
    float dir = dot(nx, ny, nz, -wx, -wy, -wz);
    if (dir < 0) {
      nx = -nx;
      ny = -ny;
      nz = -nz;
    }
    contribution[LIGHT_AMBIENT_R] = 0;
    contribution[LIGHT_AMBIENT_G] = 0;
    contribution[LIGHT_AMBIENT_B] = 0;
    contribution[LIGHT_DIFFUSE_R] = 0;
    contribution[LIGHT_DIFFUSE_G] = 0;
    contribution[LIGHT_DIFFUSE_B] = 0;
    contribution[LIGHT_SPECULAR_R] = 0;
    contribution[LIGHT_SPECULAR_G] = 0;
    contribution[LIGHT_SPECULAR_B] = 0;
    for (int i = 0; i < lightCount; i++) {
      float denom = lightFalloffConstant[i];
      float spotTerm = 1;
      if (lightType[i] == AMBIENT) {
        if (lightFalloffQuadratic[i] != 0 || lightFalloffLinear[i] != 0) {
          float distSq = mag(lightPosition[i][0] - wx,
                             lightPosition[i][1] - wy,
                             lightPosition[i][2] - wz);
          denom +=
            lightFalloffQuadratic[i] * distSq +
            lightFalloffLinear[i] * (float) sqrt(distSq);
        }
        if (denom == 0) denom = 1;
        contribution[LIGHT_AMBIENT_R] += lightDiffuse[i][0] / denom;
        contribution[LIGHT_AMBIENT_G] += lightDiffuse[i][1] / denom;
        contribution[LIGHT_AMBIENT_B] += lightDiffuse[i][2] / denom;
      } else {
        float lix, liy, liz;
        float lightDir_dot_li = 0;
        float n_dot_li = 0;
        if (lightType[i] == DIRECTIONAL) {
          lix = -lightNormal[i][0];
          liy = -lightNormal[i][1];
          liz = -lightNormal[i][2];
          denom = 1;
          n_dot_li = (nx * lix + ny * liy + nz * liz);
          if (n_dot_li <= 0) {
            continue;
          }
        } else { 
          lix = lightPosition[i][0] - wx;
          liy = lightPosition[i][1] - wy;
          liz = lightPosition[i][2] - wz;
          float distSq = mag(lix, liy, liz);
          if (distSq != 0) {
            lix /= distSq;
            liy /= distSq;
            liz /= distSq;
          }
          n_dot_li = (nx * lix + ny * liy + nz * liz);
          if (n_dot_li <= 0) {
            continue;
          }
          if (lightType[i] == SPOT) { 
            lightDir_dot_li =
              -(lightNormal[i][0] * lix +
                lightNormal[i][1] * liy +
                lightNormal[i][2] * liz);
            if (lightDir_dot_li <= lightSpotAngleCos[i]) {
              continue;
            }
            spotTerm = pow(lightDir_dot_li, lightSpotConcentration[i]);
          }
          if (lightFalloffQuadratic[i] != 0 || lightFalloffLinear[i] != 0) {
            denom +=
              lightFalloffQuadratic[i] * distSq +
              lightFalloffLinear[i] * (float) sqrt(distSq);
          }
        }
        if (denom == 0)
          denom = 1;
        float mul = n_dot_li * spotTerm / denom;
        contribution[LIGHT_DIFFUSE_R] += lightDiffuse[i][0] * mul;
        contribution[LIGHT_DIFFUSE_G] += lightDiffuse[i][1] * mul;
        contribution[LIGHT_DIFFUSE_B] += lightDiffuse[i][2] * mul;
        if ((sr > 0 || sg > 0 || sb > 0) &&
            (lightSpecular[i][0] > 0 ||
             lightSpecular[i][1] > 0 ||
             lightSpecular[i][2] > 0)) {
          float vmag = mag(wx, wy, wz);
          if (vmag != 0) {
            wx /= vmag;
            wy /= vmag;
            wz /= vmag;
          }
          float sx = lix - wx;
          float sy = liy - wy;
          float sz = liz - wz;
          vmag = mag(sx, sy, sz);
          if (vmag != 0) {
            sx /= vmag;
            sy /= vmag;
            sz /= vmag;
          }
          float s_dot_n = (sx * nx + sy * ny + sz * nz);
          if (s_dot_n > 0) {
            s_dot_n = pow(s_dot_n, shine);
            mul = s_dot_n * spotTerm / denom;
            contribution[LIGHT_SPECULAR_R] += lightSpecular[i][0] * mul;
            contribution[LIGHT_SPECULAR_G] += lightSpecular[i][1] * mul;
            contribution[LIGHT_SPECULAR_B] += lightSpecular[i][2] * mul;
          }
        }
      }
    }
    return;
  }
  private void apply_lighting_contribution(int vIndex, float[] contribution) {
    float[] v = vertices[vIndex];
    v[R] = min(1, v[ER] + v[AR] * contribution[LIGHT_AMBIENT_R] +
               v[DR] * contribution[LIGHT_DIFFUSE_R]);
    v[G] = min(1, v[EG] + v[AG] * contribution[LIGHT_AMBIENT_G] +
               v[DG] * contribution[LIGHT_DIFFUSE_G]);
    v[B] = min(1, v[EB] + v[AB] * contribution[LIGHT_AMBIENT_R] +
               v[DB] * contribution[LIGHT_DIFFUSE_B]);
    v[A] = min(1, v[DA]);
    v[SPR] = min(1, v[SPR] * contribution[LIGHT_SPECULAR_R]);
    v[SPG] = min(1, v[SPG] * contribution[LIGHT_SPECULAR_G]);
    v[SPB] = min(1, v[SPB] * contribution[LIGHT_SPECULAR_B]);
    v[SPA] = min(1, v[SPA]);
    v[BEEN_LIT] = 1;
  }
  private void light_vertex_always(int vIndex, float[] contribution) {
    calc_lighting_contribution(vIndex, contribution);
    apply_lighting_contribution(vIndex, contribution);
  }
  private void light_vertex_if_not_already_lit(int vIndex,
                                               float[] contribution) {
    if (vertices[vIndex][BEEN_LIT] == 0) {
      light_vertex_always(vIndex, contribution);
    }
  }
  private void copy_prelit_vertex_color_to_triangle(int triIndex, int vIndex,
                                                    int colorIndex) {
    float[] triColor = triangleColors[triIndex][colorIndex];
    float[] v = vertices[vIndex];
    triColor[TRI_DIFFUSE_R] = v[R];
    triColor[TRI_DIFFUSE_G] = v[G];
    triColor[TRI_DIFFUSE_B] = v[B];
    triColor[TRI_DIFFUSE_A] = v[A];
    triColor[TRI_SPECULAR_R] = v[SPR];
    triColor[TRI_SPECULAR_G] = v[SPG];
    triColor[TRI_SPECULAR_B] = v[SPB];
    triColor[TRI_SPECULAR_A] = v[SPA];
  }
  private void copy_vertex_color_to_triangle(int triIndex,
                                             int vIndex, int colorIndex,
                                             float[] lightContribution) {
    float[] triColor = triangleColors[triIndex][colorIndex];
    float[] v = vertices[vIndex];
    triColor[TRI_DIFFUSE_R] =
      min(1, v[ER] + v[AR] * lightContribution[LIGHT_AMBIENT_R] +
          v[DR] * lightContribution[LIGHT_DIFFUSE_R]);
    triColor[TRI_DIFFUSE_G] =
      min(1, v[EG] + v[AG] * lightContribution[LIGHT_AMBIENT_G] +
          v[DG] * lightContribution[LIGHT_DIFFUSE_G]);
    triColor[TRI_DIFFUSE_B] =
      min(1, v[EB] + v[AB] * lightContribution[LIGHT_AMBIENT_B] +
          v[DB] * lightContribution[LIGHT_DIFFUSE_B]);
    triColor[TRI_DIFFUSE_A] = min(1, v[DA]);
    triColor[TRI_SPECULAR_R] =
      min(1, v[SPR] * lightContribution[LIGHT_SPECULAR_R]);
    triColor[TRI_SPECULAR_G] =
      min(1, v[SPG] * lightContribution[LIGHT_SPECULAR_G]);
    triColor[TRI_SPECULAR_B] =
      min(1, v[SPB] * lightContribution[LIGHT_SPECULAR_B]);
    triColor[TRI_SPECULAR_A] = min(1, v[SPA]);
  }
  private void light_triangle(int triIndex, float[] lightContribution) {
    int vIndex = triangles[triIndex][VERTEX1];
    copy_vertex_color_to_triangle(triIndex, vIndex, 0, lightContribution);
    vIndex = triangles[triIndex][VERTEX2];
    copy_vertex_color_to_triangle(triIndex, vIndex, 1, lightContribution);
    vIndex = triangles[triIndex][VERTEX3];
    copy_vertex_color_to_triangle(triIndex, vIndex, 2, lightContribution);
  }
  private void crossProduct(float[] u, float[] v, float[] out) {
    out[0] = u[1]*v[2] - u[2]*v[1];
    out[1] = u[2]*v[0] - u[0]*v[2];
    out[2] = u[0]*v[1] - u[1]*v[0];
  }
  private void light_triangle(int triIndex) {
    int vIndex;
    if (normalMode == MANUAL_VERTEX_NORMAL) {
      vIndex = triangles[triIndex][VERTEX1];
      light_vertex_if_not_already_lit(vIndex, tempLightingContribution);
      copy_prelit_vertex_color_to_triangle(triIndex, vIndex, 0);
      vIndex = triangles[triIndex][VERTEX2];
      light_vertex_if_not_already_lit(vIndex, tempLightingContribution);
      copy_prelit_vertex_color_to_triangle(triIndex, vIndex, 1);
      vIndex = triangles[triIndex][VERTEX3];
      light_vertex_if_not_already_lit(vIndex, tempLightingContribution);
      copy_prelit_vertex_color_to_triangle(triIndex, vIndex, 2);
    }
    else if (!lightingDependsOnVertexPosition) {
      vIndex = triangles[triIndex][VERTEX1];
      int vIndex2 = triangles[triIndex][VERTEX2];
      int vIndex3 = triangles[triIndex][VERTEX3];
      dv1[0] = vertices[vIndex2][VX] - vertices[vIndex][VX];
      dv1[1] = vertices[vIndex2][VY] - vertices[vIndex][VY];
      dv1[2] = vertices[vIndex2][VZ] - vertices[vIndex][VZ];
      dv2[0] = vertices[vIndex3][VX] - vertices[vIndex][VX];
      dv2[1] = vertices[vIndex3][VY] - vertices[vIndex][VY];
      dv2[2] = vertices[vIndex3][VZ] - vertices[vIndex][VZ];
      crossProduct(dv1, dv2, norm);
      float nMag = mag(norm[X], norm[Y], norm[Z]);
      if (nMag != 0 && nMag != ONE) {
        norm[X] /= nMag; norm[Y] /= nMag; norm[Z] /= nMag;
      }
      vertices[vIndex][NX] = norm[X];
      vertices[vIndex][NY] = norm[Y];
      vertices[vIndex][NZ] = norm[Z];
      calc_lighting_contribution(vIndex, tempLightingContribution, true);
      copy_vertex_color_to_triangle(triIndex, vIndex, 0,
                                    tempLightingContribution);
      copy_vertex_color_to_triangle(triIndex, vIndex2, 1,
                                    tempLightingContribution);
      copy_vertex_color_to_triangle(triIndex, vIndex3, 2,
                                    tempLightingContribution);
    }
    else {
      if (normalMode == MANUAL_SHAPE_NORMAL) {
        vIndex = triangles[triIndex][VERTEX1];
        vertices[vIndex][NX] = vertices[vertex_start][NX];
        vertices[vIndex][NY] = vertices[vertex_start][NY];
        vertices[vIndex][NZ] = vertices[vertex_start][NZ];
        calc_lighting_contribution(vIndex, tempLightingContribution);
        copy_vertex_color_to_triangle(triIndex, vIndex, 0,
                                      tempLightingContribution);
        vIndex = triangles[triIndex][VERTEX2];
        vertices[vIndex][NX] = vertices[vertex_start][NX];
        vertices[vIndex][NY] = vertices[vertex_start][NY];
        vertices[vIndex][NZ] = vertices[vertex_start][NZ];
        calc_lighting_contribution(vIndex, tempLightingContribution);
        copy_vertex_color_to_triangle(triIndex, vIndex, 1,
                                      tempLightingContribution);
        vIndex = triangles[triIndex][VERTEX3];
        vertices[vIndex][NX] = vertices[vertex_start][NX];
        vertices[vIndex][NY] = vertices[vertex_start][NY];
        vertices[vIndex][NZ] = vertices[vertex_start][NZ];
        calc_lighting_contribution(vIndex, tempLightingContribution);
        copy_vertex_color_to_triangle(triIndex, vIndex, 2,
                                      tempLightingContribution);
      }
      else {
        vIndex = triangles[triIndex][VERTEX1];
        int vIndex2 = triangles[triIndex][VERTEX2];
        int vIndex3 = triangles[triIndex][VERTEX3];
        dv1[0] = vertices[vIndex2][VX] - vertices[vIndex][VX];
        dv1[1] = vertices[vIndex2][VY] - vertices[vIndex][VY];
        dv1[2] = vertices[vIndex2][VZ] - vertices[vIndex][VZ];
        dv2[0] = vertices[vIndex3][VX] - vertices[vIndex][VX];
        dv2[1] = vertices[vIndex3][VY] - vertices[vIndex][VY];
        dv2[2] = vertices[vIndex3][VZ] - vertices[vIndex][VZ];
        crossProduct(dv1, dv2, norm);
        float nMag = mag(norm[X], norm[Y], norm[Z]);
        if (nMag != 0 && nMag != ONE) {
          norm[X] /= nMag; norm[Y] /= nMag; norm[Z] /= nMag;
        }
        vertices[vIndex][NX] = norm[X];
        vertices[vIndex][NY] = norm[Y];
        vertices[vIndex][NZ] = norm[Z];
        calc_lighting_contribution(vIndex, tempLightingContribution, true);
        copy_vertex_color_to_triangle(triIndex, vIndex, 0,
                                      tempLightingContribution);
        vertices[vIndex2][NX] = norm[X];
        vertices[vIndex2][NY] = norm[Y];
        vertices[vIndex2][NZ] = norm[Z];
        calc_lighting_contribution(vIndex2, tempLightingContribution, true);
        copy_vertex_color_to_triangle(triIndex, vIndex2, 1,
                                      tempLightingContribution);
        vertices[vIndex3][NX] = norm[X];
        vertices[vIndex3][NY] = norm[Y];
        vertices[vIndex3][NZ] = norm[Z];
        calc_lighting_contribution(vIndex3, tempLightingContribution, true);
        copy_vertex_color_to_triangle(triIndex, vIndex3, 2,
                                      tempLightingContribution);
      }
    }
  }
  protected void handle_lighting() {
    if (!lightingDependsOnVertexPosition && normalMode == MANUAL_SHAPE_NORMAL) {
      calc_lighting_contribution(vertex_start, tempLightingContribution);
      for (int tri = 0; tri < triangleCount; tri++) {
        light_triangle(tri, tempLightingContribution);
      }
    }
    else {
      for (int tri = 0; tri < triangleCount; tri++) {
        light_triangle(tri);
      }
    }
  }
  protected void handle_no_lighting() {
    int vIndex;
    for (int tri = 0; tri < triangleCount; tri++) {
      vIndex = triangles[tri][VERTEX1];
      copy_prelit_vertex_color_to_triangle(tri, vIndex, 0);
      vIndex = triangles[tri][VERTEX2];
      copy_prelit_vertex_color_to_triangle(tri, vIndex, 1);
      vIndex = triangles[tri][VERTEX3];
      copy_prelit_vertex_color_to_triangle(tri, vIndex, 2);
    }
  }
  public void point(float x, float y) {
    point(x, y, 0);
  }
  public void point(float x, float y, float z) {
    beginShape(LINES);
    vertex(x, y, z);
    vertex(x + EPSILON, y + EPSILON, z);
    endShape();
  }
  public void triangle(float x1, float y1, float x2, float y2,
                       float x3, float y3) {
    beginShape(TRIANGLES);
    normal(0, 0, 1);
    vertex(x1, y1);
    vertex(x2, y2);
    vertex(x3, y3);
    endShape();
  }
  public void quad(float x1, float y1, float x2, float y2,
                   float x3, float y3, float x4, float y4) {
    beginShape(QUADS);
    normal(0, 0, 1);
    vertex(x1, y1);
    vertex(x2, y2);
    vertex(x3, y3);
    vertex(x4, y4);
    endShape();
  }
  public void box(float size) {
    box(size, size, size);
  }
  public void box(float w, float h, float d) {
    float x1 = -w/2f; float x2 = w/2f;
    float y1 = -h/2f; float y2 = h/2f;
    float z1 = -d/2f; float z2 = d/2f;
    if (triangle != null) {  
      triangle.setCulling(true);
    }
    beginShape(QUADS);
    normal(0, 0, 1);
    vertex(x1, y1, z1);
    vertex(x2, y1, z1);
    vertex(x2, y2, z1);
    vertex(x1, y2, z1);
    normal(1, 0, 0);
    vertex(x2, y1, z1);
    vertex(x2, y1, z2);
    vertex(x2, y2, z2);
    vertex(x2, y2, z1);
    normal(0, 0, -1);
    vertex(x2, y1, z2);
    vertex(x1, y1, z2);
    vertex(x1, y2, z2);
    vertex(x2, y2, z2);
    normal(-1, 0, 0);
    vertex(x1, y1, z2);
    vertex(x1, y1, z1);
    vertex(x1, y2, z1);
    vertex(x1, y2, z2);
    normal(0, 1, 0);
    vertex(x1, y1, z2);
    vertex(x2, y1, z2);
    vertex(x2, y1, z1);
    vertex(x1, y1, z1);
    normal(0, -1, 0);
    vertex(x1, y2, z1);
    vertex(x2, y2, z1);
    vertex(x2, y2, z2);
    vertex(x1, y2, z2);
    endShape();
    if (triangle != null) {  
      triangle.setCulling(false);
    }
  }
  public void sphereDetail(int res) {
    if (res < 3) res = 3; 
    if (res == sphereDetail) return;
    float delta = (float)SINCOS_LENGTH/res;
    float[] cx = new float[res];
    float[] cz = new float[res];
    for (int i = 0; i < res; i++) {
      cx[i] = cosLUT[(int) (i*delta) % SINCOS_LENGTH];
      cz[i] = sinLUT[(int) (i*delta) % SINCOS_LENGTH];
    }
    int vertCount = res * (res-1) + 2;
    int currVert = 0;
    sphereX = new float[vertCount];
    sphereY = new float[vertCount];
    sphereZ = new float[vertCount];
    float angle_step = (SINCOS_LENGTH*0.5f)/res;
    float angle = angle_step;
    for (int i = 1; i < res; i++) {
      float curradius = sinLUT[(int) angle % SINCOS_LENGTH];
      float currY = -cosLUT[(int) angle % SINCOS_LENGTH];
      for (int j = 0; j < res; j++) {
        sphereX[currVert] = cx[j] * curradius;
        sphereY[currVert] = currY;
        sphereZ[currVert++] = cz[j] * curradius;
      }
      angle += angle_step;
    }
    sphereDetail = res;
  }
  public void sphere(float r) {
    float x = 0;  
    float y = 0;
    float z = 0;
    if (sphereDetail == 0) {
      sphereDetail(30);
    }
    int v1,v11,v2;
    pushMatrix();
    if (x!=0f && y!=0f && z!=0f) translate(x,y,z);
    scale(r);
    if (triangle != null) {  
      triangle.setCulling(true);
    }
    beginShape(TRIANGLE_STRIP);
    for (int i = 0; i < sphereDetail; i++) {
      normal(0, -1, 0);
      vertex(0, -1, 0);
      normal(sphereX[i], sphereY[i], sphereZ[i]);
      vertex(sphereX[i], sphereY[i], sphereZ[i]);
    }
    vertex(0, -1, 0);
    normal(sphereX[0], sphereY[0], sphereZ[0]);
    vertex(sphereX[0], sphereY[0], sphereZ[0]);
    endShape();
    int voff = 0;
    for(int i = 2; i < sphereDetail; i++) {
      v1=v11=voff;
      voff += sphereDetail;
      v2=voff;
      beginShape(TRIANGLE_STRIP);
      for (int j = 0; j < sphereDetail; j++) {
        normal(sphereX[v1], sphereY[v1], sphereZ[v1]);
        vertex(sphereX[v1], sphereY[v1], sphereZ[v1++]);
        normal(sphereX[v2], sphereY[v2], sphereZ[v2]);
        vertex(sphereX[v2], sphereY[v2], sphereZ[v2++]);
      }
      v1=v11;
      v2=voff;
      normal(sphereX[v1], sphereY[v1], sphereZ[v1]);
      vertex(sphereX[v1], sphereY[v1], sphereZ[v1]);
      normal(sphereX[v2], sphereY[v2], sphereZ[v2]);
      vertex(sphereX[v2], sphereY[v2], sphereZ[v2]);
      endShape();
    }
    beginShape(TRIANGLE_STRIP);
    for (int i = 0; i < sphereDetail; i++) {
      v2 = voff + i;
      normal(sphereX[v2], sphereY[v2], sphereZ[v2]);
      vertex(sphereX[v2], sphereY[v2], sphereZ[v2]);
      normal(0, 1, 0);
    vertex(0, 1, 0);
    }
    normal(sphereX[voff], sphereY[voff], sphereZ[voff]);
    vertex(sphereX[voff], sphereY[voff], sphereZ[voff]);
    normal(0, 1, 0);
    vertex(0, 1, 0);
    endShape();
    popMatrix();
    if (triangle != null) {  
      triangle.setCulling(false);
    }
  }
  public void translate(float tx, float ty) {
    translate(tx, ty, 0);
  }
  public void translate(float tx, float ty, float tz) {
    forwardTransform.translate(tx, ty, tz);
    reverseTransform.invTranslate(tx, ty, tz);
  }
  public void rotate(float angle) {
    rotateZ(angle);
  }
  public void rotateX(float angle) {
    forwardTransform.rotateX(angle);
    reverseTransform.invRotateX(angle);
  }
  public void rotateY(float angle) {
    forwardTransform.rotateY(angle);
    reverseTransform.invRotateY(angle);
  }
  public void rotateZ(float angle) {
    forwardTransform.rotateZ(angle);
    reverseTransform.invRotateZ(angle);
  }
  public void rotate(float angle, float v0, float v1, float v2) {
    forwardTransform.rotate(angle, v0, v1, v2);
    reverseTransform.invRotate(angle, v0, v1, v2);
  }
  public void scale(float s) {
    scale(s, s, s);
  }
  public void scale(float sx, float sy) {
    scale(sx, sy, 1);
  }
  public void scale(float x, float y, float z) {
    forwardTransform.scale(x, y, z);
    reverseTransform.invScale(x, y, z);
  }
  public void pushMatrix() {
    if (!modelview.push()) {
      throw new RuntimeException("Too many calls to pushMatrix()");
    }
    modelviewInv.push();
  }
  public void popMatrix() {
    if (!modelview.pop()) {
      throw new RuntimeException("Too many calls to popMatrix() " +
                                 "(and not enough to pushMatrix)");
    }
    modelviewInv.pop();
  }
  public void resetMatrix() {
    forwardTransform.reset();
    reverseTransform.reset();
  }
  public void applyMatrix(float n00, float n01, float n02,
                          float n10, float n11, float n12) {
    throw new RuntimeException("Use applyMatrix() with a 4x4 matrix " +
                               "when using OPENGL or P3D");
  }
  public void applyMatrix(float n00, float n01, float n02, float n03,
                          float n10, float n11, float n12, float n13,
                          float n20, float n21, float n22, float n23,
                          float n30, float n31, float n32, float n33) {
    forwardTransform.apply(n00, n01, n02, n03,
                           n10, n11, n12, n13,
                           n20, n21, n22, n23,
                           n30, n31, n32, n33);
    reverseTransform.invApply(n00, n01, n02, n03,
                              n10, n11, n12, n13,
                              n20, n21, n22, n23,
                              n30, n31, n32, n33);
  }
  public void loadMatrix() {
    m00 = modelview.m00;
    m01 = modelview.m01;
    m02 = modelview.m02;
    m03 = modelview.m03;
    m10 = modelview.m10;
    m11 = modelview.m11;
    m12 = modelview.m12;
    m13 = modelview.m13;
    m20 = modelview.m20;
    m21 = modelview.m21;
    m22 = modelview.m22;
    m23 = modelview.m23;
    m30 = modelview.m30;
    m31 = modelview.m31;
    m32 = modelview.m32;
    m33 = modelview.m33;
  }
  public void printMatrix() {
    modelview.print();
  }
  public void beginCamera() {
    if (manipulatingCamera) {
      throw new RuntimeException("beginCamera() cannot be called again " +
                                 "before endCamera()");
    } else {
      manipulatingCamera = true;
      forwardTransform = cameraInv;
      reverseTransform = camera;
    }
  }
  public void endCamera() {
    if (!manipulatingCamera) {
      throw new RuntimeException("Cannot call endCamera() " +
                                 "without first calling beginCamera()");
    }
    modelview.set(camera);
    modelviewInv.set(cameraInv);
    forwardTransform = modelview;
    reverseTransform = modelviewInv;
    manipulatingCamera = false;
  }
  public void camera() {
    camera(cameraX, cameraY, cameraZ,
           cameraX, cameraY, 0,
           0, 1, 0);
  }
  public void camera(float eyeX, float eyeY, float eyeZ,
                     float centerX, float centerY, float centerZ,
                     float upX, float upY, float upZ) {
    float z0 = eyeX - centerX;
    float z1 = eyeY - centerY;
    float z2 = eyeZ - centerZ;
    float mag = sqrt(z0*z0 + z1*z1 + z2*z2);
    if (mag != 0) {
      z0 /= mag;
      z1 /= mag;
      z2 /= mag;
    }
    float y0 = upX;
    float y1 = upY;
    float y2 = upZ;
    float x0 =  y1*z2 - y2*z1;
    float x1 = -y0*z2 + y2*z0;
    float x2 =  y0*z1 - y1*z0;
    y0 =  z1*x2 - z2*x1;
    y1 = -z0*x2 + z2*x0;
    y2 =  z0*x1 - z1*x0;
    mag = sqrt(x0*x0 + x1*x1 + x2*x2);
    if (mag != 0) {
      x0 /= mag;
      x1 /= mag;
      x2 /= mag;
    }
    mag = sqrt(y0*y0 + y1*y1 + y2*y2);
    if (mag != 0) {
      y0 /= mag;
      y1 /= mag;
      y2 /= mag;
    }
    camera.set(x0, x1, x2, 0,
               y0, y1, y2, 0,
               z0, z1, z2, 0,
               0,  0,  0,  1);
    camera.translate(-eyeX, -eyeY, -eyeZ);
    cameraInv.reset();
    cameraInv.invApply(x0, x1, x2, 0,
                       y0, y1, y2, 0,
                       z0, z1, z2, 0,
                       0,  0,  0,  1);
    cameraInv.invTranslate(-eyeX, -eyeY, -eyeZ);
    modelview.set(camera);
    modelviewInv.set(cameraInv);
  }
  public void printCamera() {
    camera.print();
  }
  public void ortho() {
    ortho(0, width, 0, height, -10, 10);
  }
  public void ortho(float left, float right,
                    float bottom, float top,
                    float near, float far) {
    float x =  2.0f / (right - left);
    float y =  2.0f / (top - bottom);
    float z = -2.0f / (far - near);
    float tx = -(right + left) / (right - left);
    float ty = -(top + bottom) / (top - bottom);
    float tz = -(far + near) / (far - near);
    projection.set(x, 0, 0, tx,
                   0, y, 0, ty,
                   0, 0, z, tz,
                   0, 0, 0, 1);
  }
  public void perspective() {
    perspective(cameraFOV, cameraAspect, cameraNear, cameraFar);
  }
  public void perspective(float fov, float aspect, float zNear, float zFar) {
    float ymax = zNear * tan(fov / 2.0f);
    float ymin = -ymax;
    float xmin = ymin * aspect;
    float xmax = ymax * aspect;
    frustum(xmin, xmax, ymin, ymax, zNear, zFar);
  }
  public void frustum(float left, float right, float bottom,
                      float top, float znear, float zfar) {
    projection.set((2*znear)/(right-left), 0, (right+left)/(right-left), 0,
                   0, (2*znear)/(top-bottom), (top+bottom)/(top-bottom), 0,
                   0, 0, -(zfar+znear)/(zfar-znear),-(2*zfar*znear)/(zfar-znear),
                   0, 0, -1, 0);
  }
  public void printProjection() {
    projection.print();
  }
  public float screenX(float x, float y) {
    return screenX(x, y, 0);
  }
  public float screenY(float x, float y) {
    return screenY(x, y, 0);
  }
  public float screenX(float x, float y, float z) {
    float ax =
      modelview.m00*x + modelview.m01*y + modelview.m02*z + modelview.m03;
    float ay =
      modelview.m10*x + modelview.m11*y + modelview.m12*z + modelview.m13;
    float az =
      modelview.m20*x + modelview.m21*y + modelview.m22*z + modelview.m23;
    float aw =
      modelview.m30*x + modelview.m31*y + modelview.m32*z + modelview.m33;
    float ox =
      projection.m00*ax + projection.m01*ay +
      projection.m02*az + projection.m03*aw;
    float ow =
      projection.m30*ax + projection.m31*ay +
      projection.m32*az + projection.m33*aw;
    if (ow != 0) ox /= ow;
    return width * (1 + ox) / 2.0f;
  }
  public float screenY(float x, float y, float z) {
    float ax =
      modelview.m00*x + modelview.m01*y + modelview.m02*z + modelview.m03;
    float ay =
      modelview.m10*x + modelview.m11*y + modelview.m12*z + modelview.m13;
    float az =
      modelview.m20*x + modelview.m21*y + modelview.m22*z + modelview.m23;
    float aw =
      modelview.m30*x + modelview.m31*y + modelview.m32*z + modelview.m33;
    float oy =
      projection.m10*ax + projection.m11*ay +
      projection.m12*az + projection.m13*aw;
    float ow =
      projection.m30*ax + projection.m31*ay +
      projection.m32*az + projection.m33*aw;
    if (ow != 0) oy /= ow;
    return height * (1 + oy) / 2.0f;
  }
  public float screenZ(float x, float y, float z) {
    float ax =
      modelview.m00*x + modelview.m01*y + modelview.m02*z + modelview.m03;
    float ay =
      modelview.m10*x + modelview.m11*y + modelview.m12*z + modelview.m13;
    float az =
      modelview.m20*x + modelview.m21*y + modelview.m22*z + modelview.m23;
    float aw =
      modelview.m30*x + modelview.m31*y + modelview.m32*z + modelview.m33;
    float oz =
      projection.m20*ax + projection.m21*ay +
      projection.m22*az + projection.m23*aw;
    float ow =
      projection.m30*ax + projection.m31*ay +
      projection.m32*az + projection.m33*aw;
    if (ow != 0) oz /= ow;
    return (oz + 1) / 2.0f;
  }
  public float modelX(float x, float y, float z) {
    float ax =
      cameraInv.m00*x + cameraInv.m01*y + cameraInv.m02*z + cameraInv.m03;
    float ay =
      cameraInv.m10*x + cameraInv.m11*y + cameraInv.m12*z + cameraInv.m13;
    float az =
      cameraInv.m20*x + cameraInv.m21*y + cameraInv.m22*z + cameraInv.m23;
    float aw =
      cameraInv.m30*x + cameraInv.m31*y + cameraInv.m32*z + cameraInv.m33;
    float ox =
      modelview.m00*ax + modelview.m01*ay +
      modelview.m02*az + modelview.m03*aw;
    float ow =
      modelview.m30*ax + modelview.m31*ay +
      modelview.m32*az + modelview.m33*aw;
    return (ow != 0) ? ox / ow : ox;
  }
  public float modelY(float x, float y, float z) {
    float ax =
      cameraInv.m00*x + cameraInv.m01*y + cameraInv.m02*z + cameraInv.m03;
    float ay =
      cameraInv.m10*x + cameraInv.m11*y + cameraInv.m12*z + cameraInv.m13;
    float az =
      cameraInv.m20*x + cameraInv.m21*y + cameraInv.m22*z + cameraInv.m23;
    float aw =
      cameraInv.m30*x + cameraInv.m31*y + cameraInv.m32*z + cameraInv.m33;
    float oy =
      modelview.m10*ax + modelview.m11*ay +
      modelview.m12*az + modelview.m13*aw;
    float ow =
      modelview.m30*ax + modelview.m31*ay +
      modelview.m32*az + modelview.m33*aw;
    return (ow != 0) ? oy / ow : oy;
  }
  public float modelZ(float x, float y, float z) {
    float ax =
      cameraInv.m00*x + cameraInv.m01*y + cameraInv.m02*z + cameraInv.m03;
    float ay =
      cameraInv.m10*x + cameraInv.m11*y + cameraInv.m12*z + cameraInv.m13;
    float az =
      cameraInv.m20*x + cameraInv.m21*y + cameraInv.m22*z + cameraInv.m23;
    float aw =
      cameraInv.m30*x + cameraInv.m31*y + cameraInv.m32*z + cameraInv.m33;
    float oz =
      modelview.m20*ax + modelview.m21*ay +
      modelview.m22*az + modelview.m23*aw;
    float ow =
      modelview.m30*ax + modelview.m31*ay +
      modelview.m32*az + modelview.m33*aw;
    return (ow != 0) ? oz / ow : oz;
  }
  public void strokeJoin(int join) {
    String msg = "strokeJoin() not available with P3D";
    throw new RuntimeException(msg);
  }
  public void strokeCap(int cap) {
    String msg = "strokeCap() not available with P3D";
    throw new RuntimeException(msg);
  }
  protected void fillFromCalc() {
    super.fillFromCalc();
    ambientFromCalc();
  }
  public void ambient(int rgb) {
    if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) {  
      ambient((float) rgb);
    } else {
      colorCalcARGB(rgb, colorModeA);
      ambientFromCalc();
    }
  }
  public void ambient(float gray) {
    colorCalc(gray);
    ambientFromCalc();
  }
  public void ambient(float x, float y, float z) {
    colorCalc(x, y, z);
    ambientFromCalc();
  }
  protected void ambientFromCalc() {
    ambientR = calcR;
    ambientG = calcG;
    ambientB = calcB;
  }
  public void specular(int rgb) {
    if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) {  
      specular((float) rgb);
    } else {
      colorCalcARGB(rgb, colorModeA);
      specularFromCalc();
    }
  }
  public void specular(float gray) {
    colorCalc(gray);
    specularFromCalc();
  }
  public void specular(float gray, float alpha) {
    colorCalc(gray, alpha);
    specularFromCalc();
  }
  public void specular(float x, float y, float z) {
    colorCalc(x, y, z);
    specularFromCalc();
  }
  public void specular(float x, float y, float z, float a) {
    colorCalc(x, y, z, a);
    specularFromCalc();
  }
  protected void specularFromCalc() {
    specularR = calcR;
    specularG = calcG;
    specularB = calcB;
    specularA = calcA;
  }
  public void shininess(float shine) {
    shininess = shine;
  }
  public void emissive(int rgb) {
    if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) {  
      emissive((float) rgb);
    } else {
      colorCalcARGB(rgb, colorModeA);
      emissiveFromCalc();
    }
  }
  public void emissive(float gray) {
    colorCalc(gray);
    emissiveFromCalc();
  }
  public void emissive(float x, float y, float z) {
    colorCalc(x, y, z);
    emissiveFromCalc();
  }
  protected void emissiveFromCalc() {
    emissiveR = calcR;
    emissiveG = calcG;
    emissiveB = calcB;
  }
  public void lights() {
    int colorModeSaved = colorMode;
    colorMode = RGB;
    lightFalloff(1, 0, 0);
    lightSpecular(0, 0, 0);
    ambientLight(colorModeX * 0.5f,
                 colorModeY * 0.5f,
                 colorModeZ * 0.5f);
    directionalLight(colorModeX * 0.5f,
                     colorModeY * 0.5f,
                     colorModeZ * 0.5f,
                     0, 0, -1);
    colorMode = colorModeSaved;
    lightingDependsOnVertexPosition = false;
  }
  public void ambientLight(float r, float g, float b) {
    ambientLight(r, g, b, 0, 0, 0);
  }
  public void ambientLight(float r, float g, float b,
                           float x, float y, float z) {
    if (lightCount == MAX_LIGHTS) {
      throw new RuntimeException("can only create " + MAX_LIGHTS + " lights");
    }
    colorCalc(r, g, b);
    lightDiffuse[lightCount][0] = calcR;
    lightDiffuse[lightCount][1] = calcG;
    lightDiffuse[lightCount][2] = calcB;
    lightType[lightCount] = AMBIENT;
    lightFalloffConstant[lightCount] = currentLightFalloffConstant;
    lightFalloffLinear[lightCount] = currentLightFalloffLinear;
    lightFalloffQuadratic[lightCount] = currentLightFalloffQuadratic;
    lightPosition(lightCount, x, y, z);
    lightCount++;
  }
  public void directionalLight(float r, float g, float b,
                               float nx, float ny, float nz) {
    if (lightCount == MAX_LIGHTS) {
      throw new RuntimeException("can only create " + MAX_LIGHTS + " lights");
    }
    colorCalc(r, g, b);
    lightDiffuse[lightCount][0] = calcR;
    lightDiffuse[lightCount][1] = calcG;
    lightDiffuse[lightCount][2] = calcB;
    lightType[lightCount] = DIRECTIONAL;
    lightFalloffConstant[lightCount] = currentLightFalloffConstant;
    lightFalloffLinear[lightCount] = currentLightFalloffLinear;
    lightFalloffQuadratic[lightCount] = currentLightFalloffQuadratic;
    lightSpecular[lightCount][0] = currentLightSpecular[0];
    lightSpecular[lightCount][1] = currentLightSpecular[1];
    lightSpecular[lightCount][2] = currentLightSpecular[2];
    lightDirection(lightCount, nx, ny, nz);
    lightCount++;
  }
  public void pointLight(float r, float g, float b,
                         float x, float y, float z) {
    if (lightCount == MAX_LIGHTS) {
      throw new RuntimeException("can only create " + MAX_LIGHTS + " lights");
    }
    colorCalc(r, g, b);
    lightDiffuse[lightCount][0] = calcR;
    lightDiffuse[lightCount][1] = calcG;
    lightDiffuse[lightCount][2] = calcB;
    lightType[lightCount] = POINT;
    lightFalloffConstant[lightCount] = currentLightFalloffConstant;
    lightFalloffLinear[lightCount] = currentLightFalloffLinear;
    lightFalloffQuadratic[lightCount] = currentLightFalloffQuadratic;
    lightSpecular[lightCount][0] = currentLightSpecular[0];
    lightSpecular[lightCount][1] = currentLightSpecular[1];
    lightSpecular[lightCount][2] = currentLightSpecular[2];
    lightPosition(lightCount, x, y, z);
    lightCount++;
    lightingDependsOnVertexPosition = true;
  }
  public void spotLight(float r, float g, float b,
                        float x, float y, float z,
                        float nx, float ny, float nz,
                        float angle, float concentration) {
    if (lightCount == MAX_LIGHTS) {
      throw new RuntimeException("can only create " + MAX_LIGHTS + " lights");
    }
    colorCalc(r, g, b);
    lightDiffuse[lightCount][0] = calcR;
    lightDiffuse[lightCount][1] = calcG;
    lightDiffuse[lightCount][2] = calcB;
    lightType[lightCount] = SPOT;
    lightFalloffConstant[lightCount] = currentLightFalloffConstant;
    lightFalloffLinear[lightCount] = currentLightFalloffLinear;
    lightFalloffQuadratic[lightCount] = currentLightFalloffQuadratic;
    lightSpecular[lightCount][0] = currentLightSpecular[0];
    lightSpecular[lightCount][1] = currentLightSpecular[1];
    lightSpecular[lightCount][2] = currentLightSpecular[2];
    lightPosition(lightCount, x, y, z);
    lightDirection(lightCount, nx, ny, nz);
    lightSpotAngle[lightCount] = angle;
    lightSpotAngleCos[lightCount] = max(0, cos(angle));
    lightSpotConcentration[lightCount] = concentration;
    lightCount++;
    lightingDependsOnVertexPosition = true;
  }
  public void lightFalloff(float constant, float linear, float quadratic) {
    currentLightFalloffConstant = constant;
    currentLightFalloffLinear = linear;
    currentLightFalloffQuadratic = quadratic;
    lightingDependsOnVertexPosition = true;
  }
  public void lightSpecular(float x, float y, float z) {
    colorCalc(x, y, z);
    currentLightSpecular[0] = calcR;
    currentLightSpecular[1] = calcG;
    currentLightSpecular[2] = calcB;
    lightingDependsOnVertexPosition = true;
  }
  protected void lightPosition(int num, float x, float y, float z) {
    lightPosition[num][0] =
      modelview.m00*x + modelview.m01*y + modelview.m02*z + modelview.m03;
    lightPosition[num][1] =
      modelview.m10*x + modelview.m11*y + modelview.m12*z + modelview.m13;
    lightPosition[num][2] =
      modelview.m20*x + modelview.m21*y + modelview.m22*z + modelview.m23;
  }
  protected void lightDirection(int num, float x, float y, float z) {
    lightNormal[num][0] =
      modelviewInv.m00*x + modelviewInv.m10*y +
      modelviewInv.m20*z + modelviewInv.m30;
    lightNormal[num][1] =
      modelviewInv.m01*x + modelviewInv.m11*y +
      modelviewInv.m21*z + modelviewInv.m31;
    lightNormal[num][2] =
      modelviewInv.m02*x + modelviewInv.m12*y +
      modelviewInv.m22*z + modelviewInv.m32;
    float n = mag(lightNormal[num]);
    if (n == 0 || n == 1) return;
    lightNormal[num][0] /= n;
    lightNormal[num][1] /= n;
    lightNormal[num][2] /= n;
  }
  public void background(PImage image) {
    super.background(image);
    for (int i = 0; i < pixelCount; i++) {
      zbuffer[i] = Float.MAX_VALUE;
    }
  }
  protected void clear() {
    for (int i = 0; i < pixelCount; i++) {
      pixels[i] = backgroundColor;
      zbuffer[i] = Float.MAX_VALUE;
    }
  }
  public void smooth() {
    String msg = "smooth() not available with P3D";
    throw new RuntimeException(msg);
  }
  public void noSmooth() {
    String msg = "noSmooth() not available with P3D";
    throw new RuntimeException(msg);
  }
  private final float mag(float a, float b, float c) {
    return (float)Math.sqrt(a*a + b*b + c*c);
  }
  private final float mag(float abc[]) {
    return (float)Math.sqrt(abc[0]*abc[0] + abc[1]*abc[1] + abc[2]*abc[2]);
  }
  private final float min(float a, float b) {
    return (a < b) ? a : b;
  }
  private final float max(float a, float b) {
    return (a > b) ? a : b;
  }
  private final float pow(float a, float b) {
    return (float)Math.pow(a, b);
  }
  private final float abs(float a) {
    return (a < 0) ? -a : a;
  }
  private final float cos(float angle) {
    return (float)Math.cos(angle);
  }
  private final float tan(float angle) {
    return (float)Math.tan(angle);
  }
  private float dot(float ax, float ay, float az,
                    float bx, float by, float bz) {
    return ax * bx + ay * by + az * bz;
  }
}

  protected final void add_triangle_with_clip(int a, int b, int c) {
    boolean aClipped = false;
    boolean bClipped = false;
    int     clippedCount = 0;
    cameraNear = -8;
    if (vertices[a][VZ] > cameraNear) {
      aClipped = true;
      clippedCount++;
    }
    if (vertices[b][VZ] > cameraNear) {
      bClipped = true;
      clippedCount++;
    }
    if (vertices[c][VZ] > cameraNear) {
      clippedCount++;
    }
    if (clippedCount == 0) {
      add_triangle_no_clip(a, b, c);
      return;
    }
    else if (clippedCount == 3) {
      return;
    }
    else if (clippedCount == 2) {
      int ca, cb, cc, cd, ce;
      if (!aClipped) {
        ca = a;
        cb = b;
        cc = c;
      }
      else if (!bClipped) {
        ca = b;
        cb = a;
        cc = c;
      }
      else { 
        ca = c;
        cb = b;
        cc = a;
      }
      cd = interpolate_clip_vertex(ca, cb);
      ce = interpolate_clip_vertex(ca, cc);
      add_triangle_no_clip(ca, cd, ce);
      return;
    }
    else { 
      int ca, cb, cc, cd, ce;
      if (aClipped) {
        ca = c;
        cb = b;
        cc = a;
      }
      else if (bClipped) {
        ca = a;
        cb = c;
        cc = b;
      }
      else { 
        ca = a;
        cb = b;
        cc = c;
      }
      cd = interpolate_clip_vertex(ca, cc);
      ce = interpolate_clip_vertex(cb, cc);
      add_triangle_no_clip(ca, cd, cb);
      add_triangle_no_clip(cb, cd, ce);
      return;
    }
  }
  private final int interpolate_clip_vertex(int a, int b) {
    float[] va;
    float[] vb;
    if (vertices[a][VZ] < vertices[b][VZ]) {
      va = vertices[b];
      vb = vertices[a];
    }
    else {
      va = vertices[a];
      vb = vertices[b];
    }
    float az = va[VZ];
    float bz = vb[VZ];
    float dz = az - bz;
    if (dz == 0) {
      return a;
    }
    float pa = (cameraNear - bz) / dz;
    float pb = 1 - pa;
    vertex(pa * va[MX] + pb * vb[MX],
           pa * va[MY] + pb * vb[MY],
           pa * va[MZ] + pb * vb[MZ]);
    int irv = vertexCount - 1;
    vertex_end_including_clip_verts++;
    float[] rv = vertices[irv];
    rv[X] = pa * va[X] + pb * vb[X];
    rv[Y] = pa * va[Y] + pb * vb[Y];
    rv[Z] = pa * va[Z] + pb * vb[Z];
    rv[VX] = pa * va[VX] + pb * vb[VX];
    rv[VY] = pa * va[VY] + pb * vb[VY];
    rv[VZ] = pa * va[VZ] + pb * vb[VZ];
    rv[VW] = pa * va[VW] + pb * vb[VW];
    rv[R] = pa * va[R] + pb * vb[R];
    rv[G] = pa * va[G] + pb * vb[G];
    rv[B] = pa * va[B] + pb * vb[B];
    rv[A] = pa * va[A] + pb * vb[A];
    rv[U] = pa * va[U] + pb * vb[U];
    rv[V] = pa * va[V] + pb * vb[V];
    rv[SR] = pa * va[SR] + pb * vb[SR];
    rv[SG] = pa * va[SG] + pb * vb[SG];
    rv[SB] = pa * va[SB] + pb * vb[SB];
    rv[SA] = pa * va[SA] + pb * vb[SA];
    rv[NX] = pa * va[NX] + pb * vb[NX];
    rv[NY] = pa * va[NY] + pb * vb[NY];
    rv[NZ] = pa * va[NZ] + pb * vb[NZ];
    rv[SW] = pa * va[SW] + pb * vb[SW];
    rv[AR] = pa * va[AR] + pb * vb[AR];
    rv[AG] = pa * va[AG] + pb * vb[AG];
    rv[AB] = pa * va[AB] + pb * vb[AB];
    rv[SPR] = pa * va[SPR] + pb * vb[SPR];
    rv[SPG] = pa * va[SPG] + pb * vb[SPG];
    rv[SPB] = pa * va[SPB] + pb * vb[SPB];
    rv[SPA] = pa * va[SPA] + pb * vb[SPA];
    rv[ER] = pa * va[ER] + pb * vb[ER];
    rv[EG] = pa * va[EG] + pb * vb[EG];
    rv[EB] = pa * va[EB] + pb * vb[EB];
    rv[SHINE] = pa * va[SHINE] + pb * vb[SHINE];
    rv[BEEN_LIT] = 0;
    return irv;
  }
  protected final void add_triangle_no_clip(int a, int b, int c) {
    if (triangleCount == triangles.length) {
      int temp[][] = new int[triangleCount<<1][TRIANGLE_FIELD_COUNT];
      System.arraycopy(triangles, 0, temp, 0, triangleCount);
      triangles = temp;
      float ftemp[][][] = new float[triangleCount<<1][3][TRIANGLE_COLOR_COUNT];
      System.arraycopy(triangleColors, 0, ftemp, 0, triangleCount);
      triangleColors = ftemp;
    }
    triangles[triangleCount][VERTEX1] = a;
    triangles[triangleCount][VERTEX2] = b;
    triangles[triangleCount][VERTEX3] = c;
    if (textureImage == null) {
      triangles[triangleCount][TEXTURE_INDEX] = -1;
    } else {
      triangles[triangleCount][TEXTURE_INDEX] = texture_index;
    }
    triangles[triangleCount][INDEX] = shape_index;
    triangleCount++;
  }
  protected void depth_sort_triangles() {
    depth_sort_triangles_internal(0, triangleCount-1);
  }
  protected void depth_sort_triangles_internal(int i, int j) {
    int pivotIndex = (i+j)/2;
    depth_sort_triangles_swap(pivotIndex, j);
    int k = depth_sort_triangles_partition(i-1, j);
    depth_sort_triangles_swap(k, j);
    if ((k-i) > 1) depth_sort_triangles_internal(i, k-1);
    if ((j-k) > 1) depth_sort_triangles_internal(k+1, j);
  }
  protected int depth_sort_triangles_partition(int left, int right) {
    int pivot = right;
    do {
      while (depth_sort_triangles_compare(++left, pivot) < 0) { }
      while ((right != 0) &&
             (depth_sort_triangles_compare(--right, pivot) > 0)) { }
      depth_sort_triangles_swap(left, right);
    } while (left < right);
    depth_sort_triangles_swap(left, right);
    return left;
  }
  protected void depth_sort_triangles_swap(int a, int b) {
    int tempi[] = triangles[a];
    triangles[a] = triangles[b];
    triangles[b] = tempi;
    float tempf[][] = triangleColors[a];
    triangleColors[a] = triangleColors[b];
    triangleColors[b] = tempf;
  }
  protected float depth_sort_triangles_compare(int a, int b) {
    if (Float.isNaN(vertices[triangles[a][VERTEX1]][Z]) ||
        Float.isNaN(vertices[triangles[a][VERTEX2]][Z]) ||
        Float.isNaN(vertices[triangles[a][VERTEX3]][Z]) ||
        Float.isNaN(vertices[triangles[b][VERTEX1]][Z]) ||
        Float.isNaN(vertices[triangles[b][VERTEX2]][Z]) ||
        Float.isNaN(vertices[triangles[b][VERTEX3]][Z])) {
      throw new RuntimeException("nan triangle");
    }
    return
      (vertices[triangles[b][VERTEX1]][Z] +
       vertices[triangles[b][VERTEX2]][Z] +
       vertices[triangles[b][VERTEX3]][Z]) -
      (vertices[triangles[a][VERTEX1]][Z] +
       vertices[triangles[a][VERTEX2]][Z] +
       vertices[triangles[a][VERTEX3]][Z]);
  }
  protected void render_triangles() {
    if (raw != null) {
      raw.colorMode(RGB, 1);
      raw.noStroke();
      raw.beginShape(TRIANGLES);
    }
    for (int i = 0; i < triangleCount; i ++) {
      float a[] = vertices[triangles[i][VERTEX1]];
      float b[] = vertices[triangles[i][VERTEX2]];
      float c[] = vertices[triangles[i][VERTEX3]];
      int tex = triangles[i][TEXTURE_INDEX];
      int index = triangles[i][INDEX];
      triangle.reset();
      float ar = min(1, triangleColors[i][0][TRI_DIFFUSE_R] +
                     triangleColors[i][0][TRI_SPECULAR_R]);
      float ag = min(1, triangleColors[i][0][TRI_DIFFUSE_G] +
                     triangleColors[i][0][TRI_SPECULAR_G]);
      float ab = min(1, triangleColors[i][0][TRI_DIFFUSE_B] +
                     triangleColors[i][0][TRI_SPECULAR_B]);
      float br = min(1, triangleColors[i][1][TRI_DIFFUSE_R] +
                     triangleColors[i][1][TRI_SPECULAR_R]);
      float bg = min(1, triangleColors[i][1][TRI_DIFFUSE_G] +
                     triangleColors[i][1][TRI_SPECULAR_G]);
      float bb = min(1, triangleColors[i][1][TRI_DIFFUSE_B] +
                     triangleColors[i][1][TRI_SPECULAR_B]);
      float cr = min(1, triangleColors[i][2][TRI_DIFFUSE_R] +
                     triangleColors[i][2][TRI_SPECULAR_R]);
      float cg = min(1, triangleColors[i][2][TRI_DIFFUSE_G] +
                     triangleColors[i][2][TRI_SPECULAR_G]);
      float cb = min(1, triangleColors[i][2][TRI_DIFFUSE_B] +
                     triangleColors[i][2][TRI_SPECULAR_B]);
      if (tex > -1 && textures[tex] != null) {
        triangle.setTexture(textures[tex]);
        triangle.setUV(a[U], a[V], b[U], b[V], c[U], c[V]);
      }
      triangle.setIntensities(ar, ag, ab, a[A],
                              br, bg, bb, b[A],
                              cr, cg, cb, c[A]);
      triangle.setVertices(a[X], a[Y], a[Z],
                           b[X], b[Y], b[Z],
                           c[X], c[Y], c[Z]);
      triangle.setIndex(index);
      triangle.render();
      if (raw != null) {
        if (raw instanceof PGraphics3D) {
          if ((a[VW] != 0) && (b[VW] != 0) && (c[VW] != 0)) {
            raw.fill(ar, ag, ab, a[A]);
            raw.vertex(a[VX] / a[VW], a[VY] / a[VW], a[VZ] / a[VW]);
            raw.fill(br, bg, bb, b[A]);
            raw.vertex(b[VX] / b[VW], b[VY] / b[VW], b[VZ] / b[VW]);
            raw.fill(cr, cg, cb, c[A]);
            raw.vertex(c[VX] / c[VW], c[VY] / c[VW], c[VZ] / c[VW]);
          }
        } else {
          raw.fill(ar, ag, ab, a[A]);
          raw.vertex(a[X], a[Y]);
          raw.fill(br, bg, bb, b[A]);
          raw.vertex(b[X], b[Y]);
          raw.fill(cr, cg, cb, c[A]);
          raw.vertex(c[X], c[Y]);
        }
      }
    }
    if (raw != null) {
      raw.endShape();
    }
  }
  protected void depth_sort_lines() {
  }
  protected void render_lines() {
    if (raw != null) {
      raw.colorMode(RGB, 1);
      raw.noFill();
      raw.beginShape(LINES);
    }
    for (int i = 0; i < lineCount; i ++) {
      float a[] = vertices[lines[i][VERTEX1]];
      float b[] = vertices[lines[i][VERTEX2]];
      int index = lines[i][INDEX];
      line.reset();
      line.setIntensities(a[SR], a[SG], a[SB], a[SA],
                          b[SR], b[SG], b[SB], b[SA]);
      line.setVertices(a[X], a[Y], a[Z],
                       b[X], b[Y], b[Z]);
      if (raw != null) {
        if (raw instanceof PGraphics3D) {
          if ((a[VW] != 0) && (b[VW] != 0)) {
            raw.stroke(a[SR], a[SG], a[SB], a[SA]);
            raw.vertex(a[VX] / a[VW], a[VY] / a[VW], a[VZ] / a[VW]);
            raw.stroke(b[SR], b[SG], b[SB], b[SA]);
            raw.vertex(b[VX] / b[VW], b[VY] / b[VW], b[VZ] / b[VW]);
          }
        } else {
          raw.stroke(a[SR], a[SG], a[SB], a[SA]);
          raw.vertex(a[X], a[Y]);
          raw.stroke(b[SR], b[SG], b[SB], b[SA]);
          raw.vertex(b[X], b[Y]);
        }
      }
      line.setIndex(index);
      line.draw();
    }
    if (raw != null) {
      raw.endShape();
    }
  }
  private void triangulate_polygon() {
    int d1 = MX;
    int d2 = MY;
    float area = 0;
    for (int p = vertex_end - 1, q = vertex_start; q < vertex_end; p = q++) {
      area += (vertices[q][d1] * vertices[p][d2] -
               vertices[p][d1] * vertices[q][d2]);
    }
    if (area == 0) {
      boolean foundValidX = false;
      boolean foundValidY = false;
      for (int i = vertex_start; i < vertex_end; i++) {
        if (vertices[i][MX] != 0) foundValidX = true;
        if (vertices[i][MY] != 0) foundValidY = true;
      }
      if (foundValidX && !foundValidY) {
        d2 = MZ;
      } else if (!foundValidX && foundValidY) {
        d1 = MY;
        d2 = MZ;
      } else {
        return;
      }
      for (int p = vertex_end - 1, q = vertex_start; q < vertex_end; p = q++) {
        area += (vertices[q][d1] * vertices[p][d2] -
                 vertices[p][d1] * vertices[q][d2]);
      }
    }
    float vfirst[] = vertices[vertex_start];
    float vlast[] = vertices[vertex_end-1];
    if ((abs(vfirst[MX] - vlast[MX]) < EPSILON) &&
        (abs(vfirst[MY] - vlast[MY]) < EPSILON) &&
        (abs(vfirst[MZ] - vlast[MZ]) < EPSILON)) {
      vertex_end--;
    }
    int j = 0;
    if (area > 0) {
      for (int i = vertex_start; i < vertex_end; i++) {
        j = i - vertex_start;
        vertex_order[j] = i;
      }
    } else {
      for (int i = vertex_start; i < vertex_end; i++) {
        j = i - vertex_start;
        vertex_order[j] = (vertex_end - 1) - j;
      }
    }
    int vc = vertex_end - vertex_start;
    int count = 2*vc;  
    for (int m = 0, v = vc - 1; vc > 2; ) {
      boolean snip = true;
      if (0 >= (count--)) {
        break; 
      }
      int u = v ; if (vc <= u) u = 0;    
      v = u + 1; if (vc <= v) v = 0;     
      int w = v + 1; if (vc <= w) w = 0; 
      float Ax = -vertices[vertex_order[u]][d1];
      float Ay =  vertices[vertex_order[u]][d2];
      float Bx = -vertices[vertex_order[v]][d1];
      float By =  vertices[vertex_order[v]][d2];
      float Cx = -vertices[vertex_order[w]][d1];
      float Cy =  vertices[vertex_order[w]][d2];
      if (EPSILON > (((Bx-Ax) * (Cy-Ay)) - ((By-Ay) * (Cx-Ax)))) {
        continue;
      }
      for (int p = 0; p < vc; p++) {
        if( (p == u) || (p == v) || (p == w) ) {
          continue;
        }
        float Px = -vertices[vertex_order[p]][d1];
        float Py =  vertices[vertex_order[p]][d2];
        float ax  = Cx - Bx;  float ay  = Cy - By;
        float bx  = Ax - Cx;  float by  = Ay - Cy;
        float cx  = Bx - Ax;  float cy  = By - Ay;
        float apx = Px - Ax;  float apy = Py - Ay;
        float bpx = Px - Bx;  float bpy = Py - By;
        float cpx = Px - Cx;  float cpy = Py - Cy;
        float aCROSSbp = ax * bpy - ay * bpx;
        float cCROSSap = cx * apy - cy * apx;
        float bCROSScp = bx * cpy - by * cpx;
        if ((aCROSSbp >= 0.0f) && (bCROSScp >= 0.0f) && (cCROSSap >= 0.0f)) {
          snip = false;
        }
      }
      if (snip) {
        add_triangle(vertex_order[u], vertex_order[v], vertex_order[w]);
        m++;
        for (int s = v, t = v + 1; t < vc; s++, t++) {
          vertex_order[s] = vertex_order[t];
        }
        vc--;
        count = 2 * vc;
      }
    }
  }
  private void toWorldNormal(float nx, float ny, float nz, float[] out) {
    out[0] =
      modelviewInv.m00*nx + modelviewInv.m10*ny +
      modelviewInv.m20*nz + modelviewInv.m30;
    out[1] =
      modelviewInv.m01*nx + modelviewInv.m11*ny +
      modelviewInv.m21*nz + modelviewInv.m31;
    out[2] =
      modelviewInv.m02*nx + modelviewInv.m12*ny +
      modelviewInv.m22*nz + modelviewInv.m32;
    out[3] =
      modelviewInv.m03*nx + modelviewInv.m13*ny +
      modelviewInv.m23*nz + modelviewInv.m33;
    if (out[3] != 0 && out[3] != ONE) {
      out[0] /= out[3]; out[1] /= out[3]; out[2] /= out[3];
    }
    out[3] = 1;
    float nlen = mag(out[0], out[1], out[2]);  
    if (nlen != 0 && nlen != ONE) {
      out[0] /= nlen; out[1] /= nlen; out[2] /= nlen;
    }
  }
  private void calc_lighting_contribution(int vIndex,
                                          float[] contribution) {
    calc_lighting_contribution(vIndex, contribution, false);
  }
  private void calc_lighting_contribution(int vIndex,
                                          float[] contribution,
                                          boolean normalIsWorld) {
    float[] v = vertices[vIndex];
    float sr = v[SPR];
    float sg = v[SPG];
    float sb = v[SPB];
    float wx = v[VX];
    float wy = v[VY];
    float wz = v[VZ];
    float shine = v[SHINE];
    float nx;
    float ny;
    float nz;
    if (!normalIsWorld) {
      toWorldNormal(v[NX], v[NY], v[NZ], worldNormal);
      nx = worldNormal[X];
      ny = worldNormal[Y];
      nz = worldNormal[Z];
    }
    else {
      nx = v[NX];
      ny = v[NY];
      nz = v[NZ];
    }
    float dir = dot(nx, ny, nz, -wx, -wy, -wz);
    if (dir < 0) {
      nx = -nx;
      ny = -ny;
      nz = -nz;
    }
    contribution[LIGHT_AMBIENT_R] = 0;
    contribution[LIGHT_AMBIENT_G] = 0;
    contribution[LIGHT_AMBIENT_B] = 0;
    contribution[LIGHT_DIFFUSE_R] = 0;
    contribution[LIGHT_DIFFUSE_G] = 0;
    contribution[LIGHT_DIFFUSE_B] = 0;
    contribution[LIGHT_SPECULAR_R] = 0;
    contribution[LIGHT_SPECULAR_G] = 0;
    contribution[LIGHT_SPECULAR_B] = 0;
    for (int i = 0; i < lightCount; i++) {
      float denom = lightFalloffConstant[i];
      float spotTerm = 1;
      if (lightType[i] == AMBIENT) {
        if (lightFalloffQuadratic[i] != 0 || lightFalloffLinear[i] != 0) {
          float distSq = mag(lightPosition[i][0] - wx,
                             lightPosition[i][1] - wy,
                             lightPosition[i][2] - wz);
          denom +=
            lightFalloffQuadratic[i] * distSq +
            lightFalloffLinear[i] * (float) sqrt(distSq);
        }
        if (denom == 0) denom = 1;
        contribution[LIGHT_AMBIENT_R] += lightDiffuse[i][0] / denom;
        contribution[LIGHT_AMBIENT_G] += lightDiffuse[i][1] / denom;
        contribution[LIGHT_AMBIENT_B] += lightDiffuse[i][2] / denom;
      } else {
        float lix, liy, liz;
        float lightDir_dot_li = 0;
        float n_dot_li = 0;
        if (lightType[i] == DIRECTIONAL) {
          lix = -lightNormal[i][0];
          liy = -lightNormal[i][1];
          liz = -lightNormal[i][2];
          denom = 1;
          n_dot_li = (nx * lix + ny * liy + nz * liz);
          if (n_dot_li <= 0) {
            continue;
          }
        } else { 
          lix = lightPosition[i][0] - wx;
          liy = lightPosition[i][1] - wy;
          liz = lightPosition[i][2] - wz;
          float distSq = mag(lix, liy, liz);
          if (distSq != 0) {
            lix /= distSq;
            liy /= distSq;
            liz /= distSq;
          }
          n_dot_li = (nx * lix + ny * liy + nz * liz);
          if (n_dot_li <= 0) {
            continue;
          }
          if (lightType[i] == SPOT) { 
            lightDir_dot_li =
              -(lightNormal[i][0] * lix +
                lightNormal[i][1] * liy +
                lightNormal[i][2] * liz);
            if (lightDir_dot_li <= lightSpotAngleCos[i]) {
              continue;
            }
            spotTerm = pow(lightDir_dot_li, lightSpotConcentration[i]);
          }
          if (lightFalloffQuadratic[i] != 0 || lightFalloffLinear[i] != 0) {
            denom +=
              lightFalloffQuadratic[i] * distSq +
              lightFalloffLinear[i] * (float) sqrt(distSq);
          }
        }
        if (denom == 0)
          denom = 1;
        float mul = n_dot_li * spotTerm / denom;
        contribution[LIGHT_DIFFUSE_R] += lightDiffuse[i][0] * mul;
        contribution[LIGHT_DIFFUSE_G] += lightDiffuse[i][1] * mul;
        contribution[LIGHT_DIFFUSE_B] += lightDiffuse[i][2] * mul;
        if ((sr > 0 || sg > 0 || sb > 0) &&
            (lightSpecular[i][0] > 0 ||
             lightSpecular[i][1] > 0 ||
             lightSpecular[i][2] > 0)) {
          float vmag = mag(wx, wy, wz);
          if (vmag != 0) {
            wx /= vmag;
            wy /= vmag;
            wz /= vmag;
          }
          float sx = lix - wx;
          float sy = liy - wy;
          float sz = liz - wz;
          vmag = mag(sx, sy, sz);
          if (vmag != 0) {
            sx /= vmag;
            sy /= vmag;
            sz /= vmag;
          }
          float s_dot_n = (sx * nx + sy * ny + sz * nz);
          if (s_dot_n > 0) {
            s_dot_n = pow(s_dot_n, shine);
            mul = s_dot_n * spotTerm / denom;
            contribution[LIGHT_SPECULAR_R] += lightSpecular[i][0] * mul;
            contribution[LIGHT_SPECULAR_G] += lightSpecular[i][1] * mul;
            contribution[LIGHT_SPECULAR_B] += lightSpecular[i][2] * mul;
          }
        }
      }
    }
    return;
  }
  private void apply_lighting_contribution(int vIndex, float[] contribution) {
    float[] v = vertices[vIndex];
    v[R] = min(1, v[ER] + v[AR] * contribution[LIGHT_AMBIENT_R] +
               v[DR] * contribution[LIGHT_DIFFUSE_R]);
    v[G] = min(1, v[EG] + v[AG] * contribution[LIGHT_AMBIENT_G] +
               v[DG] * contribution[LIGHT_DIFFUSE_G]);
    v[B] = min(1, v[EB] + v[AB] * contribution[LIGHT_AMBIENT_B] +
               v[DB] * contribution[LIGHT_DIFFUSE_B]);
    v[A] = min(1, v[DA]);
    v[SPR] = min(1, v[SPR] * contribution[LIGHT_SPECULAR_R]);
    v[SPG] = min(1, v[SPG] * contribution[LIGHT_SPECULAR_G]);
    v[SPB] = min(1, v[SPB] * contribution[LIGHT_SPECULAR_B]);
    v[SPA] = min(1, v[SPA]);
    v[BEEN_LIT] = 1;
  }
  private void light_vertex_always(int vIndex, float[] contribution) {
    calc_lighting_contribution(vIndex, contribution);
    apply_lighting_contribution(vIndex, contribution);
  }
  private void light_vertex_if_not_already_lit(int vIndex,
                                               float[] contribution) {
    if (vertices[vIndex][BEEN_LIT] == 0) {
      light_vertex_always(vIndex, contribution);
    }
  }
  private void copy_prelit_vertex_color_to_triangle(int triIndex, int vIndex,
                                                    int colorIndex) {
    float[] triColor = triangleColors[triIndex][colorIndex];
    float[] v = vertices[vIndex];
    triColor[TRI_DIFFUSE_R] = v[R];
    triColor[TRI_DIFFUSE_G] = v[G];
    triColor[TRI_DIFFUSE_B] = v[B];
    triColor[TRI_DIFFUSE_A] = v[A];
    triColor[TRI_SPECULAR_R] = v[SPR];
    triColor[TRI_SPECULAR_G] = v[SPG];
    triColor[TRI_SPECULAR_B] = v[SPB];
    triColor[TRI_SPECULAR_A] = v[SPA];
  }
  private void copy_vertex_color_to_triangle(int triIndex,
                                             int vIndex, int colorIndex,
                                             float[] lightContribution) {
    float[] triColor = triangleColors[triIndex][colorIndex];
    float[] v = vertices[vIndex];
    triColor[TRI_DIFFUSE_R] =
      min(1, v[ER] + v[AR] * lightContribution[LIGHT_AMBIENT_R] +
          v[DR] * lightContribution[LIGHT_DIFFUSE_R]);
    triColor[TRI_DIFFUSE_G] =
      min(1, v[EG] + v[AG] * lightContribution[LIGHT_AMBIENT_G] +
          v[DG] * lightContribution[LIGHT_DIFFUSE_G]);
    triColor[TRI_DIFFUSE_B] =
      min(1, v[EB] + v[AB] * lightContribution[LIGHT_AMBIENT_B] +
          v[DB] * lightContribution[LIGHT_DIFFUSE_B]);
    triColor[TRI_DIFFUSE_A] = min(1, v[DA]);
    triColor[TRI_SPECULAR_R] =
      min(1, v[SPR] * lightContribution[LIGHT_SPECULAR_R]);
    triColor[TRI_SPECULAR_G] =
      min(1, v[SPG] * lightContribution[LIGHT_SPECULAR_G]);
    triColor[TRI_SPECULAR_B] =
      min(1, v[SPB] * lightContribution[LIGHT_SPECULAR_B]);
    triColor[TRI_SPECULAR_A] = min(1, v[SPA]);
  }
  private void light_triangle(int triIndex, float[] lightContribution) {
    int vIndex = triangles[triIndex][VERTEX1];
    copy_vertex_color_to_triangle(triIndex, vIndex, 0, lightContribution);
    vIndex = triangles[triIndex][VERTEX2];
    copy_vertex_color_to_triangle(triIndex, vIndex, 1, lightContribution);
    vIndex = triangles[triIndex][VERTEX3];
    copy_vertex_color_to_triangle(triIndex, vIndex, 2, lightContribution);
  }
  private void crossProduct(float[] u, float[] v, float[] out) {
    out[0] = u[1]*v[2] - u[2]*v[1];
    out[1] = u[2]*v[0] - u[0]*v[2];
    out[2] = u[0]*v[1] - u[1]*v[0];
  }
  private void light_triangle(int triIndex) {
    int vIndex;
    if (normalMode == MANUAL_VERTEX_NORMAL) {
      vIndex = triangles[triIndex][VERTEX1];
      light_vertex_if_not_already_lit(vIndex, tempLightingContribution);
      copy_prelit_vertex_color_to_triangle(triIndex, vIndex, 0);
      vIndex = triangles[triIndex][VERTEX2];
      light_vertex_if_not_already_lit(vIndex, tempLightingContribution);
      copy_prelit_vertex_color_to_triangle(triIndex, vIndex, 1);
      vIndex = triangles[triIndex][VERTEX3];
      light_vertex_if_not_already_lit(vIndex, tempLightingContribution);
      copy_prelit_vertex_color_to_triangle(triIndex, vIndex, 2);
    }
    else if (!lightingDependsOnVertexPosition) {
      vIndex = triangles[triIndex][VERTEX1];
      int vIndex2 = triangles[triIndex][VERTEX2];
      int vIndex3 = triangles[triIndex][VERTEX3];
      dv1[0] = vertices[vIndex2][VX] - vertices[vIndex][VX];
      dv1[1] = vertices[vIndex2][VY] - vertices[vIndex][VY];
      dv1[2] = vertices[vIndex2][VZ] - vertices[vIndex][VZ];
      dv2[0] = vertices[vIndex3][VX] - vertices[vIndex][VX];
      dv2[1] = vertices[vIndex3][VY] - vertices[vIndex][VY];
      dv2[2] = vertices[vIndex3][VZ] - vertices[vIndex][VZ];
      crossProduct(dv1, dv2, norm);
      float nMag = mag(norm[X], norm[Y], norm[Z]);
      if (nMag != 0 && nMag != ONE) {
        norm[X] /= nMag; norm[Y] /= nMag; norm[Z] /= nMag;
      }
      vertices[vIndex][NX] = norm[X];
      vertices[vIndex][NY] = norm[Y];
      vertices[vIndex][NZ] = norm[Z];
      calc_lighting_contribution(vIndex, tempLightingContribution, true);
      copy_vertex_color_to_triangle(triIndex, vIndex, 0,
                                    tempLightingContribution);
      copy_vertex_color_to_triangle(triIndex, vIndex2, 1,
                                    tempLightingContribution);
      copy_vertex_color_to_triangle(triIndex, vIndex3, 2,
                                    tempLightingContribution);
    }
    else {
      if (normalMode == MANUAL_SHAPE_NORMAL) {
        vIndex = triangles[triIndex][VERTEX1];
        vertices[vIndex][NX] = vertices[vertex_start][NX];
        vertices[vIndex][NY] = vertices[vertex_start][NY];
        vertices[vIndex][NZ] = vertices[vertex_start][NZ];
        calc_lighting_contribution(vIndex, tempLightingContribution);
        copy_vertex_color_to_triangle(triIndex, vIndex, 0,
                                      tempLightingContribution);
        vIndex = triangles[triIndex][VERTEX2];
        vertices[vIndex][NX] = vertices[vertex_start][NX];
        vertices[vIndex][NY] = vertices[vertex_start][NY];
        vertices[vIndex][NZ] = vertices[vertex_start][NZ];
        calc_lighting_contribution(vIndex, tempLightingContribution);
        copy_vertex_color_to_triangle(triIndex, vIndex, 1,
                                      tempLightingContribution);
        vIndex = triangles[triIndex][VERTEX3];
        vertices[vIndex][NX] = vertices[vertex_start][NX];
        vertices[vIndex][NY] = vertices[vertex_start][NY];
        vertices[vIndex][NZ] = vertices[vertex_start][NZ];
        calc_lighting_contribution(vIndex, tempLightingContribution);
        copy_vertex_color_to_triangle(triIndex, vIndex, 2,
                                      tempLightingContribution);
      }
      else {
        vIndex = triangles[triIndex][VERTEX1];
        int vIndex2 = triangles[triIndex][VERTEX2];
        int vIndex3 = triangles[triIndex][VERTEX3];
        dv1[0] = vertices[vIndex2][VX] - vertices[vIndex][VX];
        dv1[1] = vertices[vIndex2][VY] - vertices[vIndex][VY];
        dv1[2] = vertices[vIndex2][VZ] - vertices[vIndex][VZ];
        dv2[0] = vertices[vIndex3][VX] - vertices[vIndex][VX];
        dv2[1] = vertices[vIndex3][VY] - vertices[vIndex][VY];
        dv2[2] = vertices[vIndex3][VZ] - vertices[vIndex][VZ];
        crossProduct(dv1, dv2, norm);
        float nMag = mag(norm[X], norm[Y], norm[Z]);
        if (nMag != 0 && nMag != ONE) {
          norm[X] /= nMag; norm[Y] /= nMag; norm[Z] /= nMag;
        }
        vertices[vIndex][NX] = norm[X];
        vertices[vIndex][NY] = norm[Y];
        vertices[vIndex][NZ] = norm[Z];
        calc_lighting_contribution(vIndex, tempLightingContribution, true);
        copy_vertex_color_to_triangle(triIndex, vIndex, 0,
                                      tempLightingContribution);
        vertices[vIndex2][NX] = norm[X];
        vertices[vIndex2][NY] = norm[Y];
        vertices[vIndex2][NZ] = norm[Z];
        calc_lighting_contribution(vIndex2, tempLightingContribution, true);
        copy_vertex_color_to_triangle(triIndex, vIndex2, 1,
                                      tempLightingContribution);
        vertices[vIndex3][NX] = norm[X];
        vertices[vIndex3][NY] = norm[Y];
        vertices[vIndex3][NZ] = norm[Z];
        calc_lighting_contribution(vIndex3, tempLightingContribution, true);
        copy_vertex_color_to_triangle(triIndex, vIndex3, 2,
                                      tempLightingContribution);
      }
    }
  }
  protected void handle_lighting() {
    if (!lightingDependsOnVertexPosition && normalMode == MANUAL_SHAPE_NORMAL) {
      calc_lighting_contribution(vertex_start, tempLightingContribution);
      for (int tri = 0; tri < triangleCount; tri++) {
        light_triangle(tri, tempLightingContribution);
      }
    }
    else {
      for (int tri = 0; tri < triangleCount; tri++) {
        light_triangle(tri);
      }
    }
  }
  protected void handle_no_lighting() {
    int vIndex;
    for (int tri = 0; tri < triangleCount; tri++) {
      vIndex = triangles[tri][VERTEX1];
      copy_prelit_vertex_color_to_triangle(tri, vIndex, 0);
      vIndex = triangles[tri][VERTEX2];
      copy_prelit_vertex_color_to_triangle(tri, vIndex, 1);
      vIndex = triangles[tri][VERTEX3];
      copy_prelit_vertex_color_to_triangle(tri, vIndex, 2);
    }
  }
  public void point(float x, float y) {
    point(x, y, 0);
  }
  public void point(float x, float y, float z) {
    beginShape(LINES);
    vertex(x, y, z);
    vertex(x + EPSILON, y + EPSILON, z);
    endShape();
  }
  public void triangle(float x1, float y1, float x2, float y2,
                       float x3, float y3) {
    beginShape(TRIANGLES);
    normal(0, 0, 1);
    vertex(x1, y1);
    vertex(x2, y2);
    vertex(x3, y3);
    endShape();
  }
  public void quad(float x1, float y1, float x2, float y2,
                   float x3, float y3, float x4, float y4) {
    beginShape(QUADS);
    normal(0, 0, 1);
    vertex(x1, y1);
    vertex(x2, y2);
    vertex(x3, y3);
    vertex(x4, y4);
    endShape();
  }
  public void box(float size) {
    box(size, size, size);
  }
  public void box(float w, float h, float d) {
    float x1 = -w/2f; float x2 = w/2f;
    float y1 = -h/2f; float y2 = h/2f;
    float z1 = -d/2f; float z2 = d/2f;
    if (triangle != null) {  
      triangle.setCulling(true);
    }
    beginShape(QUADS);
    normal(0, 0, 1);
    vertex(x1, y1, z1);
    vertex(x2, y1, z1);
    vertex(x2, y2, z1);
    vertex(x1, y2, z1);
    normal(1, 0, 0);
    vertex(x2, y1, z1);
    vertex(x2, y1, z2);
    vertex(x2, y2, z2);
    vertex(x2, y2, z1);
    normal(0, 0, -1);
    vertex(x2, y1, z2);
    vertex(x1, y1, z2);
    vertex(x1, y2, z2);
    vertex(x2, y2, z2);
    normal(-1, 0, 0);
    vertex(x1, y1, z2);
    vertex(x1, y1, z1);
    vertex(x1, y2, z1);
    vertex(x1, y2, z2);
    normal(0, 1, 0);
    vertex(x1, y1, z2);
    vertex(x2, y1, z2);
    vertex(x2, y1, z1);
    vertex(x1, y1, z1);
    normal(0, -1, 0);
    vertex(x1, y2, z1);
    vertex(x2, y2, z1);
    vertex(x2, y2, z2);
    vertex(x1, y2, z2);
    endShape();
    if (triangle != null) {  
      triangle.setCulling(false);
    }
  }
  public void sphereDetail(int res) {
    if (res < 3) res = 3; 
    if (res == sphereDetail) return;
    float delta = (float)SINCOS_LENGTH/res;
    float[] cx = new float[res];
    float[] cz = new float[res];
    for (int i = 0; i < res; i++) {
      cx[i] = cosLUT[(int) (i*delta) % SINCOS_LENGTH];
      cz[i] = sinLUT[(int) (i*delta) % SINCOS_LENGTH];
    }
    int vertCount = res * (res-1) + 2;
    int currVert = 0;
    sphereX = new float[vertCount];
    sphereY = new float[vertCount];
    sphereZ = new float[vertCount];
    float angle_step = (SINCOS_LENGTH*0.5f)/res;
    float angle = angle_step;
    for (int i = 1; i < res; i++) {
      float curradius = sinLUT[(int) angle % SINCOS_LENGTH];
      float currY = -cosLUT[(int) angle % SINCOS_LENGTH];
      for (int j = 0; j < res; j++) {
        sphereX[currVert] = cx[j] * curradius;
        sphereY[currVert] = currY;
        sphereZ[currVert++] = cz[j] * curradius;
      }
      angle += angle_step;
    }
    sphereDetail = res;
  }
  public void sphere(float r) {
    float x = 0;  
    float y = 0;
    float z = 0;
    if (sphereDetail == 0) {
      sphereDetail(30);
    }
    int v1,v11,v2;
    pushMatrix();
    if (x!=0f && y!=0f && z!=0f) translate(x,y,z);
    scale(r);
    if (triangle != null) {  
      triangle.setCulling(true);
    }
    beginShape(TRIANGLE_STRIP);
    for (int i = 0; i < sphereDetail; i++) {
      normal(0, -1, 0);
      vertex(0, -1, 0);
      normal(sphereX[i], sphereY[i], sphereZ[i]);
      vertex(sphereX[i], sphereY[i], sphereZ[i]);
    }
    vertex(0, -1, 0);
    normal(sphereX[0], sphereY[0], sphereZ[0]);
    vertex(sphereX[0], sphereY[0], sphereZ[0]);
    endShape();
    int voff = 0;
    for(int i = 2; i < sphereDetail; i++) {
      v1=v11=voff;
      voff += sphereDetail;
      v2=voff;
      beginShape(TRIANGLE_STRIP);
      for (int j = 0; j < sphereDetail; j++) {
        normal(sphereX[v1], sphereY[v1], sphereZ[v1]);
        vertex(sphereX[v1], sphereY[v1], sphereZ[v1++]);
        normal(sphereX[v2], sphereY[v2], sphereZ[v2]);
        vertex(sphereX[v2], sphereY[v2], sphereZ[v2++]);
      }
      v1=v11;
      v2=voff;
      normal(sphereX[v1], sphereY[v1], sphereZ[v1]);
      vertex(sphereX[v1], sphereY[v1], sphereZ[v1]);
      normal(sphereX[v2], sphereY[v2], sphereZ[v2]);
      vertex(sphereX[v2], sphereY[v2], sphereZ[v2]);
      endShape();
    }
    beginShape(TRIANGLE_STRIP);
    for (int i = 0; i < sphereDetail; i++) {
      v2 = voff + i;
      normal(sphereX[v2], sphereY[v2], sphereZ[v2]);
      vertex(sphereX[v2], sphereY[v2], sphereZ[v2]);
      normal(0, 1, 0);
    vertex(0, 1, 0);
    }
    normal(sphereX[voff], sphereY[voff], sphereZ[voff]);
    vertex(sphereX[voff], sphereY[voff], sphereZ[voff]);
    normal(0, 1, 0);
    vertex(0, 1, 0);
    endShape();
    popMatrix();
    if (triangle != null) {  
      triangle.setCulling(false);
    }
  }
  public void translate(float tx, float ty) {
    translate(tx, ty, 0);
  }
  public void translate(float tx, float ty, float tz) {
    forwardTransform.translate(tx, ty, tz);
    reverseTransform.invTranslate(tx, ty, tz);
  }
  public void rotate(float angle) {
    rotateZ(angle);
  }
  public void rotateX(float angle) {
    forwardTransform.rotateX(angle);
    reverseTransform.invRotateX(angle);
  }
  public void rotateY(float angle) {
    forwardTransform.rotateY(angle);
    reverseTransform.invRotateY(angle);
  }
  public void rotateZ(float angle) {
    forwardTransform.rotateZ(angle);
    reverseTransform.invRotateZ(angle);
  }
  public void rotate(float angle, float v0, float v1, float v2) {
    forwardTransform.rotate(angle, v0, v1, v2);
    reverseTransform.invRotate(angle, v0, v1, v2);
  }
  public void scale(float s) {
    scale(s, s, s);
  }
  public void scale(float sx, float sy) {
    scale(sx, sy, 1);
  }
  public void scale(float x, float y, float z) {
    forwardTransform.scale(x, y, z);
    reverseTransform.invScale(x, y, z);
  }
  public void pushMatrix() {
    if (!modelview.push()) {
      throw new RuntimeException("Too many calls to pushMatrix()");
    }
    modelviewInv.push();
  }
  public void popMatrix() {
    if (!modelview.pop()) {
      throw new RuntimeException("Too many calls to popMatrix() " +
                                 "(and not enough to pushMatrix)");
    }
    modelviewInv.pop();
  }
  public void resetMatrix() {
    forwardTransform.reset();
    reverseTransform.reset();
  }
  public void applyMatrix(float n00, float n01, float n02,
                          float n10, float n11, float n12) {
    throw new RuntimeException("Use applyMatrix() with a 4x4 matrix " +
                               "when using OPENGL or P3D");
  }
  public void applyMatrix(float n00, float n01, float n02, float n03,
                          float n10, float n11, float n12, float n13,
                          float n20, float n21, float n22, float n23,
                          float n30, float n31, float n32, float n33) {
    forwardTransform.apply(n00, n01, n02, n03,
                           n10, n11, n12, n13,
                           n20, n21, n22, n23,
                           n30, n31, n32, n33);
    reverseTransform.invApply(n00, n01, n02, n03,
                              n10, n11, n12, n13,
                              n20, n21, n22, n23,
                              n30, n31, n32, n33);
  }
  public void loadMatrix() {
    m00 = modelview.m00;
    m01 = modelview.m01;
    m02 = modelview.m02;
    m03 = modelview.m03;
    m10 = modelview.m10;
    m11 = modelview.m11;
    m12 = modelview.m12;
    m13 = modelview.m13;
    m20 = modelview.m20;
    m21 = modelview.m21;
    m22 = modelview.m22;
    m23 = modelview.m23;
    m30 = modelview.m30;
    m31 = modelview.m31;
    m32 = modelview.m32;
    m33 = modelview.m33;
  }
  public void printMatrix() {
    modelview.print();
  }
  public void beginCamera() {
    if (manipulatingCamera) {
      throw new RuntimeException("beginCamera() cannot be called again " +
                                 "before endCamera()");
    } else {
      manipulatingCamera = true;
      forwardTransform = cameraInv;
      reverseTransform = camera;
    }
  }
  public void endCamera() {
    if (!manipulatingCamera) {
      throw new RuntimeException("Cannot call endCamera() " +
                                 "without first calling beginCamera()");
    }
    modelview.set(camera);
    modelviewInv.set(cameraInv);
    forwardTransform = modelview;
    reverseTransform = modelviewInv;
    manipulatingCamera = false;
  }
  public void camera() {
    camera(cameraX, cameraY, cameraZ,
           cameraX, cameraY, 0,
           0, 1, 0);
  }
  public void camera(float eyeX, float eyeY, float eyeZ,
                     float centerX, float centerY, float centerZ,
                     float upX, float upY, float upZ) {
    float z0 = eyeX - centerX;
    float z1 = eyeY - centerY;
    float z2 = eyeZ - centerZ;
    float mag = sqrt(z0*z0 + z1*z1 + z2*z2);
    if (mag != 0) {
      z0 /= mag;
      z1 /= mag;
      z2 /= mag;
    }
    float y0 = upX;
    float y1 = upY;
    float y2 = upZ;
    float x0 =  y1*z2 - y2*z1;
    float x1 = -y0*z2 + y2*z0;
    float x2 =  y0*z1 - y1*z0;
    y0 =  z1*x2 - z2*x1;
    y1 = -z0*x2 + z2*x0;
    y2 =  z0*x1 - z1*x0;
    mag = sqrt(x0*x0 + x1*x1 + x2*x2);
    if (mag != 0) {
      x0 /= mag;
      x1 /= mag;
      x2 /= mag;
    }
    mag = sqrt(y0*y0 + y1*y1 + y2*y2);
    if (mag != 0) {
      y0 /= mag;
      y1 /= mag;
      y2 /= mag;
    }
    camera.set(x0, x1, x2, 0,
               y0, y1, y2, 0,
               z0, z1, z2, 0,
               0,  0,  0,  1);
    camera.translate(-eyeX, -eyeY, -eyeZ);
    cameraInv.reset();
    cameraInv.invApply(x0, x1, x2, 0,
                       y0, y1, y2, 0,
                       z0, z1, z2, 0,
                       0,  0,  0,  1);
    cameraInv.invTranslate(-eyeX, -eyeY, -eyeZ);
    modelview.set(camera);
    modelviewInv.set(cameraInv);
  }
  public void printCamera() {
    camera.print();
  }
  public void ortho() {
    ortho(0, width, 0, height, -10, 10);
  }
  public void ortho(float left, float right,
                    float bottom, float top,
                    float near, float far) {
    float x =  2.0f / (right - left);
    float y =  2.0f / (top - bottom);
    float z = -2.0f / (far - near);
    float tx = -(right + left) / (right - left);
    float ty = -(top + bottom) / (top - bottom);
    float tz = -(far + near) / (far - near);
    projection.set(x, 0, 0, tx,
                   0, y, 0, ty,
                   0, 0, z, tz,
                   0, 0, 0, 1);
  }
  public void perspective() {
    perspective(cameraFOV, cameraAspect, cameraNear, cameraFar);
  }
  public void perspective(float fov, float aspect, float zNear, float zFar) {
    float ymax = zNear * tan(fov / 2.0f);
    float ymin = -ymax;
    float xmin = ymin * aspect;
    float xmax = ymax * aspect;
    frustum(xmin, xmax, ymin, ymax, zNear, zFar);
  }
  public void frustum(float left, float right, float bottom,
                      float top, float znear, float zfar) {
    projection.set((2*znear)/(right-left), 0, (right+left)/(right-left), 0,
                   0, (2*znear)/(top-bottom), (top+bottom)/(top-bottom), 0,
                   0, 0, -(zfar+znear)/(zfar-znear),-(2*zfar*znear)/(zfar-znear),
                   0, 0, -1, 0);
  }
  public void printProjection() {
    projection.print();
  }
  public float screenX(float x, float y) {
    return screenX(x, y, 0);
  }
  public float screenY(float x, float y) {
    return screenY(x, y, 0);
  }
  public float screenX(float x, float y, float z) {
    float ax =
      modelview.m00*x + modelview.m01*y + modelview.m02*z + modelview.m03;
    float ay =
      modelview.m10*x + modelview.m11*y + modelview.m12*z + modelview.m13;
    float az =
      modelview.m20*x + modelview.m21*y + modelview.m22*z + modelview.m23;
    float aw =
      modelview.m30*x + modelview.m31*y + modelview.m32*z + modelview.m33;
    float ox =
      projection.m00*ax + projection.m01*ay +
      projection.m02*az + projection.m03*aw;
    float ow =
      projection.m30*ax + projection.m31*ay +
      projection.m32*az + projection.m33*aw;
    if (ow != 0) ox /= ow;
    return width * (1 + ox) / 2.0f;
  }
  public float screenY(float x, float y, float z) {
    float ax =
      modelview.m00*x + modelview.m01*y + modelview.m02*z + modelview.m03;
    float ay =
      modelview.m10*x + modelview.m11*y + modelview.m12*z + modelview.m13;
    float az =
      modelview.m20*x + modelview.m21*y + modelview.m22*z + modelview.m23;
    float aw =
      modelview.m30*x + modelview.m31*y + modelview.m32*z + modelview.m33;
    float oy =
      projection.m10*ax + projection.m11*ay +
      projection.m12*az + projection.m13*aw;
    float ow =
      projection.m30*ax + projection.m31*ay +
      projection.m32*az + projection.m33*aw;
    if (ow != 0) oy /= ow;
    return height * (1 + oy) / 2.0f;
  }
  public float screenZ(float x, float y, float z) {
    float ax =
      modelview.m00*x + modelview.m01*y + modelview.m02*z + modelview.m03;
    float ay =
      modelview.m10*x + modelview.m11*y + modelview.m12*z + modelview.m13;
    float az =
      modelview.m20*x + modelview.m21*y + modelview.m22*z + modelview.m23;
    float aw =
      modelview.m30*x + modelview.m31*y + modelview.m32*z + modelview.m33;
    float oz =
      projection.m20*ax + projection.m21*ay +
      projection.m22*az + projection.m23*aw;
    float ow =
      projection.m30*ax + projection.m31*ay +
      projection.m32*az + projection.m33*aw;
    if (ow != 0) oz /= ow;
    return (oz + 1) / 2.0f;
  }
  public float modelX(float x, float y, float z) {
    float ax =
      cameraInv.m00*x + cameraInv.m01*y + cameraInv.m02*z + cameraInv.m03;
    float ay =
      cameraInv.m10*x + cameraInv.m11*y + cameraInv.m12*z + cameraInv.m13;
    float az =
      cameraInv.m20*x + cameraInv.m21*y + cameraInv.m22*z + cameraInv.m23;
    float aw =
      cameraInv.m30*x + cameraInv.m31*y + cameraInv.m32*z + cameraInv.m33;
    float ox =
      modelview.m00*ax + modelview.m01*ay +
      modelview.m02*az + modelview.m03*aw;
    float ow =
      modelview.m30*ax + modelview.m31*ay +
      modelview.m32*az + modelview.m33*aw;
    return (ow != 0) ? ox / ow : ox;
  }
  public float modelY(float x, float y, float z) {
    float ax =
      cameraInv.m00*x + cameraInv.m01*y + cameraInv.m02*z + cameraInv.m03;
    float ay =
      cameraInv.m10*x + cameraInv.m11*y + cameraInv.m12*z + cameraInv.m13;
    float az =
      cameraInv.m20*x + cameraInv.m21*y + cameraInv.m22*z + cameraInv.m23;
    float aw =
      cameraInv.m30*x + cameraInv.m31*y + cameraInv.m32*z + cameraInv.m33;
    float oy =
      modelview.m10*ax + modelview.m11*ay +
      modelview.m12*az + modelview.m13*aw;
    float ow =
      modelview.m30*ax + modelview.m31*ay +
      modelview.m32*az + modelview.m33*aw;
    return (ow != 0) ? oy / ow : oy;
  }
  public float modelZ(float x, float y, float z) {
    float ax =
      cameraInv.m00*x + cameraInv.m01*y + cameraInv.m02*z + cameraInv.m03;
    float ay =
      cameraInv.m10*x + cameraInv.m11*y + cameraInv.m12*z + cameraInv.m13;
    float az =
      cameraInv.m20*x + cameraInv.m21*y + cameraInv.m22*z + cameraInv.m23;
    float aw =
      cameraInv.m30*x + cameraInv.m31*y + cameraInv.m32*z + cameraInv.m33;
    float oz =
      modelview.m20*ax + modelview.m21*ay +
      modelview.m22*az + modelview.m23*aw;
    float ow =
      modelview.m30*ax + modelview.m31*ay +
      modelview.m32*az + modelview.m33*aw;
    return (ow != 0) ? oz / ow : oz;
  }
  public void strokeJoin(int join) {
    String msg = "strokeJoin() not available with P3D";
    throw new RuntimeException(msg);
  }
  public void strokeCap(int cap) {
    String msg = "strokeCap() not available with P3D";
    throw new RuntimeException(msg);
  }
  protected void fillFromCalc() {
    super.fillFromCalc();
    ambientFromCalc();
  }
  public void ambient(int rgb) {
    if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) {  
      ambient((float) rgb);
    } else {
      colorCalcARGB(rgb, colorModeA);
      ambientFromCalc();
    }
  }
  public void ambient(float gray) {
    colorCalc(gray);
    ambientFromCalc();
  }
  public void ambient(float x, float y, float z) {
    colorCalc(x, y, z);
    ambientFromCalc();
  }
  protected void ambientFromCalc() {
    ambientR = calcR;
    ambientG = calcG;
    ambientB = calcB;
  }
  public void specular(int rgb) {
    if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) {  
      specular((float) rgb);
    } else {
      colorCalcARGB(rgb, colorModeA);
      specularFromCalc();
    }
  }
  public void specular(float gray) {
    colorCalc(gray);
    specularFromCalc();
  }
  public void specular(float gray, float alpha) {
    colorCalc(gray, alpha);
    specularFromCalc();
  }
  public void specular(float x, float y, float z) {
    colorCalc(x, y, z);
    specularFromCalc();
  }
  public void specular(float x, float y, float z, float a) {
    colorCalc(x, y, z, a);
    specularFromCalc();
  }
  protected void specularFromCalc() {
    specularR = calcR;
    specularG = calcG;
    specularB = calcB;
    specularA = calcA;
  }
  public void shininess(float shine) {
    shininess = shine;
  }
  public void emissive(int rgb) {
    if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) {  
      emissive((float) rgb);
    } else {
      colorCalcARGB(rgb, colorModeA);
      emissiveFromCalc();
    }
  }
  public void emissive(float gray) {
    colorCalc(gray);
    emissiveFromCalc();
  }
  public void emissive(float x, float y, float z) {
    colorCalc(x, y, z);
    emissiveFromCalc();
  }
  protected void emissiveFromCalc() {
    emissiveR = calcR;
    emissiveG = calcG;
    emissiveB = calcB;
  }
  public void lights() {
    int colorModeSaved = colorMode;
    colorMode = RGB;
    lightFalloff(1, 0, 0);
    lightSpecular(0, 0, 0);
    ambientLight(colorModeX * 0.5f,
                 colorModeY * 0.5f,
                 colorModeZ * 0.5f);
    directionalLight(colorModeX * 0.5f,
                     colorModeY * 0.5f,
                     colorModeZ * 0.5f,
                     0, 0, -1);
    colorMode = colorModeSaved;
    lightingDependsOnVertexPosition = false;
  }
  public void ambientLight(float r, float g, float b) {
    ambientLight(r, g, b, 0, 0, 0);
  }
  public void ambientLight(float r, float g, float b,
                           float x, float y, float z) {
    if (lightCount == MAX_LIGHTS) {
      throw new RuntimeException("can only create " + MAX_LIGHTS + " lights");
    }
    colorCalc(r, g, b);
    lightDiffuse[lightCount][0] = calcR;
    lightDiffuse[lightCount][1] = calcG;
    lightDiffuse[lightCount][2] = calcB;
    lightType[lightCount] = AMBIENT;
    lightFalloffConstant[lightCount] = currentLightFalloffConstant;
    lightFalloffLinear[lightCount] = currentLightFalloffLinear;
    lightFalloffQuadratic[lightCount] = currentLightFalloffQuadratic;
    lightPosition(lightCount, x, y, z);
    lightCount++;
  }
  public void directionalLight(float r, float g, float b,
                               float nx, float ny, float nz) {
    if (lightCount == MAX_LIGHTS) {
      throw new RuntimeException("can only create " + MAX_LIGHTS + " lights");
    }
    colorCalc(r, g, b);
    lightDiffuse[lightCount][0] = calcR;
    lightDiffuse[lightCount][1] = calcG;
    lightDiffuse[lightCount][2] = calcB;
    lightType[lightCount] = DIRECTIONAL;
    lightFalloffConstant[lightCount] = currentLightFalloffConstant;
    lightFalloffLinear[lightCount] = currentLightFalloffLinear;
    lightFalloffQuadratic[lightCount] = currentLightFalloffQuadratic;
    lightSpecular[lightCount][0] = currentLightSpecular[0];
    lightSpecular[lightCount][1] = currentLightSpecular[1];
    lightSpecular[lightCount][2] = currentLightSpecular[2];
    lightDirection(lightCount, nx, ny, nz);
    lightCount++;
  }
  public void pointLight(float r, float g, float b,
                         float x, float y, float z) {
    if (lightCount == MAX_LIGHTS) {
      throw new RuntimeException("can only create " + MAX_LIGHTS + " lights");
    }
    colorCalc(r, g, b);
    lightDiffuse[lightCount][0] = calcR;
    lightDiffuse[lightCount][1] = calcG;
    lightDiffuse[lightCount][2] = calcB;
    lightType[lightCount] = POINT;
    lightFalloffConstant[lightCount] = currentLightFalloffConstant;
    lightFalloffLinear[lightCount] = currentLightFalloffLinear;
    lightFalloffQuadratic[lightCount] = currentLightFalloffQuadratic;
    lightSpecular[lightCount][0] = currentLightSpecular[0];
    lightSpecular[lightCount][1] = currentLightSpecular[1];
    lightSpecular[lightCount][2] = currentLightSpecular[2];
    lightPosition(lightCount, x, y, z);
    lightCount++;
    lightingDependsOnVertexPosition = true;
  }
  public void spotLight(float r, float g, float b,
                        float x, float y, float z,
                        float nx, float ny, float nz,
                        float angle, float concentration) {
    if (lightCount == MAX_LIGHTS) {
      throw new RuntimeException("can only create " + MAX_LIGHTS + " lights");
    }
    colorCalc(r, g, b);
    lightDiffuse[lightCount][0] = calcR;
    lightDiffuse[lightCount][1] = calcG;
    lightDiffuse[lightCount][2] = calcB;
    lightType[lightCount] = SPOT;
    lightFalloffConstant[lightCount] = currentLightFalloffConstant;
    lightFalloffLinear[lightCount] = currentLightFalloffLinear;
    lightFalloffQuadratic[lightCount] = currentLightFalloffQuadratic;
    lightSpecular[lightCount][0] = currentLightSpecular[0];
    lightSpecular[lightCount][1] = currentLightSpecular[1];
    lightSpecular[lightCount][2] = currentLightSpecular[2];
    lightPosition(lightCount, x, y, z);
    lightDirection(lightCount, nx, ny, nz);
    lightSpotAngle[lightCount] = angle;
    lightSpotAngleCos[lightCount] = max(0, cos(angle));
    lightSpotConcentration[lightCount] = concentration;
    lightCount++;
    lightingDependsOnVertexPosition = true;
  }
  public void lightFalloff(float constant, float linear, float quadratic) {
    currentLightFalloffConstant = constant;
    currentLightFalloffLinear = linear;
    currentLightFalloffQuadratic = quadratic;
    lightingDependsOnVertexPosition = true;
  }
  public void lightSpecular(float x, float y, float z) {
    colorCalc(x, y, z);
    currentLightSpecular[0] = calcR;
    currentLightSpecular[1] = calcG;
    currentLightSpecular[2] = calcB;
    lightingDependsOnVertexPosition = true;
  }
  protected void lightPosition(int num, float x, float y, float z) {
    lightPosition[num][0] =
      modelview.m00*x + modelview.m01*y + modelview.m02*z + modelview.m03;
    lightPosition[num][1] =
      modelview.m10*x + modelview.m11*y + modelview.m12*z + modelview.m13;
    lightPosition[num][2] =
      modelview.m20*x + modelview.m21*y + modelview.m22*z + modelview.m23;
  }
  protected void lightDirection(int num, float x, float y, float z) {
    lightNormal[num][0] =
      modelviewInv.m00*x + modelviewInv.m10*y +
      modelviewInv.m20*z + modelviewInv.m30;
    lightNormal[num][1] =
      modelviewInv.m01*x + modelviewInv.m11*y +
      modelviewInv.m21*z + modelviewInv.m31;
    lightNormal[num][2] =
      modelviewInv.m02*x + modelviewInv.m12*y +
      modelviewInv.m22*z + modelviewInv.m32;
    float n = mag(lightNormal[num]);
    if (n == 0 || n == 1) return;
    lightNormal[num][0] /= n;
    lightNormal[num][1] /= n;
    lightNormal[num][2] /= n;
  }
  public void background(PImage image) {
    super.background(image);
    for (int i = 0; i < pixelCount; i++) {
      zbuffer[i] = Float.MAX_VALUE;
    }
  }
  protected void clear() {
    for (int i = 0; i < pixelCount; i++) {
      pixels[i] = backgroundColor;
      zbuffer[i] = Float.MAX_VALUE;
    }
  }
  public void smooth() {
    String msg = "smooth() not available with P3D";
    throw new RuntimeException(msg);
  }
  public void noSmooth() {
    String msg = "noSmooth() not available with P3D";
    throw new RuntimeException(msg);
  }
  private final float mag(float a, float b, float c) {
    return (float)Math.sqrt(a*a + b*b + c*c);
  }
  private final float mag(float abc[]) {
    return (float)Math.sqrt(abc[0]*abc[0] + abc[1]*abc[1] + abc[2]*abc[2]);
  }
  private final float min(float a, float b) {
    return (a < b) ? a : b;
  }
  private final float max(float a, float b) {
    return (a > b) ? a : b;
  }
  private final float pow(float a, float b) {
    return (float)Math.pow(a, b);
  }
  private final float abs(float a) {
    return (a < 0) ? -a : a;
  }
  private final float cos(float angle) {
    return (float)Math.cos(angle);
  }
  private final float tan(float angle) {
    return (float)Math.tan(angle);
  }
  private float dot(float ax, float ay, float az,
                    float bx, float by, float bz) {
    return ax * bx + ay * by + az * bz;
  }
}
