From 65f3550baaa15a959565768561182eed9acb5292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20Guimar=C3=A3es?= Date: Wed, 16 Jul 2025 11:25:25 -0300 Subject: [PATCH 1/2] fix(objects): convert local-space hits to world-space for validation --- src/src/objects/mesh.cpp | 21 ++++++++++++--------- src/src/objects/plane.cpp | 9 +++++---- src/src/objects/sphere.cpp | 25 +++++++++++++++++-------- src/src/objects/triangle.cpp | 34 ++++++++++++++++++---------------- 4 files changed, 52 insertions(+), 37 deletions(-) diff --git a/src/src/objects/mesh.cpp b/src/src/objects/mesh.cpp index 65ea57f..fa79f16 100644 --- a/src/src/objects/mesh.cpp +++ b/src/src/objects/mesh.cpp @@ -54,21 +54,24 @@ bool Mesh::hit(const Ray& ray, double t_min, double t_max, HitRecord& rec) const Point3 world_p; double world_t = INFINITY; - if (rec.t < t_max) { + if (rec.t < t_max) { // If a hit was found, transform the hit point back to world space world_p = transform * transformed_ray.at(rec.t); world_t = (world_p - ray.origin()).dot(ray.direction().normalize()); } - if (world_t < t_max) { - rec.p = world_p; - rec.t = world_t; - Vector3 world_normal = (inverseTransposeTransform * rec.normal).normalize(); - rec.set_face_normal(ray, world_normal); - rec.material = material; - return true; + if (world_t < t_min || world_t > t_max) { + return false; } - return false; + rec.t = world_t; + rec.p = world_p; + + Vector3 world_normal = (inverseTransposeTransform * rec.normal).normalize(); + rec.set_face_normal(ray, world_normal); + + rec.material = material; + + return true; }; void Mesh::setMaterial(std::shared_ptr new_material) { diff --git a/src/src/objects/plane.cpp b/src/src/objects/plane.cpp index 9e7f881..4af2260 100644 --- a/src/src/objects/plane.cpp +++ b/src/src/objects/plane.cpp @@ -20,16 +20,17 @@ bool Plane::hit(const Ray& ray, double t_min, double t_max, HitRecord& rec) cons return false; } - double t = (point_on_plane - transformed_ray.origin()).dot(normal) / denominator; + Vector3 world_normal = (this->inverseTransposeTransform * this->normal).normalize(); + Vector3 world_point_on_plane = transform * point_on_plane; + double t = (world_point_on_plane - ray.origin()).dot(world_normal) / denominator; if (t < t_min || t > t_max) { return false; } rec.t = t; - rec.p = transform * transformed_ray.at(t); // Ponto de volta para o espaço global - Vector3 outward_normal_world = (inverseTransposeTransform * this->normal).normalize(); - rec.set_face_normal(ray, outward_normal_world); // Usa o raio original + rec.p = ray.at(t); // Ponto de volta para o espaço global + rec.set_face_normal(ray, world_normal); // Usa o raio original rec.material = material; return true; diff --git a/src/src/objects/sphere.cpp b/src/src/objects/sphere.cpp index 203e875..2cd42a3 100644 --- a/src/src/objects/sphere.cpp +++ b/src/src/objects/sphere.cpp @@ -33,21 +33,30 @@ bool Sphere::hit(const Ray& ray, double t_min, double t_max, HitRecord& rec) con } auto sqrtd = sqrt(discriminant); + auto root = (-halfb - sqrtd) / a; - if (root < t_min || t_max < root) { + + Point3 local_hit_point = transformed_ray.at(root); + Point3 world_hit_point = transform * local_hit_point; + double t = (world_hit_point - ray.origin()).dot(ray.direction()); + + if (t < t_min || t > t_max) { root = (-halfb + sqrtd) / a; - if (root < t_min || t_max < root) { + local_hit_point = transformed_ray.at(root); + world_hit_point = transform * local_hit_point; + t = (world_hit_point - ray.origin()).dot(ray.direction()); + if (t < t_min || t > t_max) { return false; } } - rec.t = root; - Point3 local_hit_point = transformed_ray.at(rec.t); - Vector3 outward_normal_local = (local_hit_point - center) / radius; + rec.t = t; + rec.p = world_hit_point; + + Vector3 normal_local = (local_hit_point - center) / radius; + Vector3 normal_world = (inverseTransposeTransform * normal_local).normalize(); + rec.set_face_normal(ray, normal_world); - rec.p = transform * local_hit_point; - Vector3 outward_normal_world = (inverseTransposeTransform * outward_normal_local).normalize(); - rec.set_face_normal(ray, outward_normal_world); rec.material = material; return true; diff --git a/src/src/objects/triangle.cpp b/src/src/objects/triangle.cpp index f2f7e8f..abd35bd 100644 --- a/src/src/objects/triangle.cpp +++ b/src/src/objects/triangle.cpp @@ -27,8 +27,8 @@ bool Triangle::hit(const Ray& ray, double t_min, double t_max, HitRecord& rec) c const Vector3 edge1 = this->getPoint2() - this->getPoint1(); const Vector3 edge2 = this->getPoint3() - this->getPoint1(); - const Vector3 h = transformed_ray.direction() ^ edge2; - const double a = edge1 * h; + const Vector3 h = transformed_ray.direction().cross(edge2); + const double a = edge1.dot(h); if (a > -epsilon && a < epsilon) { return false; @@ -36,34 +36,37 @@ bool Triangle::hit(const Ray& ray, double t_min, double t_max, HitRecord& rec) c const double f = 1.0 / a; const Vector3 s = transformed_ray.origin() - this->getPoint1(); - const double u = f * (s * h); + const double u = f * s.dot(h); if (u < 0.0 || u > 1.0) { return false; } - const Vector3 q = s ^ edge1; + const Vector3 q = s.cross(edge1); const double v = f * (transformed_ray.direction() * q); if (v < 0.0 || u + v > 1.0) { return false; } - const double t = f * (edge2 * q); + const double t_local = f * edge2.dot(q); + Point3 world_hit_point = transform * transformed_ray.at(t_local); + const double t_global = (world_hit_point - ray.origin()).dot(ray.direction()); - if (t > t_min && t < t_max) { - rec.t = t; - rec.p = transform * transformed_ray.at(t); + if (t_global < t_min || t_global > t_max) { + return false; + } - Vector3 local_normal = edge1 ^ edge2; - Vector3 world_normal = (inverseTransposeTransform * local_normal).normalize(); - rec.set_face_normal(ray, world_normal); + rec.t = t_global; + rec.p = world_hit_point; - rec.material = material; - return true; - } + Vector3 local_normal = edge1.cross(edge2); + Vector3 world_normal = (inverseTransposeTransform * local_normal).normalize(); + rec.set_face_normal(ray, world_normal); - return false; + rec.material = material; + + return true; } MeshTriangle::MeshTriangle(std::shared_ptr p1, std::shared_ptr p2, @@ -128,7 +131,6 @@ bool MeshTriangle::hit(const Ray& ray, double t_min, double t_max, HitRecord& re if (t > t_min && t < t_max) { const double w = 1.0 - u - v; - ; rec.t = t; rec.normal = ((*normal1 * w) + (*normal2 * u) + (*normal3 * v)).normalize(); return true; From 9c0238039dfe4dc7ffa1933bba39a97538f4f41d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20Guimar=C3=A3es?= Date: Wed, 16 Jul 2025 11:48:02 -0300 Subject: [PATCH 2/2] fix: transfomation tests --- tests/src/TransformationsTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/TransformationsTest.cpp b/tests/src/TransformationsTest.cpp index d8b446c..03fe536 100644 --- a/tests/src/TransformationsTest.cpp +++ b/tests/src/TransformationsTest.cpp @@ -63,7 +63,7 @@ TEST(TransformationsTest, SphereHitWithNonUniformScale) { // Act & Assert EXPECT_TRUE(s->hit(ray_that_hits, 0, 100, rec)); - EXPECT_NEAR(rec.t, 1.5, 1e-6); + EXPECT_NEAR(rec.t, 3, 1e-6); // As asserções mais importantes (ponto e normal em espaço global) continuam as mesmas. AssertPointAlmostEqual(rec.p, Point3(0, 2, 0));