bool CheckRay(const CRay& ray, const CBoundingBox& bb, Vector3* hitPoint) { //This algorithm is adapted from: http://tavianator.com/fast-branchless-raybounding-box-intersections/ //Its based on the "Slab Technique" which treats a bounding box as a series of 2 planes in each axis. //It finds where the ray lies on each of these planes and compares those values across each axis. float tmin = -INFINITY; float tmax = INFINITY; float tx1 = (bb.GetMin().x - ray.GetOrigin().x) / ray.GetDirection().x; float tx2 = (bb.GetMax().x - ray.GetOrigin().x) / ray.GetDirection().x; tmin = max(tmin, min(tx1, tx2)); tmax = min(tmax, max(tx1, tx2)); float ty1 = (bb.GetMin().y - ray.GetOrigin().y) / ray.GetDirection().y; float ty2 = (bb.GetMax().y - ray.GetOrigin().y) / ray.GetDirection().y; tmin = max(tmin, min(ty1, ty2)); tmax = min(tmax, max(ty1, ty2)); float tz1 = (bb.GetMin().z - ray.GetOrigin().z) / ray.GetDirection().z; float tz2 = (bb.GetMax().z - ray.GetOrigin().z) / ray.GetDirection().z; tmin = max(tmin, min(tz1, tz2)); tmax = min(tmax, max(tz1, tz2)); //We end up with two hit values just like the sphere, if the max hit is less than 0 then it all happened behind the ray origin if (tmax < 0) return false; //If the max hit value is greater than the min hit (as it should be) then we have a hit! if (tmax >= tmin) { if (hitPoint) { //If we need to know the first hit point on the box then we should use tmin, unless it's negative then we use tmax. //This would come up in situations where the origin of the ray is within the box. float firstHitDistance = tmin < 0 ? tmax : tmin; Vector3 pointOnBox = (ray.GetDirection() * firstHitDistance) + ray.GetOrigin(); hitPoint->x = pointOnBox.x; hitPoint->y = pointOnBox.y; hitPoint->z = pointOnBox.z; } return true; } return false; }
bool CDynamics3DEntity::CheckIntersectionWithRay(Real& f_t_on_ray, const CRay& c_ray) const { /* Create an ODE ray from ARGoS ray */ Real fRayLength = c_ray.GetLength(); dGeomID tRay = dCreateRay(m_cEngine.GetSpaceID(), fRayLength); CVector3 cDirection; c_ray.GetDirection(cDirection); dGeomRaySet(tRay, c_ray.GetStart().GetX(), c_ray.GetStart().GetY(), c_ray.GetStart().GetZ(), cDirection.GetX(), cDirection.GetY(), cDirection.GetZ()); /* Create the structure to contain info about the possible ray/geom intersection */ dContactGeom tIntersection; /* Check for intersection between the ray and the object local space */ if(dCollide(tRay, reinterpret_cast<dGeomID>(m_tEntitySpace), 1, &tIntersection, sizeof(dContactGeom)) > 0) { /* There is an intersecton */ f_t_on_ray = tIntersection.depth / fRayLength; return true; } else { /* No intersection detected */ return false; } }
bool CheckRay(const CRay& ray, const CBoundingSphere& sphere, Vector3* hitPoint) { //This algorithm is adatped from: http://www.cosinekitty.com/raytrace/chapter06_sphere.html //It breaks the problem down into the plane equations needed to find the hit points between the ray and sphere //To solve this correctly it makes use of a quadratic equation. Vector3 displacement = ray.GetOrigin() - sphere.GetCenter(); float a = ray.GetDirection().LengthSquared(); float b = 2.0f * displacement.Dot(ray.GetDirection()); float c = displacement.LengthSquared() - sphere.GetRadius() * sphere.GetRadius(); float randicand = b*b - 4.0f * a * c; //If the quadratic equation comes back as a negitive then there is no hit. if (randicand < 0.0f) { return false; } float root = sqrt(randicand); float denom = 2.0 * a; //Here we calculate the distance between ray origin and the two hit points (where the ray enters the sphere and where it exits) float hit1 = (-b + root) / denom; float hit2 = (-b - root) / denom; //If both of the hits are negitive then it means that the sphere is behind the origin of the ray so there is no hit if (hit1 < 0 && hit2 < 0) { return false; } if (hitPoint) { //If we need to know the first hit point on the sphere then we should use hit1, unless it's negative then we use hit2. //This would come up in situations where the origin of the ray is within the sphere. float firstHitDistance = hit1 < 0 ? hit2 : hit1; Vector3 pointOnSphere = (ray.GetDirection() * firstHitDistance) + ray.GetOrigin(); hitPoint->x = pointOnSphere.x; hitPoint->y = pointOnSphere.y; hitPoint->z = pointOnSphere.z; } return true; }
bool CPlane::Hit(const CRay &ray, SRayIntersection &intersection) const { // Величина, меньше которой модуль скалярного произведения вектора направления луча и // нормали плоскости означает параллельность луча и плоскости const float EPSILON = std::numeric_limits<float>::epsilon(); // Нормаль к плоскости в системе координат объекта const glm::vec3 normalInObjectSpace(m_planeEquation); // Скалярное произведение направления луча и нормали к плоскости const float normalDotDirection = glm::dot(ray.GetDirection(), normalInObjectSpace); // Если скалярное произведение близко к нулю, луч параллелен плоскости if (fabs(normalDotDirection) < EPSILON) { return false; } /* Находим время пересечения луча с плоскостью, подставляя в уравнение плоскости точку испускания луча и деление результата на ранее вычисленное сканярное произведение направления луча и нормали к плоскости */ const float hitTime = -glm::dot(glm::vec4(ray.GetStart(), 1), m_planeEquation) / normalDotDirection; // Нас интересует только пересечение луча с плоскостью в положительный момент времени, // поэтому находящуюся "позади" точки испускания луча точку пересечения мы за точку пересечения не считаем // Сравнение с величиной EPSILON, а не с 0 нужно для того, чтобы позволить // лучам, испущенным с плоскости, оторваться от нее. // Это необходимо при обработке вторичных лучей для построения теней и отражений и преломлений if (hitTime <= EPSILON) { return false; } // Вычисляем точку столкновения с лучом в системе координат сцены в момент столкновения const glm::vec3 hitPoint = ray.GetPointAtTime(hitTime); intersection.m_time = hitTime; intersection.m_point = hitPoint; return true; }
bool CTriangleMesh::Hit(CRay const& ray, CIntersection & intersection) const { // Вычисляем обратно преобразованный луч (вместо вполнения прямого преобразования объекта) CRay invRay = Transform(ray, GetInverseTransform()); CVector3d const& invRayStart = invRay.GetStart(); CVector3d const& invRayDirection = invRay.GetDirection(); ////////////////////////////////////////////////////////////////////////// // Здесь следует выполнить проверку на пересечение луча с ограничивающим // объемом (bounding volume) полигональной сетки. // При отсутствии такого пересечения луч гарантированно не будет пересекать // ни одну из граней сетки, что позволит избежать лишних проверок: // if (!ray_intesects_bounding_volume) // return false; ////////////////////////////////////////////////////////////////////////// // Получаем информацию о массиве треугольников сетки CTriangle const* const triangles = m_pMeshData->GetTriangles(); const size_t numTriangles = m_pMeshData->GetTriangleCount(); // Массив найденных пересечений луча с гранями сетки. std::vector<FaceHit> faceHits; ////////////////////////////////////////////////////////////////////////// // Используется поиск пересечения луча со всеми гранями сетки. // Данный подход является очень неэффективным уже на полигональных сетках, // содержащих более нескольких десятков граней, а, тем более, сотни и тысячи граней. // // Для эффективного поиска столкновений следует представить полигональную сетку // не в виде массива граней, а в виде древовидной структуры (Oct-Tree или BSP-Tree), // что уменьшит вычислительную сложность поиска столкновений с O(N) до O(log N) ////////////////////////////////////////////////////////////////////////// FaceHit hit; for (size_t i = 0; i < numTriangles; ++i) { CTriangle const& triangle = triangles[i]; // Проверка на пересечение луча с треугольной гранью if (triangle.HitTest(invRayStart, invRayDirection, hit.hitTime, hit.hitPointInObjectSpace, hit.w0, hit.w1, hit.w2)) { // Сохраняем индекс грани и добавляем информацию в массив найденных пересечений hit.faceIndex = i; if (faceHits.empty()) { // При обнаружени первого пересечения резервируем // память сразу под 8 пересечений (для уменьшения количества операций выделения памяти) faceHits.reserve(8); } faceHits.push_back(hit); } } // При отсутствии пересечений выходим if (faceHits.empty()) { return false; } ////////////////////////////////////////////////////////////////////////// // Упорядочиваем найденные пересечения по возрастанию времени столкновения ////////////////////////////////////////////////////////////////////////// size_t const numHits = faceHits.size(); std::vector<FaceHit const *> hitPointers(numHits); { // Инициализируем массив указателей на точки пересечения for (size_t i = 0; i < numHits; ++i) { hitPointers[i] = &faceHits[i]; } // Сортируем массив указателей по возрастанию времени столкновения // Сортируются указатели, а не сами объекты, чтобы сократить // издержки на обмен элементов массива: // На 32 битной платформе размер структуры FaceHit равен 64 байтам, // а размер указателя - всего 4 байта. if (numHits > 1) { std::sort(hitPointers.begin(), hitPointers.end(), HitPointerComparator()); // Теперь указатели в массиве hitPointers отсортированы // в порядке возрастания времени столкновения луча с гранями } } ////////////////////////////////////////////////////////////////////////// // Возвращаем информацию о найденных пересечениях ////////////////////////////////////////////////////////////////////////// for (size_t i = 0; i < numHits; ++i) { // Получаем информацию о столкновении FaceHit const& faceHit = *hitPointers[i]; // Сохраняем информацию только о первом пересечении if (i == 0) { ExtendedHitData *hd = (ExtendedHitData*)&m_lastHitData; hd->faceIndex = faceHit.faceIndex; hd->vc[0] = static_cast<float>(faceHit.w0); hd->vc[1] = static_cast<float>(faceHit.w1); hd->vc[2] = static_cast<float>(faceHit.w2); } // Точка столкновения в мировой системе координат CVector3d hitPoint = ray.GetPointAtTime(faceHit.hitTime); // Грань, с которой произошло столкновение CTriangle const& triangle = triangles[faceHit.faceIndex]; // Нормаль "плоской грани" во всех точках столкновения равна нормали самой грани CVector3d normalInObjectSpace = triangle.GetPlaneEquation(); if (!triangle.IsFlatShaded()) { // Для неплоских граней выполняется интерполяция нормалей вершин треугольника // с учетом их весовых коэффициентов в точке пересечения Vertex const& v0 = triangle.GetVertex0(); Vertex const& v1 = triangle.GetVertex1(); Vertex const& v2 = triangle.GetVertex2(); // Взвешенный вектор нормали normalInObjectSpace = faceHit.w0 * v0.normal + faceHit.w1 * v1.normal + faceHit.w2 * v2.normal; } // Нормаль в мировой системе координат CVector3d normal = GetNormalMatrix() * normalInObjectSpace; // Добавляем информацию о точке пересечения в объект intersection intersection.AddHit( CHitInfo( faceHit.hitTime, *this, hitPoint, faceHit.hitPointInObjectSpace, normal, normalInObjectSpace ) ); } return true; }