The following class is used in program HLines.java, listed in Section 6.8:
// CvHLines.java: Used in the file HLines.java. import java.awt.*; import java.util.*; class CvHLines extends Canvas3D { private int maxX, maxY, centerX, centerY, nTria, nVertices; private Obj3D obj; private Point2D imgCenter; private Tria[] tr; private HPGL hpgl; private int[] refPol; private int[][] connect; private int[] nConnect; private int chunkSize = 4; private double hLimit; private Vector polyList; private float maxScreenRange; Obj3D getObj(){return obj;} void setObj(Obj3D obj){this.obj = obj;}
void setHPGL(HPGL hpgl){this.hpgl = hpgl;} public void paint(Graphics g) { if (obj == null) return; Vector polyList = obj.getPolyList(); if (polyList == null) return; int nFaces = polyList.size(); if (nFaces == 0) return; float xe, ye, ze; Dimension dim = getSize(); maxX = dim.width - 1; maxY = dim.height - 1; centerX = maxX/2; centerY = maxY/2; // ze-axis towards eye, so ze-coordinates of // object points are all negative. Since screen // coordinates x and y are used to interpolate for // the z-direction, we have to deal with 1/z instead // of z. With negative z, a small value of 1/z means // a small value of |z| for a nearby point. // obj is a java object that contains all data, // with w, e and vScr parallel (with vertex numbers // as index values): // - Vector w (with Point3D elements) // - Array e (with Point3D elements) // - Array vScr (with Point2D elements) // - Vector polyList (with Polygon3D elements) // Every Polygon3D value contains: // - Array 'nrs' for vertex numbers (n elements) // - Values a, b, c, h for the plane ax+by+cz=h. // - Array t (with n-2 elements of type Tria) // Every Tria value consists of the three vertex // numbers A, B and C. maxScreenRange = obj.eyeAndScreen(dim); imgCenter = obj.getImgCenter(); obj.planeCoeff(); // Compute a, b, c and h. hLimit = −1e-6 * obj.getRho(); buildLineSet(); // Construct an array of triangles in // each polygon and count the total number
// of triangles. nTria = 0; for (int j=0; j<nFaces; j++) { Polygon3D pol = (Polygon3D)(polyList.elementAt(j)); if (pol.getNrs().length > 2 && pol.getH() <= hLimit) { pol.triangulate(obj); nTria += pol.getT().length; } } tr = new Tria[nTria]; // Triangles of all polygons refPol = new int[nTria]; // tr[i] belongs to refPol[i] int iTria = 0; for (int j=0; j<nFaces; j++) { Polygon3D pol = (Polygon3D)(polyList.elementAt(j)); Tria[] t = pol.getT(); // Triangles of one polygon if (pol.getNrs().length > 2 && pol.getH() <= hLimit) { for (int i=0; i<t.length; i++) { Tria tri = t[i]; tr[iTria] = tri; refPol[iTria++] = j; } } } Point3D[] e = obj.getE(); Point2D[] vScr = obj.getVScr(); for (int i=0; i<nVertices; i++) { for (int j=0; j<nConnect[i]; j++) { int jj = connect[i][j]; lineSegment(g, e[i], e[jj], vScr[i], vScr[jj], i, jj, 0); } } hpgl = null; } private void buildLineSet() { // Build the array // 'connect' of int arrays, where // connect[i] is the array of all // vertex numbers j, such that connect[i][j] is // an edge of the 3D object. polyList = obj.getPolyList();
nVertices = obj.getVScr().length; connect = new int[nVertices][]; nConnect = new int[nVertices]; for (int i=0; i<nVertices; i++) nConnect[i] = 0; int nFaces = polyList.size(); for (int j=0; j<nFaces; j++) { Polygon3D pol = (Polygon3D)(polyList.elementAt(j)); int[] nrs = pol.getNrs(); int n = nrs.length; if (n > 2 && pol.getH() > 0) continue; int ii = Math.abs(nrs[n-1]); for (int k=0; k<n; k++) { int jj = nrs[k]; if (jj < 0) jj = -jj; // abs else { int i1 = Math.min(ii, jj), j1 = Math.max(ii, jj), nCon = nConnect[i1]; // Look if j1 is already present: int l; for (l=0; l<nCon; l++) if (connect[i1][l] == j1) break; if (l == nCon) // Not found: { if (nCon % chunkSize == 0) { int[] temp = new int[nCon + chunkSize]; for (l=0; l<nCon; l++) temp[l] = connect[i1][l]; connect[i1] = temp; } connect[i1][nConnect[i1]++] = j1; } } ii = jj; } } } int iX(float x){return Math.round(centerX + x - imgCenter.x);} int iY(float y){return Math.round(centerY - y + imgCenter.y);} private String toString(float t) // From screen device units (pixels) to HP-GL units (0-10000) : { int i = Math.round(5000 + t * 9000/maxScreenRange);
String s = ""; int n = 1000; for (int j=3; j>=0; j--) { s += i/n; i %= n; n /= 10; } return s; } private String hpx(float x){return toString(x - imgCenter.x);} private String hpy(float y){return toString(y - imgCenter.y);} private void drawLine(Graphics g, float x1, float y1, float x2, float y2) { if (x1 != x2 || y1 != y2) { g.drawLine(iX(x1), iY(y1), iX(x2), iY(y2)); if (hpgl != null) { hpgl.write("PU;PA" + hpx(x1) + "," + hpy(y1)); hpgl.write("PD;PA" + hpx(x2) + "," + hpy(y2) + " "); } } } private void lineSegment(Graphics g, Point3D p, Point3D q, Point2D pScr, Point2D qScr, int iP, int iQ, int iStart) { double u1 = qScr.x - pScr.x, u2 = qScr.y - pScr.y; double minPQx = Math.min(pScr.x, qScr.x); double maxPQx = Math.max(pScr.x, qScr.x); double minPQy = Math.min(pScr.y, qScr.y); double maxPQy = Math.max(pScr.y, qScr.y); double zP = p.z, zQ = q.z; // p and q give eye-coordinates double minPQz = Math.min(zP, zQ); Point3D[] e = obj.getE(); Point2D[] vScr = obj.getVScr(); for (int i=iStart; i<nTria; i++) { Tria t = tr[i]; int iA = t.iA, iB = t.iB, iC = t.iC; Point2D aScr = vScr[iA], bScr = vScr[iB], cScr = vScr[iC]; // 1. Minimax test for x and y screen coordinates: if (maxPQx <= aScr.x && maxPQx <= bScr.x && maxPQx <= cScr.x || minPQx >= aScr.x && minPQx >= bScr.x && minPQx >= cScr.x
|| maxPQy <= aScr.y && maxPQy <= bScr.y && maxPQy <= cScr.y || minPQy >= aScr.y && minPQy >= bScr.y && minPQy >= cScr.y) continue; // This triangle does not obscure PQ. // 2. Test if PQ is an edge of ABC: if ((iP == iA || iP == iB || iP == iC) && (iQ == iA || iQ == iB || iQ == iC)) continue; // This triangle does not obscure PQ. // 3. Test if PQ is clearly nearer than ABC: double zA = e[iA].z, zB = e[iB].z, zC = e[iC].z; if (minPQz >= zA && minPQz >= zB && minPQz >= zC) continue; // This triangle does not obscure PQ. // 4. Do P and Q (in 2D) lie in a half plane defined // by line AB, on the side other than that of C? // Similar for the edges BC and CA. double eps = 0.1; // Relative to numbers of pixels if (Tools2D.area2(aScr, bScr, pScr) < eps && Tools2D.area2(aScr, bScr, qScr) < eps || Tools2D.area2(bScr, cScr, pScr) < eps && Tools2D.area2(bScr, cScr, qScr) < eps || Tools2D.area2(cScr, aScr, pScr) < eps && Tools2D.area2(cScr, aScr, qScr) < eps) continue; // This triangle does not obscure PQ. // 5. Test (2D) if A, B and C lie on the same side // of the infinite line through P and Q: double pqa = Tools2D.area2(pScr, qScr, aScr); double pqb = Tools2D.area2(pScr, qScr, bScr); double pqc = Tools2D.area2(pScr, qScr, cScr); if (pqa < +eps && pqb < +eps && pqc < +eps || pqa > -eps && pqb > -eps && pqc > -eps) continue; // This triangle does not obscure PQ. // 6. Test if neither P nor Q lies behind the // infinite plane through A, B and C: int iPol = refPol[i]; Polygon3D pol = (Polygon3D)polyList.elementAt(iPol); double a = pol.getA(), b = pol.getB(), c = pol.getC(), h = pol.getH(), eps1 = 1e-5 * Math.abs(h), hP = a * p.x + b * p.y + c * p.z,
hQ = a * q.x + b * q.y + c * q.z; if (hP > h - eps1 && hQ > h - eps1) continue; // This triangle does not obscure PQ. // 7. Test if both P and Q behind triangle ABC: boolean pInside = Tools2D.insideTriangle(aScr, bScr, cScr, pScr); boolean qInside = Tools2D.insideTriangle(aScr, bScr, cScr, qScr); if (pInside && qInside) return; // This triangle obscures PQ. // 8. If P nearer than ABC and inside, PQ visible; // the same for Q: double h1 = h + eps1; if (hP > h1 && pInside || hQ > h1 && qInside) continue; // This triangle does not obscure PQ. // 9. Compute the intersections I and J of PQ // with ABC in 2D. // If, in 3D, such an intersection lies in front of // ABC, this triangle does not obscure PQ. // Otherwise, the intersections lie behind ABC and // this triangle obscures part of PQ: double lambdaMin = 1.0, lambdaMax = 0.0; for (int ii=0; ii<3; ii++) { double v1 = bScr.x - aScr.x, v2 = bScr.y - aScr.y, w1 = aScr.x - pScr.x, w2 = aScr.y - pScr.y, denom = u2 * v1 - u1 * v2; if (denom != 0) { double mu = (u1 * w2 - u2 * w1)/denom; // mu = 0 gives A and mu = 1 gives B. if (mu > −0.0001 && mu < 1.0001) { double lambda = (v1 * w2 - v2 * w1)/denom; // lambda = PI/PQ // (I is point of intersection) if (lambda > −0.0001 && lambda < 1.0001) { if (pInside != qInside && lambda > 0.0001 && lambda < 0.9999) { lambdaMin = lambdaMax = lambda; break; // Only one point of intersection }
if (lambda < lambdaMin) lambdaMin = lambda; if (lambda > lambdaMax) lambdaMax = lambda; } } } Point2D temp = aScr; aScr = bScr; bScr = cScr; cScr = temp; } float d = obj.getD(); if (!pInside && lambdaMin > 0.001) { double iScrx = pScr.x + lambdaMin * u1, iScry = pScr.y + lambdaMin * u2; // Back from screen to eye coordinates: double zI = 1/(lambdaMin/zQ + (1 - lambdaMin)/zP), xI = -zI * iScrx / d, yI = -zI * iScry / d; if (a * xI + b * yI + c * zI > h1) continue; // This triangle does not obscure PQ. Point2D iScr = new Point2D((float)iScrx, (float)iScry); if (Tools2D.distance2(iScr, pScr) >= 1.0) lineSegment(g, p, new Point3D(xI, yI, zI), pScr, iScr, iP, −1, i + 1); } if (!qInside && lambdaMax < 0.999) { double jScrx = pScr.x + lambdaMax * u1, jScry = pScr.y + lambdaMax * u2; double zJ = 1/(lambdaMax/zQ + (1 - lambdaMax)/zP), xJ = -zJ * jScrx / d, yJ = -zJ * jScry / d; if (a * xJ + b * yJ + c * zJ > h1) continue; // This triangle does not obscure PQ. Point2D jScr = new Point2D((float)jScrx, (float)jScry); if (Tools2D.distance2(jScr, qScr) >= 1.0) lineSegment(g, q, new Point3D(xJ, yJ, zJ), qScr, jScr, iQ, −1, i + 1); } return; // if no continue-statement has been executed } drawLine(g, pScr.x, pScr.y, qScr.x, qScr.y); // No triangle obscures PQ. } }