/* * This file is part of MoleculeViewer. * * MoleculeViewer is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * MoleculeViewer is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with MoleculeViewer. If not, see <http://www.gnu.org/licenses/>. */ package astex; import it.unimi.dsi.fastutil.floats.FloatArrayList; import it.unimi.dsi.fastutil.ints.IntArrayList; class ShadowCache { /** Sphere cache for shadows. */ private static FloatArrayList scachex = new FloatArrayList(); private static FloatArrayList scachey = new FloatArrayList(); private static FloatArrayList scachez = new FloatArrayList(); private static FloatArrayList scacher = new FloatArrayList(); /** Cylinder cache for shadows. */ private static FloatArrayList ccachex0 = new FloatArrayList(); private static FloatArrayList ccachey0 = new FloatArrayList(); private static FloatArrayList ccachez0 = new FloatArrayList(); private static FloatArrayList ccachex1 = new FloatArrayList(); private static FloatArrayList ccachey1 = new FloatArrayList(); private static FloatArrayList ccachez1 = new FloatArrayList(); private static FloatArrayList ccacher = new FloatArrayList(); /** Triangle cache for shadows. */ private static FloatArrayList tcachex0 = new FloatArrayList(); private static FloatArrayList tcachey0 = new FloatArrayList(); private static FloatArrayList tcachez0 = new FloatArrayList(); private static FloatArrayList tcachex1 = new FloatArrayList(); private static FloatArrayList tcachey1 = new FloatArrayList(); private static FloatArrayList tcachez1 = new FloatArrayList(); private static FloatArrayList tcachex2 = new FloatArrayList(); private static FloatArrayList tcachey2 = new FloatArrayList(); private static FloatArrayList tcachez2 = new FloatArrayList(); private static FloatArrayList tcen2dx = new FloatArrayList(); private static FloatArrayList tcen2dy = new FloatArrayList(); private static FloatArrayList tcenx = new FloatArrayList(); private static FloatArrayList tceny = new FloatArrayList(); private static FloatArrayList tcenz = new FloatArrayList(); private static FloatArrayList tcenr = new FloatArrayList(); /** References to the triangle array contents. */ private static float tx0[] = null; private static float ty0[] = null; private static float tz0[] = null; private static float tx1[] = null; private static float ty1[] = null; private static float tz1[] = null; private static float tx2[] = null; private static float ty2[] = null; private static float tz2[] = null; /** References to the triangle bounding sphere info. */ private static float tc2x[] = null; private static float tc2y[] = null; private static float tcx[] = null; private static float tcy[] = null; private static float tcz[] = null; private static float tcr[] = null; /** References for sphere centers. */ private static float scx[] = null; private static float scy[] = null; private static float scz[] = null; private static float scr[] = null; /** Cache and occlusion lists. */ private static IntArrayList sphereShadowCacheList = new IntArrayList(); private static IntArrayList sphereOcclusionCacheList = new IntArrayList(); private static IntArrayList cylinderShadowCacheList = new IntArrayList(); private static IntArrayList triangleShadowCacheList = new IntArrayList(); private static IntArrayList initialList = new IntArrayList(); /** Overall scale factor in the renderer. */ public static double overallScale = 1.0; /** * Prepare shadow cache list for triangle, by making bounding * volume a sphere that encloses the whole triangle. */ public static void prepareTriangleCacheList(double x0, double y0, double z0, double x1, double y1, double z1, double x2, double y2, double z2, boolean targetIsTransparent){ boundingSphereTriangle(x0, y0, z0, x1, y1, z1, x2, y2, z2, cbs); prepareSphereCacheList(cbs[0], cbs[1], cbs[2], cbs[3], targetIsTransparent); } /** Bounding sphere. */ private static double cbs[] = new double[4]; /** * Prepare shadow cache list for cylinder, by making bounding * volume a sphere that encloses the whole cylinder. */ public static void prepareCylinderCacheList(double c0x, double c0y, double c0z, double c1x, double c1y, double c1z, double r){ boundingSphereCylinder(c0x, c0y, c0z, c1x, c1y, c1z, r, cbs); prepareSphereCacheList(cbs[0], cbs[1], cbs[2], cbs[3], false); } /** Prepare bounding sphere that encloses cylinder. */ private static void boundingSphereCylinder(double c0x, double c0y, double c0z, double c1x, double c1y, double c1z, double r, double bs[]){ bs[0] = 0.5 * (c0x + c1x); bs[1] = 0.5 * (c0y + c1y); bs[2] = 0.5 * (c0z + c1z); double dmx = c0x - c1x; double dmy = c0y - c1y; double dmz = c0z - c1z; bs[3] = 0.5 * Math.sqrt(dmx*dmx + dmy*dmy + dmz*dmz) + r; } /** Prepare bounding sphere that encloses cylinder. */ private static void boundingSphereTriangle(double x0, double y0, double z0, double x1, double y1, double z1, double x2, double y2, double z2, double bs[]){ bs[0] = (x0 + x1 + x2)/3.0; bs[1] = (y0 + y1 + y2)/3.0; bs[2] = (z0 + z1 + z2)/3.0; double dmx = x0 - bs[0]; double dmy = y0 - bs[1]; double dmz = z0 - bs[2]; double r = 0.0; double rad = Math.sqrt(dmx*dmx + dmy*dmy + dmz*dmz); dmx = x1 - bs[0]; dmy = y1 - bs[1]; dmz = z1 - bs[2]; r = Math.sqrt(dmx*dmx + dmy*dmy + dmz*dmz); if(r > rad) rad = r; dmx = x2 - bs[0]; dmy = y2 - bs[1]; dmz = z2 - bs[2]; r = Math.sqrt(dmx*dmx + dmy*dmy + dmz*dmz); if(r > rad) rad = r; bs[3] = rad; } /** * Prepare a list of objects that may obscure points on _this_ sphere. * As we have scan line coherence, the cache list can be reused many * times for a given sphere. */ public static void prepareSphereCacheList(double sx, double sy, double sz, double sr, boolean transparent){ int sphereCount = scachex.size(); double x = sx * lightx.x + sy * lightx.y + sz * lightx.z; double y = sx * lighty.x + sy * lighty.y + sz * lighty.z; if(sphereCount > 0){ sphereOcclusionCacheList.clear(); sphereShadowCacheList.clear(); initialList.clear(); sphereGrid.getPossibleNeighbours(-1, x, y, sr + sphereGrid.getSpacing(), initialList, true); int initialSize = initialList.size(); for(int j = 0; j < initialSize; j++){ int i = initialList.getInt(j); // look up triangle center and radius double r = sr + scr[i]; double s2px = sx - scx[i]; double s2py = sy - scy[i]; double s2pz = sz - scz[i]; double dot = s2px*light.x + s2py*light.y + s2pz*light.z; if(dot <= 0.0){ // could shadow us // project intersphere vector onto 2d coordinate frame double projx = lightx.x * s2px + lightx.y * s2py + lightx.z * s2pz; double projy = lighty.x * s2px + lighty.y * s2py + lighty.z * s2pz; if(projx < r && projy < r && projx * projx + projy * projy < r * r){ // ok, it was between us and the light // and the sphere perimeters overlap sphereShadowCacheList.add(i); } } } } // prepare the cylinder overlap int cylinderCount = ccachex0.size(); if(cylinderCount > 0){ cylinderShadowCacheList.clear(); initialList.clear(); cylinderGrid.getPossibleNeighbours(-1, x, y, sr + cylinderGrid.getSpacing(), initialList, true); int initialSize = initialList.size(); for(int j = 0; j < initialSize; j++){ int i = initialList.getInt(j); boundingSphereCylinder(ccachex0.getFloat(i), ccachey0.getFloat(i), ccachez0.getFloat(i), ccachex1.getFloat(i), ccachey1.getFloat(i), ccachez1.getFloat(i), ccacher.getFloat(i), cbs); double r = sr + cbs[3]; double r2 = r * r; double s2px = sx - cbs[0]; double s2py = sy - cbs[1]; double s2pz = sz - cbs[2]; double dot = s2px*light.x + s2py*light.y + s2pz*light.z; if(dot <= 0.0){ // could shadow us // project intersphere vector onto 2d coordinate frame double projx = lightx.x * s2px + lightx.y * s2py + lightx.z * s2pz; double projy = lighty.x * s2px + lighty.y * s2py + lighty.z * s2pz; if(projx < r && projy < r && projx * projx + projy * projy < r2){ // ok, it was between us and the light // and the sphere perimeters overlap cylinderShadowCacheList.add(i); } } } } // two stages // first get spheres that could possibly overlap in the // grid structure // two remove those that don't overlap if(tcachex0.size() > 0){ triangleShadowCacheList.clear(); initialList.clear(); triangleGrid.getPossibleNeighbours(-1, x, y, sr + triangleGrid.getSpacing(), initialList, true); int initialSize = initialList.size(); for(int j = 0; j < initialSize; j++){ int i = initialList.getInt(j); // look up triangle center and radius double r = sr + tcr[i]; double s2px = sx - tcx[i]; double s2py = sy - tcy[i]; double s2pz = sz - tcz[i]; double dot = s2px*light.x + s2py*light.y + s2pz*light.z; if(dot <= 0.0 && (Math.abs(s2px) > 1.e-3 || Math.abs(s2py) > 1.e-3 || Math.abs(s2pz) > 1.e-3)){ // could shadow us // project intersphere vector onto 2d coordinate frame double projx = lightx.x * s2px + lightx.y * s2py + lightx.z * s2pz; double projy = lighty.x * s2px + lighty.y * s2py + lighty.z * s2pz; if(projx < r && projy < r && projx * projx + projy * projy < r * r){ // ok, it was between us and the light // and the sphere perimeters overlap triangleShadowCacheList.add(i); } } } } } /** Add a sphere to the cache list. */ public static void addSphereToCacheList(double x, double y, double z, double r){ scachex.add((float)x); scachey.add((float)y); scachez.add((float)z); scacher.add((float)r); } /** Add a cylinder to the cache list. */ public static void addCylinderToCacheList(double x0, double y0, double z0, double x1, double y1, double z1, double r){ ccachex0.add((float)x0); ccachey0.add((float)y0); ccachez0.add((float)z0); ccachex1.add((float)x1); ccachey1.add((float)y1); ccachez1.add((float)z1); ccacher.add((float)r); } /** Add a triangle to the cache list. */ public static void addTriangleToCacheList(double x0, double y0, double z0, double x1, double y1, double z1, double x2, double y2, double z2, int transparency){ tcachex0.add((float)x0); tcachey0.add((float)y0); tcachez0.add((float)z0); tcachex1.add((float)x1); tcachey1.add((float)y1); tcachez1.add((float)z1); tcachex2.add((float)x2); tcachey2.add((float)y2); tcachez2.add((float)z2); } /** Cylinder endpoints */ private static double c0[] = new double[3]; private static double c1[] = new double[3]; private static double c2[] = new double[3]; /** ray endpoints */ private static double ray0[] = new double[3]; private static double ray1[] = new double[3]; /** Intersection points. */ private static double rint[] = new double[3]; private static double nint[] = new double[3]; /** Intersection parameters for triangle. */ private static double tuv[] = new double[3]; /** * Is the surface at this point self shadowing. * i.e. does the normal point away from the light. */ public static boolean selfShadowed(double nx, double ny, double nz, double tol){ if(nx*light.x + ny*light.y + nz*light.z < tol){ return true; } return false; } /** project surface point. */ private static double px = 0.0; private static double py = 0.0; /** Is this point shadowed by stuff in the shadow cache. */ public static boolean pointShadowed(double x, double y, double z){ int sphereCacheCount = sphereShadowCacheList.size(); // shift point towards light to handle // self intersections more gracefully. x += 1. * light.x; y += 1. * light.y; z += 1. * light.z; // point on light coordinate system px = lightx.x * x + lightx.y * y + lightx.z * z; py = lighty.x * x + lighty.y * y + lighty.z * z; ray0[0] = x; ray0[1] = y; ray0[2] = z; ray1[0] = x + 100000. * light.x; ray1[1] = y + 100000. * light.y; ray1[2] = z + 100000. * light.z; // check caches if(lastObscuringSphere != -1){ if(obscuredBySphere(lastObscuringSphere, x, y, z)){ return true; } lastObscuringSphere = -1; } if(lastObscuringCylinder != -1){ if(obscuredByCylinder(lastObscuringCylinder)){ return true; } lastObscuringCylinder = -1; } int slist[] = sphereShadowCacheList.toIntArray(); // ok now check lists for(int j = 0; j < sphereCacheCount; j++){ int i = slist[j]; if(obscuredBySphere(i, x, y, z)){ lastObscuringSphere = i; return true; } } int cylinderCacheCount = cylinderShadowCacheList.size(); for(int j = 0; j < cylinderCacheCount; j++){ int i = cylinderShadowCacheList.getInt(j); if(obscuredByCylinder(i)){ lastObscuringCylinder = i; return true; } } light.normalize(); //reform ray1 as the direction to the light ray1[0] = light.x; ray1[1] = light.y; ray1[2] = light.z; // check last triangle, if not still obscuring clear it if(lastObscuringTriangle != -1){ if(obscuredByTriangle(lastObscuringTriangle)){ return true; } lastObscuringTriangle = -1; } int triangleCacheCount = triangleShadowCacheList.size(); for(int j = 0; j < triangleCacheCount; j++){ int i = triangleShadowCacheList.getInt(j); if(obscuredByTriangle(i)){ lastObscuringTriangle = i; return true; } } return false; } private static int lastObscuringTriangle = -1; private static boolean obscuredByTriangle(int i){ double dx = px - tc2x[i]; double dy = py - tc2y[i]; double r = tcr[i]; if(dx*dx + dy*dy > r*r){ return false; } c0[0] = tx0[i]; c0[1] = ty0[i]; c0[2] = tz0[i]; c1[0] = tx1[i]; c1[1] = ty1[i]; c1[2] = tz1[i]; c2[0] = tx2[i]; c2[1] = ty2[i]; c2[2] = tz2[i]; if(intersect_triangle(ray0, ray1, c0, c1, c2, tuv) == 1 && tuv[0] >= 0.0){ return true; } return false; } private static int lastObscuringSphere = -1; private static boolean obscuredBySphere(int i, double x, double y, double z){ double s2px = x - scx[i]; double s2py = y - scy[i]; double s2pz = z - scz[i]; double dot = s2px * light.x + s2py * light.y + s2pz * light.z; if(dot < 0.0){ // this sphere is between us and infinite light. double projx = lightx.x * s2px + lightx.y * s2py + lightx.z * s2pz; double r = scr[i]; if(projx < r){ double projy = lighty.x * s2px + lighty.y * s2py + lighty.z * s2pz; if(projy < r && projx * projx + projy * projy < r * r){ return true; } } } return false; } private static int lastObscuringCylinder = -1; private static boolean obscuredByCylinder(int i){ c0[0] = ccachex0.getFloat(i); c0[1] = ccachey0.getFloat(i); c0[2] = ccachez0.getFloat(i); c1[0] = ccachex1.getFloat(i); c1[1] = ccachey1.getFloat(i); c1[2] = ccachez1.getFloat(i); if(astex.anasurface.AnaSurface.intersect(ray0, ray1, c0, c1, rint, nint) < ccacher.getFloat(i)){ return true; } return false; } /** Clear out the shadow data structures. */ public static void clearShadowCaches(){ scachex.clear(); scachey.clear(); scachez.clear(); scacher.clear(); ccachex0.clear(); ccachey0.clear(); ccachez0.clear(); ccachex1.clear(); ccachey1.clear(); ccachez1.clear(); ccacher.clear(); tcachex0.clear(); tcachey0.clear(); tcachez0.clear(); tcachex1.clear(); tcachey1.clear(); tcachez1.clear(); tcachex2.clear(); tcachey2.clear(); tcachez2.clear(); tcen2dx.clear(); tcen2dy.clear(); tcenx.clear(); tceny.clear(); tcenz.clear(); tcenr.clear(); } private static Point3d light = new Point3d(); private static Point3d lightx = null; private static Point3d lighty = null; /** Set up the shadow cache data structures for rendering. */ public static void setupShadowCaches(Light l0, double ovs){ light.x = l0.pos[0]; // light y needs to be negative to correct for // on screen orientation light.y = -l0.pos[1]; light.z = l0.pos[2]; light.normalize(); lightx = Point3d.normalToLine(light); lightx.normalize(); lighty = lightx.cross(light); lighty.normalize(); tx0 = tcachex0.toFloatArray(); ty0 = tcachey0.toFloatArray(); tz0 = tcachez0.toFloatArray(); tx1 = tcachex1.toFloatArray(); ty1 = tcachey1.toFloatArray(); tz1 = tcachez1.toFloatArray(); tx2 = tcachex2.toFloatArray(); ty2 = tcachey2.toFloatArray(); tz2 = tcachez2.toFloatArray(); prepareTriangleGrid(); prepareSphereGrid(); prepareCylinderGrid(); lastObscuringSphere = -1; lastObscuringCylinder = -1; lastObscuringTriangle = -1; overallScale = ovs; tc2x = tcen2dx.toFloatArray(); tc2y = tcen2dy.toFloatArray(); tcx = tcenx.toFloatArray(); tcy = tceny.toFloatArray(); tcz = tcenz.toFloatArray(); tcr = tcenr.toFloatArray(); scx = scachex.toFloatArray(); scy = scachey.toFloatArray(); scz = scachez.toFloatArray(); scr = scacher.toFloatArray(); } private static NeighbourGrid2D triangleGrid = new NeighbourGrid2D(); private static NeighbourGrid2D sphereGrid = new NeighbourGrid2D(); private static NeighbourGrid2D cylinderGrid = new NeighbourGrid2D(); private static void prepareTriangleGrid(){ int triangleCount = tcachex0.size(); if(triangleCount == 0) return; double xmin = 1.e10; double ymin = 1.e10; double xmax = -1.e10; double ymax = -1.e10; double rmax = 0.0; for(int i = 0; i < triangleCount; i++){ boundingSphereTriangle(tx0[i], ty0[i], tz0[i], tx1[i], ty1[i], tz1[i], tx2[i], ty2[i], tz2[i], cbs); // capture centre tcenx.add((float)cbs[0]); tceny.add((float)cbs[1]); tcenz.add((float)cbs[2]); tcenr.add((float)cbs[3]); // project tri center onto light orthonormal set double x = cbs[0] * lightx.x + cbs[1] * lightx.y + cbs[2] * lightx.z; double y = cbs[0] * lighty.x + cbs[1] * lighty.y + cbs[2] * lighty.z; // capture projected centre tcen2dx.add((float)x); tcen2dy.add((float)y); // can do this as we add triangles in principle // gather extents if(x < xmin) xmin = x; if(x > xmax) xmax = x; if(y < ymin) ymin = y; if(y > ymax) ymax = y; if(cbs[3] > rmax) rmax = cbs[3]; } // make the bounding box for the grid. triangleGrid.reset(xmin - 0.1, ymin - 0.1, xmax + 0.1, ymax + 0.1, 1.01 * rmax); tcx = tcen2dx.toFloatArray(); tcy = tcen2dy.toFloatArray(); for(int i = 0; i < triangleCount; i++){ triangleGrid.add(i, tcx[i], tcy[i]); } } private static void prepareSphereGrid(){ int sphereCount = scachex.size(); if(sphereCount == 0) return; double xmin = 1.e10; double ymin = 1.e10; double xmax = -1.e10; double ymax = -1.e10; double rmax = 0.0; scx = scachex.toFloatArray(); scy = scachey.toFloatArray(); scz = scachez.toFloatArray(); scr = scacher.toFloatArray(); for(int i = 0; i < sphereCount; i++){ // project tri center onto light orthonormal set double x = scx[i] * lightx.x + scy[i] * lightx.y + scz[i] * lightx.z; double y = scx[i] * lighty.x + scy[i] * lighty.y + scz[i] * lighty.z; // can do this as we add triangles in principle // gather extents if(x < xmin) xmin = x; if(x > xmax) xmax = x; if(y < ymin) ymin = y; if(y > ymax) ymax = y; if(scr[i] > rmax) rmax = scr[i]; } // make the bounding box for the grid. sphereGrid.reset(xmin - 0.1, ymin - 0.1, xmax + 0.1, ymax + 0.1, 1.01 * rmax); for(int i = 0; i < sphereCount; i++){ // project tri center onto light orthonormal set double x = scx[i] * lightx.x + scy[i] * lightx.y + scz[i] * lightx.z; double y = scx[i] * lighty.x + scy[i] * lighty.y + scz[i] * lighty.z; sphereGrid.add(i, x, y); } } private static void prepareCylinderGrid(){ int cylinderCount = ccachex0.size(); if(cylinderCount == 0) return; double xmin = 1.e10; double ymin = 1.e10; double xmax = -1.e10; double ymax = -1.e10; double rmax = 0.0; for(int i = 0; i < cylinderCount; i++){ boundingSphereCylinder(ccachex0.getFloat(i), ccachey0.getFloat(i), ccachez0.getFloat(i), ccachex1.getFloat(i), ccachey1.getFloat(i), ccachez1.getFloat(i), ccacher.getFloat(i), cbs); // project tri center onto light orthonormal set double x = cbs[0] * lightx.x + cbs[1] * lightx.y + cbs[2] * lightx.z; double y = cbs[0] * lighty.x + cbs[1] * lighty.y + cbs[2] * lighty.z; // can do this as we add cylinders in principle // gather extents if(x < xmin) xmin = x; if(x > xmax) xmax = x; if(y < ymin) ymin = y; if(y > ymax) ymax = y; if(cbs[3] > rmax) rmax = cbs[3]; } // make the bounding box for the grid. cylinderGrid.reset(xmin - 0.1, ymin - 0.1, xmax + 0.1, ymax + 0.1, 1.01 * rmax); tcx = tcen2dx.toFloatArray(); tcy = tcen2dy.toFloatArray(); for(int i = 0; i < cylinderCount; i++){ boundingSphereCylinder(ccachex0.getFloat(i), ccachey0.getFloat(i), ccachez0.getFloat(i), ccachex1.getFloat(i), ccachey1.getFloat(i), ccachez1.getFloat(i), ccacher.getFloat(i), cbs); // project tri center onto light orthonormal set double x = cbs[0] * lightx.x + cbs[1] * lightx.y + cbs[2] * lightx.z; double y = cbs[0] * lighty.x + cbs[1] * lighty.y + cbs[2] * lighty.z; cylinderGrid.add(i, x, y); } } // Ray-triangle intersection // Tomas M�ller and Ben Trumbore. // Fast, minimum storage ray-triangle intersection. // Journal of graphics tools, 2(1):21-28, 1997 // Source code from // http://www.acm.org/jgt/papers/MollerTrumbore97/code.html private static final double EPSILON = 0.000001; private static void CROSS(double dest[], double v1[], double v2[]){ dest[0]=v1[1]*v2[2]-v1[2]*v2[1]; dest[1]=v1[2]*v2[0]-v1[0]*v2[2]; dest[2]=v1[0]*v2[1]-v1[1]*v2[0]; } private static double DOT(double v1[], double v2[]){ return v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2]; } private static void SUB(double dest[], double v1[], double v2[]){ dest[0]=v1[0]-v2[0]; dest[1]=v1[1]-v2[1]; dest[2]=v1[2]-v2[2]; } private static double edge1[] = new double[3]; private static double edge2[] = new double[3]; private static double tvec[] = new double[3]; private static double pvec[] = new double[3]; private static double qvec[] = new double[3]; public static int intersect_triangle(double orig[], double dir[], double vert0[], double vert1[], double vert2[], double tuv[]){ double det, inv_det; // find vectors for two edges sharing vert0 SUB(edge1, vert1, vert0); SUB(edge2, vert2, vert0); // begin calculating determinant - also used to calculate U parameter CROSS(pvec, dir, edge2); // if determinant is near zero, ray lies in plane of triangle det = DOT(edge1, pvec); if (det > -EPSILON && det < EPSILON) return 0; inv_det = 1.0 / det; // calculate distance from vert0 to ray origin SUB(tvec, orig, vert0); // calculate U parameter and test bounds tuv[1] = DOT(tvec, pvec) * inv_det; if (tuv[1] < 0.0 || tuv[1] > 1.0) return 0; // prepare to test V parameter CROSS(qvec, tvec, edge1); // calculate V parameter and test bounds tuv[2] = DOT(dir, qvec) * inv_det; if (tuv[2] < 0.0 || tuv[1] + tuv[2] > 1.0) return 0; // calculate t, ray intersects triangle tuv[0] = DOT(edge2, qvec) * inv_det; return 1; } }