bool Intersect::sameSideOfPlane(const Vec3f& v1, const Vec3f& v2, const Vec3f& v3, const Vec3f& n, FCL_REAL t) { FCL_REAL dist1 = distanceToPlane(n, t, v1); FCL_REAL dist2 = dist1 * distanceToPlane(n, t, v2); FCL_REAL dist3 = dist1 * distanceToPlane(n, t, v3); if((dist2 > 0) && (dist3 > 0)) return true; return false; }
void Intersect::clipSegmentByPlane(const Vec3f& v1, const Vec3f& v2, const Vec3f& n, FCL_REAL t, Vec3f* clipped_point) { FCL_REAL dist1 = distanceToPlane(n, t, v1); Vec3f tmp = v2 - v1; FCL_REAL dist2 = tmp.dot(n); *clipped_point = tmp * (-dist1 / dist2) + v1; }
bool reduceSphereToPlane(const glm::vec4& sphere, const glm::vec4& plane, glm::vec4& reducedSphere) { float distance = distanceToPlane(glm::vec3(sphere), plane); if (std::abs(distance) <= sphere.w) { reducedSphere = glm::vec4(sphere.x - distance * plane.x, sphere.y - distance * plane.y, sphere.z - distance * plane.z, sqrt(sphere.w * sphere.w - distance * distance)); return true; } return false; }
/** * Check that the point is on the plane AND that it is inside or on@brief * MeshObject2D::isOnSide one of the triangles that defines the plane. Both must * be true. * @param point : Point to test * @return : True only if the point is valid */ bool MeshObject2D::isValid(const Kernel::V3D &point) const { static const double tolerance = 1e-9; if (distanceToPlane(point) < tolerance) { for (size_t i = 0; i < m_vertices.size(); i += 3) { if (MeshObjectCommon::isOnTriangle(point, m_vertices[i], m_vertices[i + 1], m_vertices[i + 2])) return true; } } return false; }
Point getPointOnPlane(Point point, Plane plane){ Point pointOnPlane; double dist = distanceToPlane(point, plane); Vector normal = plane.normal; Vector v; v.dx = dist*normal.dx; v.dy = dist*normal.dy; v.dz = dist*normal.dz; pointOnPlane.x = point.x - v.dx; pointOnPlane.y = point.y - v.dy; pointOnPlane.z = point.z - v.dz; return pointOnPlane; }
void Intersect::computeDeepestPoints(Vec3f* clipped_points, unsigned int num_clipped_points, const Vec3f& n, FCL_REAL t, FCL_REAL* penetration_depth, Vec3f* deepest_points, unsigned int* num_deepest_points) { *num_deepest_points = 0; FCL_REAL max_depth = -std::numeric_limits<FCL_REAL>::max(); unsigned int num_deepest_points_ = 0; unsigned int num_neg = 0; unsigned int num_pos = 0; unsigned int num_zero = 0; for(unsigned int i = 0; i < num_clipped_points; ++i) { FCL_REAL dist = -distanceToPlane(n, t, clipped_points[i]); if(dist > EPSILON) num_pos++; else if(dist < -EPSILON) num_neg++; else num_zero++; if(dist > max_depth) { max_depth = dist; num_deepest_points_ = 1; deepest_points[num_deepest_points_ - 1] = clipped_points[i]; } else if(dist + 1e-6 >= max_depth) { num_deepest_points_++; deepest_points[num_deepest_points_ - 1] = clipped_points[i]; } } if(max_depth < -EPSILON) num_deepest_points_ = 0; if(num_zero == 0 && ((num_neg == 0) || (num_pos == 0))) num_deepest_points_ = 0; *penetration_depth = max_depth; *num_deepest_points = num_deepest_points_; }
double Vector3D::distanceToPlane(const Vector3D &plane1, const Vector3D &plane2, const Vector3D &plane3) const { Vector3D normal = Vector3D::normal(plane1,plane2,plane3); return distanceToPlane(plane1, normal); }
void Intersect::clipPolygonByPlane(Vec3f* polygon_points, unsigned int num_polygon_points, const Vec3f& n, FCL_REAL t, Vec3f clipped_points[], unsigned int* num_clipped_points) { *num_clipped_points = 0; unsigned int num_clipped_points_ = 0; unsigned int vi; unsigned int prev_classify = 2; unsigned int classify; for(unsigned int i = 0; i <= num_polygon_points; ++i) { vi = (i % num_polygon_points); FCL_REAL d = distanceToPlane(n, t, polygon_points[i]); classify = ((d > EPSILON) ? 1 : 0); if(classify == 0) { if(prev_classify == 1) { if(num_clipped_points_ < MAX_TRIANGLE_CLIPS) { Vec3f tmp; clipSegmentByPlane(polygon_points[i - 1], polygon_points[vi], n, t, &tmp); if(num_clipped_points_ > 0) { if((tmp - clipped_points[num_clipped_points_ - 1]).sqrLength() > EPSILON) { clipped_points[num_clipped_points_] = tmp; num_clipped_points_++; } } else { clipped_points[num_clipped_points_] = tmp; num_clipped_points_++; } } } if(num_clipped_points_ < MAX_TRIANGLE_CLIPS && i < num_polygon_points) { clipped_points[num_clipped_points_] = polygon_points[vi]; num_clipped_points_++; } } else { if(prev_classify == 0) { if(num_clipped_points_ < MAX_TRIANGLE_CLIPS) { Vec3f tmp; clipSegmentByPlane(polygon_points[i - 1], polygon_points[vi], n, t, &tmp); if(num_clipped_points_ > 0) { if((tmp - clipped_points[num_clipped_points_ - 1]).sqrLength() > EPSILON) { clipped_points[num_clipped_points_] = tmp; num_clipped_points_++; } } else { clipped_points[num_clipped_points_] = tmp; num_clipped_points_++; } } } } prev_classify = classify; } if(num_clipped_points_ > 2) { if((clipped_points[0] - clipped_points[num_clipped_points_ - 1]).sqrLength() < EPSILON) { num_clipped_points_--; } } *num_clipped_points = num_clipped_points_; }
glm::ivec3 LightClusters::updateClusters() { // Make sure resource are in good shape updateClusterResource(); // Clean up last info uint32_t numClusters = (uint32_t)_clusterGrid.size(); std::vector< std::vector< LightIndex > > clusterGridPoint(numClusters); std::vector< std::vector< LightIndex > > clusterGridSpot(numClusters); _clusterGrid.clear(); _clusterGrid.resize(numClusters, EMPTY_CLUSTER); uint32_t maxNumIndices = (uint32_t)_clusterContent.size(); _clusterContent.clear(); _clusterContent.resize(maxNumIndices, INVALID_LIGHT); auto theFrustumGrid(_frustumGridBuffer.get()); glm::ivec3 gridPosToOffset(1, theFrustumGrid.dims.x, theFrustumGrid.dims.x * theFrustumGrid.dims.y); uint32_t numClusterTouched = 0; uint32_t numLightsIn = _visibleLightIndices[0]; uint32_t numClusteredLights = 0; for (size_t lightNum = 1; lightNum < _visibleLightIndices.size(); ++lightNum) { auto lightId = _visibleLightIndices[lightNum]; auto light = _lightStage->getLight(lightId); if (!light) { continue; } auto worldOri = light->getPosition(); auto radius = light->getMaximumRadius(); bool isSpot = light->isSpot(); // Bring into frustum eye space auto eyeOri = theFrustumGrid.frustumGrid_worldToEye(glm::vec4(worldOri, 1.0f)); // Remove light that slipped through and is not in the z range float eyeZMax = eyeOri.z - radius; if (eyeZMax > -theFrustumGrid.rangeNear) { continue; } float eyeZMin = eyeOri.z + radius; bool beyondFar = false; if (eyeZMin < -theFrustumGrid.rangeFar) { beyondFar = true; } // Get z slices int zMin = theFrustumGrid.frustumGrid_eyeDepthToClusterLayer(eyeZMin); int zMax = theFrustumGrid.frustumGrid_eyeDepthToClusterLayer(eyeZMax); // That should never happen if (zMin == -2 && zMax == -2) { continue; } // Before Range NEar just apss, range neatr == true near for now if ((zMin == -1) && (zMax == -1)) { continue; } // CLamp the z range zMin = std::max(0, zMin); auto xLeftDistance = radius - distanceToPlane(eyeOri, _gridPlanes[0][0]); auto xRightDistance = radius + distanceToPlane(eyeOri, _gridPlanes[0].back()); auto yBottomDistance = radius - distanceToPlane(eyeOri, _gridPlanes[1][0]); auto yTopDistance = radius + distanceToPlane(eyeOri, _gridPlanes[1].back()); if ((xLeftDistance < 0.f) || (xRightDistance < 0.f) || (yBottomDistance < 0.f) || (yTopDistance < 0.f)) { continue; } // find 2D corners of the sphere in grid int xMin { 0 }; int xMax { theFrustumGrid.dims.x - 1 }; int yMin { 0 }; int yMax { theFrustumGrid.dims.y - 1 }; float radius2 = radius * radius; auto eyeOriH = glm::vec3(eyeOri); auto eyeOriV = glm::vec3(eyeOri); eyeOriH.y = 0.0f; eyeOriV.x = 0.0f; float eyeOriLen2H = glm::length2(eyeOriH); float eyeOriLen2V = glm::length2(eyeOriV); if ((eyeOriLen2H > radius2)) { float eyeOriLenH = sqrt(eyeOriLen2H); auto eyeOriDirH = glm::vec3(eyeOriH) / eyeOriLenH; float eyeToTangentCircleLenH = sqrt(eyeOriLen2H - radius2); float eyeToTangentCircleCosH = eyeToTangentCircleLenH / eyeOriLenH; float eyeToTangentCircleSinH = radius / eyeOriLenH; // rotate the eyeToOriDir (H & V) in both directions glm::vec3 leftDir(eyeOriDirH.x * eyeToTangentCircleCosH + eyeOriDirH.z * eyeToTangentCircleSinH, 0.0f, eyeOriDirH.x * -eyeToTangentCircleSinH + eyeOriDirH.z * eyeToTangentCircleCosH); glm::vec3 rightDir(eyeOriDirH.x * eyeToTangentCircleCosH - eyeOriDirH.z * eyeToTangentCircleSinH, 0.0f, eyeOriDirH.x * eyeToTangentCircleSinH + eyeOriDirH.z * eyeToTangentCircleCosH); auto lc = theFrustumGrid.frustumGrid_eyeToClusterDirH(leftDir); if (lc > xMax) { lc = xMin; } auto rc = theFrustumGrid.frustumGrid_eyeToClusterDirH(rightDir); if (rc < 0) { rc = xMax; } xMin = std::max(xMin, lc); xMax = std::min(rc, xMax); assert(xMin <= xMax); } if ((eyeOriLen2V > radius2)) { float eyeOriLenV = sqrt(eyeOriLen2V); auto eyeOriDirV = glm::vec3(eyeOriV) / eyeOriLenV; float eyeToTangentCircleLenV = sqrt(eyeOriLen2V - radius2); float eyeToTangentCircleCosV = eyeToTangentCircleLenV / eyeOriLenV; float eyeToTangentCircleSinV = radius / eyeOriLenV; // rotate the eyeToOriDir (H & V) in both directions glm::vec3 bottomDir(0.0f, eyeOriDirV.y * eyeToTangentCircleCosV + eyeOriDirV.z * eyeToTangentCircleSinV, eyeOriDirV.y * -eyeToTangentCircleSinV + eyeOriDirV.z * eyeToTangentCircleCosV); glm::vec3 topDir(0.0f, eyeOriDirV.y * eyeToTangentCircleCosV - eyeOriDirV.z * eyeToTangentCircleSinV, eyeOriDirV.y * eyeToTangentCircleSinV + eyeOriDirV.z * eyeToTangentCircleCosV); auto bc = theFrustumGrid.frustumGrid_eyeToClusterDirV(bottomDir); auto tc = theFrustumGrid.frustumGrid_eyeToClusterDirV(topDir); if (bc > yMax) { bc = yMin; } if (tc < 0) { tc = yMax; } yMin = std::max(yMin, bc); yMax =std::min(tc, yMax); assert(yMin <= yMax); } // now voxelize auto& clusterGrid = (isSpot ? clusterGridSpot : clusterGridPoint); if (beyondFar) { numClusterTouched += scanLightVolumeBoxSlice(theFrustumGrid, _gridPlanes, zMin, yMin, yMax, xMin, xMax, lightId, glm::vec4(glm::vec3(eyeOri), radius), clusterGrid); } else { numClusterTouched += scanLightVolumeSphere(theFrustumGrid, _gridPlanes, zMin, zMax, yMin, yMax, xMin, xMax, lightId, glm::vec4(glm::vec3(eyeOri), radius), clusterGrid); } numClusteredLights++; } // Lights have been gathered now reexpress in terms of 2 sequential buffers // Start filling from near to far and stops if it overflows bool checkBudget = false; if (numClusterTouched > maxNumIndices) { checkBudget = true; } uint16_t indexOffset = 0; for (int i = 0; i < (int) clusterGridPoint.size(); i++) { auto& clusterPoint = clusterGridPoint[i]; auto& clusterSpot = clusterGridSpot[i]; uint8_t numLightsPoint = ((uint8_t)clusterPoint.size()); uint8_t numLightsSpot = ((uint8_t)clusterSpot.size()); uint16_t numLights = numLightsPoint + numLightsSpot; uint16_t offset = indexOffset; // Check for overflow if (checkBudget) { if ((indexOffset + numLights) > (uint16_t) maxNumIndices) { break; } } // Encode the cluster grid: [ ContentOffset - 16bits, Num Point LIghts - 8bits, Num Spot Lights - 8bits] _clusterGrid[i] = (uint32_t)((0xFF000000 & (numLightsSpot << 24)) | (0x00FF0000 & (numLightsPoint << 16)) | (0x0000FFFF & offset)); if (numLightsPoint) { memcpy(_clusterContent.data() + indexOffset, clusterPoint.data(), numLightsPoint * sizeof(LightIndex)); indexOffset += numLightsPoint; } if (numLightsSpot) { memcpy(_clusterContent.data() + indexOffset, clusterSpot.data(), numLightsSpot * sizeof(LightIndex)); indexOffset += numLightsSpot; } } // update the buffers _clusterGridBuffer._buffer->setData(_clusterGridBuffer._size, (gpu::Byte*) _clusterGrid.data()); _clusterContentBuffer._buffer->setSubData(0, indexOffset * sizeof(LightIndex), (gpu::Byte*) _clusterContent.data()); return glm::ivec3(numLightsIn, numClusteredLights, numClusterTouched); }
uint32_t scanLightVolumeSphere(FrustumGrid& grid, const FrustumGrid::Planes planes[3], int zMin, int zMax, int yMin, int yMax, int xMin, int xMax, LightClusters::LightID lightId, const glm::vec4& eyePosRadius, std::vector< std::vector<LightClusters::LightIndex>>& clusterGrid) { glm::ivec3 gridPosToOffset(1, grid.dims.x, grid.dims.x * grid.dims.y); uint32_t numClustersTouched = 0; const auto& xPlanes = planes[0]; const auto& yPlanes = planes[1]; const auto& zPlanes = planes[2]; // FInd the light origin cluster auto centerCluster = grid.frustumGrid_eyeToClusterPos(glm::vec3(eyePosRadius)); int center_z = centerCluster.z; int center_y = centerCluster.y; for (auto z = zMin; (z <= zMax); z++) { auto zSphere = eyePosRadius; if (z != center_z) { auto plane = (z < center_z) ? zPlanes[z + 1] : -zPlanes[z]; if (!reduceSphereToPlane(zSphere, plane, zSphere)) { // pass this slice! continue; } } for (auto y = yMin; (y <= yMax); y++) { auto ySphere = zSphere; if (y != center_y) { auto plane = (y < center_y) ? yPlanes[y + 1] : -yPlanes[y]; if (!reduceSphereToPlane(ySphere, plane, ySphere)) { // pass this slice! continue; } } glm::vec3 spherePoint(ySphere); auto x = xMin; for (; (x < xMax); ++x) { const auto& plane = xPlanes[x + 1]; auto testDistance = distanceToPlane(spherePoint, plane) + ySphere.w; if (testDistance >= 0.0f) { break; } } auto xs = xMax; for (; (xs >= x); --xs) { auto plane = -xPlanes[xs]; auto testDistance = distanceToPlane(spherePoint, plane) + ySphere.w; if (testDistance >= 0.0f) { break; } } for (; (x <= xs); x++) { auto index = grid.frustumGrid_clusterToIndex(ivec3(x, y, z)); if (index < (int)clusterGrid.size()) { clusterGrid[index].emplace_back(lightId); numClustersTouched++; } else { qCDebug(renderutils) << "WARNING: LightClusters::scanLightVolumeSphere invalid index found ? numClusters = " << clusterGrid.size() << " index = " << index << " found from cluster xyz = " << x << " " << y << " " << z; } } } } return numClustersTouched; }
campvis::FaceGeometry FaceGeometry::clipAgainstPlane(float p, const cgt::vec3& pNormal, float epsilon /*= 1e-4f*/) const { cgtAssert(epsilon >= 0, "Epsilon must be positive."); std::vector<cgt::vec3> verts, texCoords, norms; std::vector<cgt::vec4> cols; std::vector<cgt::col4> picks; size_t lastIndex = _vertices.size() - 1; float lastDistance = distanceToPlane(_vertices.back(), p, pNormal, epsilon); // Implementation of Sutherland-Hodgman polygon clipping: for (size_t i = 0; i < _vertices.size(); ++i) { float currrentDistance = distanceToPlane(_vertices[i], p, pNormal, epsilon); // case 1: last vertex outside, this vertex inside clip region => clip if (lastDistance > 0 && currrentDistance <= 0) { float t = lastDistance / (lastDistance - currrentDistance); verts.push_back(cgt::mix(_vertices[lastIndex], _vertices[i], t)); if (!_textureCoordinates.empty()) texCoords.push_back(cgt::mix(_textureCoordinates[lastIndex], _textureCoordinates[i], t)); if (!_colors.empty()) cols.push_back(cgt::mix(_colors[lastIndex], _colors[i], t)); if (!_normals.empty()) norms.push_back(cgt::mix(_normals[lastIndex], _normals[i], t)); if (!_pickingInformation.empty()) picks.push_back(_pickingInformation[i]); } // case 2: last vertex inside, this vertex outside clip region => clip else if (lastDistance <= 0 && currrentDistance > 0) { float t = lastDistance / (lastDistance - currrentDistance); verts.push_back(cgt::mix(_vertices[lastIndex], _vertices[i], t)); if (!_textureCoordinates.empty()) texCoords.push_back(cgt::mix(_textureCoordinates[lastIndex], _textureCoordinates[i], t)); if (!_colors.empty()) cols.push_back(cgt::mix(_colors[lastIndex], _colors[i], t)); if (!_normals.empty()) norms.push_back(cgt::mix(_normals[lastIndex], _normals[i], t)); if (!_pickingInformation.empty()) picks.push_back(_pickingInformation[lastIndex]); } // case 1.2 + case 3: current vertix in front of plane => keep if (currrentDistance <= 0) { verts.push_back(_vertices[i]); if (!_textureCoordinates.empty()) texCoords.push_back(_textureCoordinates[i]); if (!_colors.empty()) cols.push_back(_colors[i]); if (!_normals.empty()) norms.push_back(_normals[i]); if (!_pickingInformation.empty()) picks.push_back(_pickingInformation[i]); } lastIndex = i; lastDistance = currrentDistance; } FaceGeometry toReturn(verts, texCoords, cols, norms); toReturn.setPickingInformation(picks); return toReturn; }
void ntlTree::intersectX(const ntlRay &ray, gfxReal &distance, ntlVec3Gfx &normal, ntlTriangle *&tri, int flags, bool forceNonsmooth) const { gfxReal mint = GFX_REAL_MAX; /* current minimal t */ ntlVec3Gfx retnormal; /* intersection (interpolated) normal */ gfxReal mintu=0.0, mintv=0.0; /* u,v for min t intersection */ BSPNode *curr, *nearChild, *farChild; /* current node and children */ gfxReal planedist, mindist, maxdist; ntlVec3Gfx pos; ntlTriangle *hit = NULL; tri = NULL; ray.intersectCompleteAABB(mStart,mEnd,mindist,maxdist); // +X if((maxdist < 0.0) || (!mpRoot) || (mindist == GFX_REAL_MAX) || (maxdist == GFX_REAL_MAX) ) { distance = -1.0; return; } mindist -= getVecEpsilon(); maxdist += getVecEpsilon(); /* stack init */ mpNodeStack->elem[0].node = NULL; mpNodeStack->stackPtr = 1; curr = mpRoot; mint = GFX_REAL_MAX; while(curr != NULL) { // +X while( !curr->isLeaf() ) { planedist = distanceToPlane(curr, curr->child[0]->max, ray ); getChildren(curr, ray.getOrigin(), nearChild, farChild ); // check ray direction for small plane distances if( (planedist>-getVecEpsilon() )&&(planedist< getVecEpsilon() ) ) { // ray origin on intersection plane planedist = 0.0; if(ray.getDirection()[curr->axis]>getVecEpsilon() ) { // larger coords curr = curr->child[1]; } else if(ray.getDirection()[curr->axis]<-getVecEpsilon() ) { // smaller coords curr = curr->child[0]; } else { // paralell, order doesnt really matter are min/max/plane ok? mpNodeStack->elem[ mpNodeStack->stackPtr ].node = curr->child[0]; mpNodeStack->elem[ mpNodeStack->stackPtr ].mindist = planedist; mpNodeStack->elem[ mpNodeStack->stackPtr ].maxdist = maxdist; (mpNodeStack->stackPtr)++; curr = curr->child[1]; maxdist = planedist; } } else { // normal ray if( (planedist>maxdist) || (planedist<0.0-getVecEpsilon() ) ) { curr = nearChild; } else if(planedist < mindist) { curr = farChild; } else { mpNodeStack->elem[ mpNodeStack->stackPtr ].node = farChild; mpNodeStack->elem[ mpNodeStack->stackPtr ].mindist = planedist; mpNodeStack->elem[ mpNodeStack->stackPtr ].maxdist = maxdist; (mpNodeStack->stackPtr)++; curr = nearChild; maxdist = planedist; } } } // +X /* intersect with current node */ for (vector<ntlTriangle *>::iterator iter = curr->members->begin(); iter != curr->members->end(); iter++ ) { /* check for triangle flags before intersecting */ if((!flags) || ( ((*iter)->getFlags() & flags) > 0 )) { if( ((*iter)->getLastRay() == ray.getID() )&&((*iter)->getLastRay()>0) ) { // was already intersected... } else { // we still need to intersect this triangle gfxReal u=0.0,v=0.0, t=-1.0; ray.intersectTriangleX( mpVertices, (*iter), t,u,v); (*iter)->setLastRay( ray.getID() ); if( (t > 0.0) && (t<mint) ) { mint = t; hit = (*iter); mintu = u; mintv = v; } } } // flags check } // +X /* check if intersection is valid */ if( (mint>0.0) && (mint < GFX_REAL_MAX) ) { pos = ray.getOrigin() + ray.getDirection()*mint; if( (pos[0] >= curr->min[0]) && (pos[0] <= curr->max[0]) && (pos[1] >= curr->min[1]) && (pos[1] <= curr->max[1]) && (pos[2] >= curr->min[2]) && (pos[2] <= curr->max[2]) ) { if(forceNonsmooth) { // calculate triangle normal ntlVec3Gfx e0,e1,e2; e0 = (*mpVertices)[ hit->getPoints()[0] ]; e1 = (*mpVertices)[ hit->getPoints()[1] ]; e2 = (*mpVertices)[ hit->getPoints()[2] ]; retnormal = cross( -(e2-e0), (e1-e0) ); } else { // calculate interpolated normal retnormal = (*mpVertNormals)[ hit->getPoints()[0] ] * (1.0-mintu-mintv)+ (*mpVertNormals)[ hit->getPoints()[1] ]*mintu + (*mpVertNormals)[ hit->getPoints()[2] ]*mintv; } normalize(retnormal); normal = retnormal; distance = mint; tri = hit; return; } } // +X (mpNodeStack->stackPtr)--; curr = mpNodeStack->elem[ mpNodeStack->stackPtr ].node; mindist = mpNodeStack->elem[ mpNodeStack->stackPtr ].mindist; maxdist = mpNodeStack->elem[ mpNodeStack->stackPtr ].maxdist; } /* traverse tree */ if(mint == GFX_REAL_MAX) { distance = -1.0; } else { // intersection outside the BSP bounding volumes might occur due to roundoff... if(forceNonsmooth) { // calculate triangle normal ntlVec3Gfx e0,e1,e2; e0 = (*mpVertices)[ hit->getPoints()[0] ]; e1 = (*mpVertices)[ hit->getPoints()[1] ]; e2 = (*mpVertices)[ hit->getPoints()[2] ]; retnormal = cross( -(e2-e0), (e1-e0) ); } else { // calculate interpolated normal retnormal = (*mpVertNormals)[ hit->getPoints()[0] ] * (1.0-mintu-mintv)+ (*mpVertNormals)[ hit->getPoints()[1] ]*mintu + (*mpVertNormals)[ hit->getPoints()[2] ]*mintv; } normalize(retnormal); normal = retnormal; distance = mint; tri = hit; } // +X return; }