public static final int find2dIntersection(
      int[][] intersections,
      int index,
      ProjectionMesh backFace,
      ProjectionLine backLine,
      int[] frontFrom,
      int[] frontTo) {
    // check that the lines are even in the same vicinity
    // "blur" the edges to account for our rounding errors
    int result;

    int x12 = MathFP.toFP(frontFrom[0]);
    int y12 = MathFP.toFP(frontFrom[1]);
    //        int z12 = MathFP.toFP(frontFrom[2]);

    int x22 = MathFP.toFP(frontTo[0]);
    int y22 = MathFP.toFP(frontTo[1]);
    //        int z22 = MathFP.toFP(frontTo[2]);

    int minfrontx = Math.min(x12, x22) - FixedMath.DEFAULT_ONE / 2;
    int minfronty = Math.min(y12, y22) - FixedMath.DEFAULT_ONE / 2;
    int maxfrontx = Math.max(x12, x22) + FixedMath.DEFAULT_ONE / 2;
    int maxfronty = Math.max(y12, y22) + FixedMath.DEFAULT_ONE / 2;

    byte backLineIndex = backLine.fromIndex;
    byte nextBackLineIndex = backLine.toIndex;

    int x11 = MathFP.toFP(backFace.points[backLineIndex][0]);
    int y11 = MathFP.toFP(backFace.points[backLineIndex][1]);
    int z11 = backFace.points[backLineIndex][2];

    int x21 = MathFP.toFP(backFace.points[nextBackLineIndex][0]);
    int y21 = MathFP.toFP(backFace.points[nextBackLineIndex][1]);
    int z21 = backFace.points[nextBackLineIndex][2];

    int dx1 = x21 - x11;
    int dy1 = y21 - y11;
    int dz1 = z21 - z11;

    int dx2 = x22 - x12;
    int dy2 = y22 - y12;
    //        int dz2 = z22 - z12;

    if (dx1 == 0 && dx2 == 0) {
      // they're both vertical lines or points
      result = index;
    } else if (dx1 == 0) {
      // l1 is a vertical line
      int m2 = MathFP.div(dy2, dx2);
      int c2 = y22 - MathFP.mul(m2, x22);
      int y = MathFP.mul(x21, m2) + c2;
      int x = x21;

      if (x >= minfrontx && x <= maxfrontx && dy1 != 0) {
        // work out the dz of the intersection point
        // cancels out : int z1 = z11 + FixedMath.divide(FixedMath.multiply(dz1, (y - y11)), dy1);
        int z1 = z11 + (dz1 * (y - y11)) / dy1;
        //                int z2 = z12 + FixedMath.divide(FixedMath.multiply(dz2, (x - x12)), dx2);
        intersections[index][0] = FixedMath.getInteger(x);
        intersections[index][1] = FixedMath.getInteger(y);
        //                intersections[index][2] = Math.min(z1, z2);
        intersections[index][2] = z1;
        result = index + 1;
      } else {
        result = index;
      }
    } else if (dx2 == 0) {
      // l2 is a vertical line

      int m1 = MathFP.div(dy1, dx1);
      int c1 = y21 - MathFP.mul(m1, x21);
      int y = MathFP.mul(x22, m1) + c1;

      if (y >= minfronty && y <= maxfronty && dy2 != 0) {
        // work out the dz of the intersection point
        int x = x22;
        // cancels out int z1 = z11 + MathFP.div(FixedMath.multiply(dz1, (x - x11)), dx1);
        int z1 = z11 + ((dz1 * (x - x11)) / dx1);
        //                int z2 = z12 + FixedMath.divide(FixedMath.multiply(dz2, (y - y12)), dy2);
        intersections[index][0] = FixedMath.getInteger(x);
        intersections[index][1] = FixedMath.getInteger(y);
        //                intersections[index][2] = Math.min(z1, z2);
        intersections[index][2] = z1;
        result = index + 1;
      } else {
        result = index;
      }
    } else {
      // check for gradient equality
      if (MathFP.mul(dy1, dx2) == MathFP.mul(dy2, dx1)) {
        result = index;
        //                System.out.println("parallel");
      } else {
        int m2 = MathFP.div(dy2, dx2);
        int c2 = y22 - MathFP.mul(m2, x22);
        int m1 = MathFP.div(dy1, dx1);
        int c1 = y21 - MathFP.mul(m1, x21);
        int x = MathFP.div((c2 - c1), (m1 - m2));
        int y = MathFP.mul(m1, x) + c1;
        // go for the larger, more accurate m

        //                System.out.print("intersection
        // ("+FixedMath.getInteger(x)+","+FixedMath.getInteger(y)+")");
        // check that it is contained
        if (x >= minfrontx && x <= maxfrontx && y >= minfronty && y <= maxfronty) {
          // work out the dz of the intersection point
          // int z1 = z11 + FixedMath.divide(FixedMath.multiply(dz1, (x - x11)), dx1);
          // precision problems for multiplies and divides cancel out here
          int z1 = z11 + (dz1 * (x - x11)) / dx1;
          // int z2 = z12 + FixedMath.divide(FixedMath.multiply(dz2, (x - x12)), dx2);
          intersections[index][0] = FixedMath.getInteger(x);
          intersections[index][1] = FixedMath.getInteger(y);
          // intersections[index][2] = Math.min(z1, z2);
          intersections[index][2] = z1;
          result = index + 1;
          //                    System.out.println(" - good");
        } else {
          result = index;
          //                    System.out.println(" - bad");
        }
      }
    }
    return result;
  }
  public void project(Body body) {
    Shape shape = body.shape;

    int dx = body.center[0] - cx;
    int dy = body.center[1] - cy;
    int dz = body.center[2] - cz;

    int[] dpoints = new int[] {dx, dy, dz};

    int[] rotateddpoints = MatrixMath.multiplyVector(dpoints, dpoints, this.rotationMatrix);

    int rdxf = rotateddpoints[0];
    int rdyf = rotateddpoints[1];
    int rdzf = rotateddpoints[2];

    int rdx = FixedMath.getInteger(rdxf);
    int rdy = FixedMath.getInteger(rdyf);
    int rdz = FixedMath.getInteger(rdzf);

    int radius = FixedMath.getInteger(shape.getMaxRadius());

    // TODO : rotate the body center around the viewers center by the view angle

    boolean mightBeVisible;
    // check that the body is actually visible at all (not sure about deye check)

    if ((rdz - radius) < -this.deye && rdz < 0) {

      // double pow = Math.pow(2,
      // Math.abs((double)FixedMath.getInteger(dz)/(double)this.shrinkage));
      int adjustedRadius = (radius * deye) / -rdz;
      // check that the mesh isn't so small that it won't be visible or should
      // be simplified to a point

      if (adjustedRadius >= this.minRadius) {
        int screenycenter = (rdy * deye) / -rdz;
        if ((screenycenter - adjustedRadius < this.height / 2)
            && (screenycenter + adjustedRadius > -this.height / 2)) {
          int screenxcenter = (rdx * deye) / -rdz;
          mightBeVisible =
              ((screenxcenter - adjustedRadius < this.width / 2)
                  && (screenxcenter + adjustedRadius > -this.width / 2));
          //                    if(!mightBeVisible)
          //                    {
          //                        System.out.println("outside x bounds of screen");
          //                    }
        } else {
          mightBeVisible = false;
          // System.out.println("outside y bounds of screen "+rdy+" "+screenycenter);
        }
      } else {
        // TODO : simplify to a point if it's reasonably close
        // too far away
        mightBeVisible = false;
        // System.out.println("too small "+body.center[2]+" "+rdz+" "+radius+" -> "+adjustedRadius);
      }
    } else {
      mightBeVisible = false;
      // System.out.println("behind pane "+rdz);
    }

    // mightBeVisible = (rdz - radius) <= 0;

    if (mightBeVisible) {
      if (shape instanceof Mesh) {
        Mesh mesh = (Mesh) shape;
        int[][] working = body.rotatedMatrix;
        if (working == null) {
          int[][] points = mesh.baseMatrix;
          working = new int[points.length][points[0].length];
          body.rotatedMatrix = working;
        }
        addMesh(mesh, body, working, rdxf, rdyf, rdzf);
      } else if (shape instanceof Composite) {
        Composite composite = (Composite) shape;
        addComposite(composite, body, rdxf, rdyf, rdzf);
      } else if (shape instanceof Sphere) {
        // TODO : sphere
      } else {
        throw new IllegalArgumentException("unrecognised shape type : " + shape.getClass());
      }
    }
  }
  private void addMesh(Mesh mesh, Body body, int[][] working, int rdxf, int rdyf, int rdzf) {
    int[][] points = mesh.baseMatrix;
    // MatrixMath.translate(points, working, dx, dy, dz);
    // project onto the view plane

    // int[][] target = new int[mesh.baseMatrix.length][mesh.baseMatrix[0].length];
    int[][] target = working;
    int[] rotation = new int[5];
    int[][] rotationProjection = new int[3][3];
    if (mesh instanceof AnimatedMesh) {
      // we need to redo the rotation
      AnimatedMesh animatedMesh = (AnimatedMesh) mesh;
      //            MatrixMath.multiplyQuaternion(rotation, body.rotation,
      // animatedMesh.animation.getRotation());
      //            MatrixMath.multiplyQuaternion(rotation, rotation, this.rotation);
      MatrixMath.multiplyQuaternion(rotation, animatedMesh.animation.getRotation(), body.rotation);
      MatrixMath.multiplyQuaternion(rotation, rotation, this.rotation);
      MatrixMath.getRotationMatrix(rotationProjection, rotation);
    } else {
      MatrixMath.multiplyQuaternion(rotation, body.rotation, this.rotation);
      MatrixMath.getRotationMatrix(rotationProjection, rotation);
    }

    MatrixMath.multiply(target, points, rotationProjection);
    // MatrixMath.rotate(target, points, new int[4][4], rx, ry, rz);
    points = target;

    for (int row = points.length; row > 0; ) {
      row--;
      points[row][0] += rdxf;
      points[row][1] += rdyf;
      points[row][2] += rdzf;
    }

    // TODO : replace with single multiplied out matrix from above!!

    for (int row = points.length; row > 0; ) {
      row--;
      // points[row][0] += rdx;
      // points[row][1] += rdy;
      // points[row][2] += rdz;
      for (int col = 3; col > 0; ) {
        col--;
        // points[row][col] += points[points.length-1][col];
        // convert back to pixel values, after all, that's all the precision we'll need from now on
        // points[row][col] += points[points.length-1][col];
        points[row][col] = FixedMath.getInteger(points[row][col]);
      }

      // perspective
      // TODO : have a cut off point for when we even stop representing dots

      int z = points[row][2];
      if (z >= 0) {
        z = -1;
      }
      points[row][0] = (points[row][0] * deye) / -z;
      points[row][1] = (points[row][1] * deye) / -z;
    }

    ProjectionMesh projectionMesh = new ProjectionMesh(points);
    byte[][] faces = mesh.faces;
    projectionMesh.color = mesh.color;

    // work out the mesh's convex hull for intersections and rendering beauty
    // TODO : reuse working somehow
    byte[] stack = new byte[points.length];
    int stackLength = getConvexHull(points, new byte[points.length], stack);
    projectionMesh.hull = stack;
    projectionMesh.hullLength = stackLength;

    // add the convex hull to the lines
    // to reduce intersection detection and allow accurate
    // outlining
    for (int i = stackLength; i > 0; ) {
      i--;
      byte fromIndex = stack[i];
      byte nextIndex = stack[(i + 1) % stackLength];
      int[] from = points[fromIndex];
      int[] next = points[nextIndex];
      ProjectionLine line = new ProjectionLine(from, next, fromIndex, nextIndex, true);
      projectionMesh.lines.addElement(line);
    }

    for (int i = faces.length; i > 0; ) {
      i--;
      byte[] faceIndices = faces[i];

      // beckface removal
      int minFaceIndex = faceIndices.length - 1;
      int minIndex = faceIndices[minFaceIndex];
      int miny = points[minIndex][1];
      int minx = points[minIndex][0];
      for (int j = faceIndices.length - 1; j > 0; ) {
        j--;
        int index = (int) faceIndices[j];
        if (points[index][1] < miny || (points[index][1] == miny && points[index][0] < minx)) {
          minFaceIndex = j;
          miny = points[index][1];
          minx = points[index][0];
        }
      }

      int preIndex = minFaceIndex - 1;
      if (preIndex < 0) {
        preIndex = faceIndices.length - 1;
      }
      preIndex = faceIndices[preIndex];
      int postIndex = faceIndices[(minFaceIndex + 1) % faceIndices.length];

      int prex = points[preIndex][0];
      int prey = points[preIndex][1];
      int postx = points[postIndex][0];
      int posty = points[postIndex][1];

      int predx = prex - minx;
      int predy = prey - miny;
      int postdx = postx - minx;
      int postdy = posty - miny;

      boolean faceOK;
      if (predx == 0 && postdx == 0) {
        // it's flat, don't draw it
        // System.out.println("no dx");
        faceOK = false;
      } else if (predx == 0) {
        // the incoming line comes from the right
        // System.out.println("no predx "+preIndex+"("+prex+","+prey+")
        // "+minIndex+"("+minx+","+miny+") "+postIndex+"("+postx+","+posty+")");
        faceOK = postdx > 0;
      } else if (postdx == 0) {
        // the outbound line goes to the right
        // System.out.println("no postdx "+preIndex+"("+prex+","+prey+")
        // "+minIndex+"("+minx+","+miny+") "+postIndex+"("+postx+","+posty+")");

        faceOK = predx < 0;
      } else {
        // multiply it out a bit, we're losing precision, preferably this multiplier will be the
        // width
        // of the screen since that will ensure that it's longer than almost any value of dx
        int prem = (predy * 1024) / predx;
        int postm = (postdy * 1024) / postdx;
        // System.out.println("not vertical "+preIndex+"("+prex+","+prey+")
        // "+minIndex+"("+minx+","+miny+") "+postIndex+"("+postx+","+posty+")");
        if (prem == postm) {
          // it's flat
          faceOK = false;
        } else if (prem < 0 && postm < 0 || prem >= 0 && postm >= 0) {
          faceOK = prem > postm;
        } else {
          faceOK = prem < 0 && postm >= 0;
        }
        // or, more accurately (negatives dys and zeros are impossible in this context)
        // faceOK = predy * postdx > predx * postdy;
        //                    double prem =
        // (double)FixedMath.getInteger(predy)/(double)FixedMath.getInteger(predx);
        //                    double postm =
        // (double)FixedMath.getInteger(postdy)/(double)FixedMath.getInteger(postdx);
        //                    double preAngle = Math.atan(prem);
        //                    double postAngle = Math.atan(postm);
        //                    System.out.println(preAngle);
        //                    System.out.println(postAngle);
        //                    faceOK = preAngle < postAngle;
      }

      if (faceOK) {
        for (int j = faceIndices.length; j > 0; ) {
          j--;
          byte index = faceIndices[j];
          byte nextIndex = faceIndices[(j + 1) % faceIndices.length];

          // if a line with the same indices exists then we don't need to
          // add it again
          boolean found = false;
          for (int k = projectionMesh.lines.size(); k > 0; ) {
            k--;
            ProjectionLine line = (ProjectionLine) projectionMesh.lines.elementAt(k);
            if (line.fromIndex == index && line.toIndex == nextIndex
                || line.toIndex == index && line.fromIndex == nextIndex) {
              found = true;
              break;
            } else if (line.perimeter) {
              // the line's as long as it can be, hence may contain other lines
              if (line.fromIndex == index) {
                int[] common = points[index];
                int[] linePoint = points[line.toIndex];
                int[] testPoint = points[nextIndex];
                if (this.isLeft(common, linePoint, testPoint) == 0) {
                  found = true;
                  break;
                }
              } else if (line.toIndex == index) {
                int[] common = points[index];
                int[] linePoint = points[line.fromIndex];
                int[] testPoint = points[nextIndex];
                if (this.isLeft(common, linePoint, testPoint) == 0) {
                  found = true;
                  break;
                }
              } else if (line.fromIndex == nextIndex) {
                int[] common = points[nextIndex];
                int[] linePoint = points[line.toIndex];
                int[] testPoint = points[index];
                if (this.isLeft(common, linePoint, testPoint) == 0) {
                  found = true;
                  break;
                }
              } else if (line.toIndex == nextIndex) {
                int[] common = points[nextIndex];
                int[] linePoint = points[line.fromIndex];
                int[] testPoint = points[index];
                if (this.isLeft(common, linePoint, testPoint) == 0) {
                  found = true;
                  break;
                }
              } else {
                // TODO : implement this...
                // test to see if the line sits within the other although this
                // is probably a very unusual case
                found = false;
              }
            }
          }
          if (!found) {
            ProjectionLine line = new ProjectionLine(projectionMesh, index, nextIndex, false);
            projectionMesh.lines.addElement(line);
          }
        }
      } else {
        // System.out.println("culled "+i);
      }
    }

    this.projected.addElement(projectionMesh);
  }