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); }