diff --git a/include/SparkyStudios/Audio/Amplitude/HRTF/HRIRSphere.h b/include/SparkyStudios/Audio/Amplitude/HRTF/HRIRSphere.h index e9a0b1e6..8212a846 100644 --- a/include/SparkyStudios/Audio/Amplitude/HRTF/HRIRSphere.h +++ b/include/SparkyStudios/Audio/Amplitude/HRTF/HRIRSphere.h @@ -150,14 +150,24 @@ namespace SparkyStudios::Audio::Amplitude /** * @brief Samples the HRIR sphere for the given direction using bilinear interpolation. - * See more info here http://www02.smt.ufrj.br/~diniz/conf/confi117.pdf. * - * @param direction The sound to listener direction. - * @param leftHRIR The left HRIR data. - * @param rightHRIR The right HRIR data. + * See more info about bilinear sampling [here](http://www02.smt.ufrj.br/~diniz/conf/confi117.pdf). + * + * @param[in] direction The sound to listener direction. + * @param[out] leftHRIR The left HRIR data. + * @param[out] rightHRIR The right HRIR data. */ virtual void SampleBilinear(const AmVec3& direction, AmReal32* leftHRIR, AmReal32* rightHRIR) const = 0; + /** + * @brief Samples the HRIR sphere for the given direction using nearest neighbor interpolation. + * + * @param[in] direction The sound to listener direction. + * @param[out] leftHRIR The left HRIR data. + * @param[out] rightHRIR The right HRIR data. + */ + virtual void SampleNearestNeighbor(const AmVec3& direction, AmReal32* leftHRIR, AmReal32* rightHRIR) const = 0; + virtual void Transform(const AmMat4& matrix) = 0; [[nodiscard]] virtual bool IsLoaded() const = 0; diff --git a/src/HRTF/HRIRSphere.cpp b/src/HRTF/HRIRSphere.cpp index 803bd319..464a00d5 100644 --- a/src/HRTF/HRIRSphere.cpp +++ b/src/HRTF/HRIRSphere.cpp @@ -149,40 +149,71 @@ namespace SparkyStudios::Audio::Amplitude if (face == nullptr) return; + // If we are very close to any vertex, just return the HRIR of that vertex + { + const auto* vertex = GetClosestVertex(direction, face); + + if (vertex != nullptr) + { + const AmSize length = vertex->m_LeftIR.size(); + std::memcpy(leftHRIR, vertex->m_LeftIR.data(), length * sizeof(AmReal32)); + std::memcpy(rightHRIR, vertex->m_RightIR.data(), length * sizeof(AmReal32)); + return; + } + } + const auto& vertexA = _vertices[face->m_A]; const auto& vertexB = _vertices[face->m_B]; const auto& vertexC = _vertices[face->m_C]; - // If we are close to any vertex, just return the HRIR of that vertex + // Otherwise, perform bilinear interpolation { - constexpr AmReal32 k2 = kEpsilon * kEpsilon; - - if (AM_LenSqr(vertexA.m_Position - direction) < k2) + BarycentricCoordinates barycenter; + if (!BarycentricCoordinates::RayTriangleIntersection( + AM_V3(0.0f, 0.0f, 0.0f), dir, { vertexA.m_Position, vertexB.m_Position, vertexC.m_Position }, barycenter)) { - const AmSize length = vertexA.m_LeftIR.size(); - std::memcpy(leftHRIR, vertexA.m_LeftIR.data(), length * sizeof(AmReal32)); - std::memcpy(rightHRIR, vertexA.m_RightIR.data(), length * sizeof(AmReal32)); return; } - if (AM_LenSqr(vertexB.m_Position - direction) < k2) + const AmSize length = vertexA.m_LeftIR.size(); + + for (AmSize i = 0; i < length; ++i) { - const AmSize length = vertexB.m_LeftIR.size(); - std::memcpy(leftHRIR, vertexB.m_LeftIR.data(), length * sizeof(AmReal32)); - std::memcpy(rightHRIR, vertexB.m_RightIR.data(), length * sizeof(AmReal32)); - return; + leftHRIR[i] = + vertexA.m_LeftIR[i] * barycenter.m_U + vertexB.m_LeftIR[i] * barycenter.m_V + vertexC.m_LeftIR[i] * barycenter.m_W; + + rightHRIR[i] = + vertexA.m_RightIR[i] * barycenter.m_U + vertexB.m_RightIR[i] * barycenter.m_V + vertexC.m_RightIR[i] * barycenter.m_W; } + } + } + + void HRIRSphereImpl::SampleNearestNeighbor(const AmVec3& direction, AmReal32* leftHRIR, AmReal32* rightHRIR) const + { + const auto& dir = AM_Mul(direction, 10.0f); + const auto* face = _tree.Query(dir); - if (AM_LenSqr(vertexC.m_Position - direction) < k2) + if (face == nullptr) + return; + + // If we are very close to any vertex, just return the HRIR of that vertex + { + const auto* vertex = GetClosestVertex(direction, face); + + if (vertex != nullptr) { - const AmSize length = vertexC.m_LeftIR.size(); - std::memcpy(leftHRIR, vertexC.m_LeftIR.data(), length * sizeof(AmReal32)); - std::memcpy(rightHRIR, vertexC.m_RightIR.data(), length * sizeof(AmReal32)); + const AmSize length = vertex->m_LeftIR.size(); + std::memcpy(leftHRIR, vertex->m_LeftIR.data(), length * sizeof(AmReal32)); + std::memcpy(rightHRIR, vertex->m_RightIR.data(), length * sizeof(AmReal32)); return; } } - // Otherwise, perform bilinear interpolation + const auto& vertexA = _vertices[face->m_A]; + const auto& vertexB = _vertices[face->m_B]; + const auto& vertexC = _vertices[face->m_C]; + + // Otherwise, perform nearest neighbor interpolation { BarycentricCoordinates barycenter; if (!BarycentricCoordinates::RayTriangleIntersection( @@ -192,14 +223,27 @@ namespace SparkyStudios::Audio::Amplitude } const AmSize length = vertexA.m_LeftIR.size(); + const AmReal32 min = std::min({ barycenter.m_U, barycenter.m_V, barycenter.m_W }); - for (AmSize i = 0; i < length; ++i) + if (min == barycenter.m_U) { - leftHRIR[i] = - vertexA.m_LeftIR[i] * barycenter.m_U + vertexB.m_LeftIR[i] * barycenter.m_V + vertexC.m_LeftIR[i] * barycenter.m_W; + std::memcpy(leftHRIR, vertexA.m_LeftIR.data(), length * sizeof(AmReal32)); + std::memcpy(rightHRIR, vertexA.m_RightIR.data(), length * sizeof(AmReal32)); + return; + } - rightHRIR[i] = - vertexA.m_RightIR[i] * barycenter.m_U + vertexB.m_RightIR[i] * barycenter.m_V + vertexC.m_RightIR[i] * barycenter.m_W; + if (min == barycenter.m_V) + { + std::memcpy(leftHRIR, vertexB.m_LeftIR.data(), length * sizeof(AmReal32)); + std::memcpy(rightHRIR, vertexB.m_RightIR.data(), length * sizeof(AmReal32)); + return; + } + + if (min == barycenter.m_W) + { + std::memcpy(leftHRIR, vertexC.m_LeftIR.data(), length * sizeof(AmReal32)); + std::memcpy(rightHRIR, vertexC.m_RightIR.data(), length * sizeof(AmReal32)); + return; } } } @@ -214,4 +258,24 @@ namespace SparkyStudios::Audio::Amplitude { return _loaded; } + + const HRIRSphereVertex* HRIRSphereImpl::GetClosestVertex(const AmVec3& position, const Face* face) const + { + const auto& vertexA = _vertices[face->m_A]; + const auto& vertexB = _vertices[face->m_B]; + const auto& vertexC = _vertices[face->m_C]; + + constexpr AmReal32 k2 = kEpsilon * kEpsilon; + + if (AM_LenSqr(vertexA.m_Position - position) < k2) + return &vertexA; + + if (AM_LenSqr(vertexB.m_Position - position) < k2) + return &vertexB; + + if (AM_LenSqr(vertexC.m_Position - position) < k2) + return &vertexC; + + return nullptr; + } } // namespace SparkyStudios::Audio::Amplitude diff --git a/src/HRTF/HRIRSphere.h b/src/HRTF/HRIRSphere.h index fd9df42d..6c8a63c4 100644 --- a/src/HRTF/HRIRSphere.h +++ b/src/HRTF/HRIRSphere.h @@ -53,10 +53,13 @@ namespace SparkyStudios::Audio::Amplitude [[nodiscard]] AmUInt32 GetSampleRate() const override; [[nodiscard]] AmUInt32 GetIRLength() const override; void SampleBilinear(const AmVec3& direction, AmReal32* leftHRIR, AmReal32* rightHRIR) const override; + void SampleNearestNeighbor(const AmVec3& direction, AmReal32* leftHRIR, AmReal32* rightHRIR) const override; void Transform(const AmMat4& matrix) override; [[nodiscard]] bool IsLoaded() const override; private: + const HRIRSphereVertex* GetClosestVertex(const AmVec3& position, const Face* face) const; + HRIRSphereFileHeaderDescription _header; std::vector _vertices; std::vector _faces;