Post by loop23 on Jan 16, 2024 8:49:46 GMT
I'm up to implementing groups in chapter 14 and so far all tests I've implemented so far are passing except for tests #7 "Convert a Point from World Space to Object Space" and #8 "Convert a Normal Vector from Object Space to World Space".
To clarify in my ray tracer I made it so all shapes, cameras and light sources all inherit from an abstract Object base class which is where the transformation matrix is defined.
I had difficulty getting test #6 to pass the way I got it to pass was in my SetTransform() function I added an optional boolean argument which switches the order the multiplication with the objects current transformation matrix and the new transformation matrix is done. However I feel that the fact I had to do that hack means something else in my code may be wrong even though before implementing that hack all previous tests were passing and are still passing just fine.
Here is the body of the SetTransform function just to better explain the hack I did.
Moving on, I've checked the tests and they are written as per the book, no typos with values or anything and I checked that the world_to_object and normal_to_world functions are written correctly.
Here's some infomation about the objects from test #7 and the resulting point I get from world_to_object:
Name: Group1
Type: kGroup
Transform:
[-4.37114e-08, 0, 1, 0]
[0, 1, 0, 0]
[-1, 0, -4.37114e-08, 0]
[0, 0, 0, 1]
Material:
Color: (1, 1, 1)
Ambient: 0.1
Diffuse: 0.9
Specular: 0.9
Shininess: 200
Reflectivity: 0
Transparency: 0
Refractive Index: 1
Parent: None
Name: Group2
Type: kGroup
Transform:
[-8.74228e-08, 0, 2, 0]
[0, 2, 0, 0]
[-2, 0, -8.74228e-08, 0]
[0, 0, 0, 1]
Material:
Color: (1, 1, 1)
Ambient: 0.1
Diffuse: 0.9
Specular: 0.9
Shininess: 200
Reflectivity: 0
Transparency: 0
Refractive Index: 1
Parent: Group1
Name: Sphere
Type: kSphere
Transform:
[-8.74228e-08, 0, 2, -4.37114e-07]
[0, 2, 0, 0]
[-2, 0, -8.74228e-08, -10]
[0, 0, 0, 1]
Material:
Color: (1, 1, 1)
Ambient: 0.1
Diffuse: 0.9
Specular: 0.9
Shininess: 200
Reflectivity: 0
Transparency: 0
Refractive Index: 1
Parent: Group2
Resulting Point from world_to_object() is: (-7.5, 0, 0.5)
The point is not (0, 0, -1) like it should be.
The full list of point values the function outputs (for comparison with other people's code) are:
(10, 0, -2)
(1, 0, 5)
(-7.5, 0, 0.5)
And here is infomation about Test #8 and the resulting vector(s) from normal_to_world:
The final vector returned is not (0.2857, 0.4286, -0.8571)) like it should be.
Name: Group1
Type: kGroup
Transform:
[-4.37114e-08, 0, 1, 0]
[0, 1, 0, 0]
[-1, 0, -4.37114e-08, 0]
[0, 0, 0, 1]
Material:
Color: (1, 1, 1)
Ambient: 0.1
Diffuse: 0.9
Specular: 0.9
Shininess: 200
Reflectivity: 0
Transparency: 0
Refractive Index: 1
Parent: None
Name: Group2
Type: kGroup
Transform:
[-4.37114e-08, 0, 3, 0]
[0, 2, 0, 0]
[-1, 0, -1.31134e-07, 0]
[0, 0, 0, 1]
Material:
Color: (1, 1, 1)
Ambient: 0.1
Diffuse: 0.9
Specular: 0.9
Shininess: 200
Reflectivity: 0
Transparency: 0
Refractive Index: 1
Parent: Group1
Name: Sphere
Type: kSphere
Transform:
[-4.37114e-08, 0, 3, -2.18557e-07]
[0, 2, 0, 0]
[-1, 0, -1.31134e-07, -5]
[0, 0, 0, 1]
Material:
Color: (1, 1, 1)
Ambient: 0.1
Diffuse: 0.9
Specular: 0.9
Shininess: 200
Reflectivity: 0
Transparency: 0
Refractive Index: 1
Parent: Group2
(-0.624695, 0.468521, 0.624695)
(-0.624695, 0.468521, 0.624695)
(-0.624695, 0.468521, 0.624695)
(-0.624695, 0.468521, 0.624695)
With all that out of the way here's my code.
Here's my shape implementation:
My group implementation:
My Object implementation:
And finally here's the two Group tests that are failing just in case I did type something wrong reading from the book:
Hopefully I've provided enoug infomation. Let me know if there's anything else I can provide that may be helpful in solving this issue.
Thanks.
To clarify in my ray tracer I made it so all shapes, cameras and light sources all inherit from an abstract Object base class which is where the transformation matrix is defined.
I had difficulty getting test #6 to pass the way I got it to pass was in my SetTransform() function I added an optional boolean argument which switches the order the multiplication with the objects current transformation matrix and the new transformation matrix is done. However I feel that the fact I had to do that hack means something else in my code may be wrong even though before implementing that hack all previous tests were passing and are still passing just fine.
Here is the body of the SetTransform function just to better explain the hack I did.
insert code herevoid Object::SetTransform(const Matrix4& transform, const bool& changeOrder) {
transform_ = changeOrder ? transform * transform_ : transform_ * transform;
}
Moving on, I've checked the tests and they are written as per the book, no typos with values or anything and I checked that the world_to_object and normal_to_world functions are written correctly.
Here's some infomation about the objects from test #7 and the resulting point I get from world_to_object:
Name: Group1
Type: kGroup
Transform:
[-4.37114e-08, 0, 1, 0]
[0, 1, 0, 0]
[-1, 0, -4.37114e-08, 0]
[0, 0, 0, 1]
Material:
Color: (1, 1, 1)
Ambient: 0.1
Diffuse: 0.9
Specular: 0.9
Shininess: 200
Reflectivity: 0
Transparency: 0
Refractive Index: 1
Parent: None
Name: Group2
Type: kGroup
Transform:
[-8.74228e-08, 0, 2, 0]
[0, 2, 0, 0]
[-2, 0, -8.74228e-08, 0]
[0, 0, 0, 1]
Material:
Color: (1, 1, 1)
Ambient: 0.1
Diffuse: 0.9
Specular: 0.9
Shininess: 200
Reflectivity: 0
Transparency: 0
Refractive Index: 1
Parent: Group1
Name: Sphere
Type: kSphere
Transform:
[-8.74228e-08, 0, 2, -4.37114e-07]
[0, 2, 0, 0]
[-2, 0, -8.74228e-08, -10]
[0, 0, 0, 1]
Material:
Color: (1, 1, 1)
Ambient: 0.1
Diffuse: 0.9
Specular: 0.9
Shininess: 200
Reflectivity: 0
Transparency: 0
Refractive Index: 1
Parent: Group2
Resulting Point from world_to_object() is: (-7.5, 0, 0.5)
The point is not (0, 0, -1) like it should be.
The full list of point values the function outputs (for comparison with other people's code) are:
(10, 0, -2)
(1, 0, 5)
(-7.5, 0, 0.5)
And here is infomation about Test #8 and the resulting vector(s) from normal_to_world:
The final vector returned is not (0.2857, 0.4286, -0.8571)) like it should be.
Name: Group1
Type: kGroup
Transform:
[-4.37114e-08, 0, 1, 0]
[0, 1, 0, 0]
[-1, 0, -4.37114e-08, 0]
[0, 0, 0, 1]
Material:
Color: (1, 1, 1)
Ambient: 0.1
Diffuse: 0.9
Specular: 0.9
Shininess: 200
Reflectivity: 0
Transparency: 0
Refractive Index: 1
Parent: None
Name: Group2
Type: kGroup
Transform:
[-4.37114e-08, 0, 3, 0]
[0, 2, 0, 0]
[-1, 0, -1.31134e-07, 0]
[0, 0, 0, 1]
Material:
Color: (1, 1, 1)
Ambient: 0.1
Diffuse: 0.9
Specular: 0.9
Shininess: 200
Reflectivity: 0
Transparency: 0
Refractive Index: 1
Parent: Group1
Name: Sphere
Type: kSphere
Transform:
[-4.37114e-08, 0, 3, -2.18557e-07]
[0, 2, 0, 0]
[-1, 0, -1.31134e-07, -5]
[0, 0, 0, 1]
Material:
Color: (1, 1, 1)
Ambient: 0.1
Diffuse: 0.9
Specular: 0.9
Shininess: 200
Reflectivity: 0
Transparency: 0
Refractive Index: 1
Parent: Group2
(-0.624695, 0.468521, 0.624695)
(-0.624695, 0.468521, 0.624695)
(-0.624695, 0.468521, 0.624695)
(-0.624695, 0.468521, 0.624695)
With all that out of the way here's my code.
Here's my shape implementation:
#include <sstream>
#include "shape.h"
#include "material.h"
#include <stdexcept>
int Shape::shape_count_ = 0;
Shape::Shape(const std::string& name, const ObjectType& type)
: Object(name, type), material_(Material()), parent_(nullptr) {
shape_count_++;
}
Shape::Shape(const std::string& name, const ObjectType& type, const Matrix4& transform)
: Object(name, type, transform), material_(Material()), parent_(nullptr) {
shape_count_++;
}
Shape::Shape(const std::string& name, const ObjectType& type, const Material& material)
: Object(name, type), material_(material), parent_(nullptr) {
shape_count_++;
}
Shape::Shape(const std::string& name, const ObjectType& type, const Material& material,
const Matrix4& transform)
: Object(name, type, transform), material_(material), parent_(nullptr) {
shape_count_++;
}
Shape::~Shape() {
shape_count_--;
parent_ = nullptr;
delete parent_;
}
void Shape::ListDetails() {
std::string parent_name = parent_ == nullptr ? "None" : parent_->GetName();
std::cout << "Name: " << name_ << "\n"
<< "Type: " << this->GetObjectTypeName() << "\n"
<< "Transform:\n" << transform_.format()
<< "Material:\n" << material_.format()
<< "Parent: " << parent_name << "\n\n";
}
Vector Shape::normal_at(const Point& point) const {
Matrix4 shape_inverse = this->GetTransform().inverse();
Point local_point = shape_inverse * point;
Vector local_normal = this->local_normal_at(local_point);
Vector world_normal = shape_inverse.transpose() * local_normal;
world_normal(3, 0); // set the w coordinate to 0
return world_normal.normalize();
}
Material& Shape::GetMaterial() {
return material_;
}
void Shape::SetMaterial(const Material& material) {
material_ = material;
}
int Shape::GetCount() {
return shape_count_;
}
bool Shape::operator==(const Object& object) {
Shape* other = (Shape*)&object;
return(name_ == other->GetName() &&
transform_ == other->GetTransform() &&
type_ == other->GetObjectType() &&
material_ == other->GetMaterial() &&
parent_ == other->GetParent());
}
Shape& Shape::operator=(const Object& object) {
Shape* other = (Shape*)&object;
name_ = other->GetName();
type_ = other->GetObjectType();
transform_ = other->GetTransform();
material_ = other->GetMaterial();
parent_ = other->GetParent();
return *this;
}
Shape* Shape::GetParent() const {
return parent_;
}
void Shape::SetParent(Shape* parent) {
if (parent == this) {
throw std::invalid_argument("Error: Shape parent cannot be itself.");
}
if (parent == nullptr) {
throw std::invalid_argument("Error: Shape parent cannot be null.");
}
parent_ = parent;
}
void Shape::RemoveParent() {
if (parent_ == nullptr) {
throw std::invalid_argument("Error: No parent to remove from shape");
}
parent_ = nullptr;
}
bool Shape::HasParent() const {
return parent_ != nullptr;
}
Point Shape::world_to_object(Shape* shape, Point point) {
if (shape->HasParent()) {
point = Shape::world_to_object(shape->GetParent(), point);
}
return shape->GetTransform().inverse() * point;
}
Vector Shape::normal_to_world(Shape* shape, Vector normal) {
normal = shape->GetTransform().inverse().transpose() * normal;
normal(3, 0); // set w coordinate to 0
normal = normal.normalize();
if (shape->HasParent()) {
normal = Shape::normal_to_world(shape->GetParent(), normal);
}
std::cout << normal << "\n";
return normal;
}
My group implementation:
#include "group.h"
#include "ray.h"
#include <stdexcept>
Group::Group(const std::string& name)
: Shape(name, ObjectType::kGroup), children_({}) {}
Group::Group(const std::string& name, const std::vector<Shape*>& children)
: Shape(name, ObjectType::kGroup) {
this->AddChildren(children);
}
Group::Group(const std::string& name, const std::vector<Shape*>& children, const Material& material)
: Shape(name, ObjectType::kGroup, material) {
this->AddChildren(children);
}
Group::Group(const std::string& name, const std::vector<Shape*>& children, const Matrix4& transform)
: Shape(name, ObjectType::kGroup, transform) {
this->AddChildren(children);
}
Group::Group(const std::string& name, const std::vector<Shape*>& children, const Material& material, const Matrix4& transform)
: Shape(name, ObjectType::kGroup, material, transform) {
this->AddChildren(children);
}
Group::~Group() {
for (int i = 0; i < children_.size(); i++) {
children_[i] = nullptr;
delete children_[i];
}
}
//bool Group::operator==(const Object& object) {
// Group* other = (Group*)&object;
// return(this->GetName() == other->GetName() &&
// this->GetTransform() == other->GetTransform() &&
// this->GetObjectType() == other->GetObjectType() &&
// this->GetMaterial() == other->GetMaterial());
//}
//
//Group& Group::operator=(const Object& object) {
// Group* other = (Group*)&object;
// this->SetName(other->GetName());
// this->SetObjectType(other->GetObjectType());
// this->SetTransform(other->GetTransform());
// this->SetMaterial(other->GetMaterial());
// return *this;
//}
std::vector<Shape*> Group::GetChildren() const {
return children_;
}
void Group::AddChild(Shape* child) {
if (child == nullptr) {
throw std::invalid_argument("Error: Group child cannot be null.");
}
child->SetParent(this);
child->SetTransform(this->GetTransform(), true);
children_.push_back(child);
}
void Group::AddChildren(const std::vector<Shape*>& children) {
for (auto& child : children) {
this->AddChild(child);
}
}
int Group::GetChildrenCount() const {
return children_.size();
}
bool Group::isEmpty() const {
return children_.size() == 0;
}
bool Group::ContainsChild(Shape* other) const {
for (auto& child : children_) {
if ((*child) == (*other)) {
return true;
}
}
return false;
}
void Group::RemoveChild(Shape* child) {
for (int i = 0; i < children_.size(); i++) {
if ((*children_[i]) == (*child)) {
// Set the transform of the child being removed to the inverse of the group's transform to undo the transforms that were performed on it
// when it was added to the group.
children_[i]->SetTransform(this->GetTransform().inverse(), true);
children_[i] = nullptr;
children_.erase(children_.begin() + i);
}
}
}
std::vector<Intersection> Group::local_intersect(const utils::RayStruct& local_ray) {
std::vector<Intersection> xs;
for (auto& object : children_) {
Ray local(local_ray);
Ray transformedRay = local.transform(object->GetTransform().inverse());
std::vector<Intersection> child_xs = object->local_intersect(transformedRay.to_ray_struct());
for (auto& child : child_xs) {
xs.push_back(child);
}
}
return Intersection::intersections(xs);
}
Vector Group::local_normal_at(const Point& local_point) const {
return Vector(0, 0, 0);
}
My Object implementation:
#include "object.h"
int Object::object_count_ = 0;
Object::Object(const std::string& name, const ObjectType& type)
: transform_(Matrix4()), name_(name), type_(type) {
object_count_++;
}
Object::Object(const std::string& name, const ObjectType& type, const Matrix4& transform)
: transform_(transform), name_(name), type_(type) {
object_count_++;
}
Object::Object(const std::string& name, const ObjectType& type, const Point& position)
: transform_(Matrix4().translation(position[0], position[1], position[2])),
name_(name), type_(type) {
object_count_++;
}
Object::~Object() {
object_count_--;
}
void Object::ListDetails() {
std::cout << "Name: " << name_ << "\n"
<< "Type: " << this->GetObjectTypeName() << "\n"
<< "Transform:\n" << transform_.format() << "\n";
}
std::string Object::GetName() const { return name_; }
void Object::SetName(const std::string& name) { name_ = name; }
Matrix4 Object::GetTransform() const { return transform_; }
void Object::SetTransform(const Matrix4& transform, const bool& changeOrder) {
transform_ = changeOrder ? transform * transform_ : transform_ * transform;
}
void Object::SetObjectType(const ObjectType& type) {
type_ = type;
}
Point Object::GetPosition() const {
float x = transform_(0, 3);
float y = transform_(1, 3);
float z = transform_(2, 3);
return Point(x, y, z);
}
ObjectType Object::GetObjectType() const { return type_; }
std::string Object::GetObjectTypeName() const {
return typeMap[type_];
}
bool Object::operator==(const Object& other) {
return(name_ == other.GetName() &&
transform_ == other.GetTransform() &&
type_ == other.GetObjectType());
}
Object& Object::operator=(const Object& other) {
name_ = other.GetName();
type_ = other.GetObjectType();
transform_ = other.GetTransform();
return *this;
}
int Object::GetCount() { return object_count_; }
And finally here's the two Group tests that are failing just in case I did type something wrong reading from the book:
TEST(Chapter14_tests, Converting_a_point_from_world_to_object_space_with_multiple_grouped_objects) {
Group g1("Group1");
g1.SetTransform(Matrix4().rotation_y(utils::kPI / 2));
Group g2("Group2");
g2.SetTransform(Matrix4().scaling(2, 2, 2));
g1.AddChild(&g2);
Sphere s("Sphere");
s.SetTransform(Matrix4().translation(5, 0, 0));
g2.AddChild(&s);
Point p = Shape::world_to_object(&s, Point(-2, 0, -10));
EXPECT_TRUE(p == Point(0, 0, -1));
}
TEST(Chapter14_tests, Converting_a_normal_from_object_to_world_space) {
Group g1("Group1");
g1.SetTransform(Matrix4().rotation_y(utils::kPI / 2));
Group g2("Group2");
g2.SetTransform(Matrix4().scaling(1, 2, 3));
g1.AddChild(&g2);
Sphere s("Sphere");
s.SetTransform(Matrix4().translation(5, 0, 0));
g2.AddChild(&s);
Vector n = Shape::normal_to_world(&s, Vector(sqrt(3) / 3, sqrt(3) / 3, sqrt(3) / 3));
EXPECT_TRUE(n == Vector(0.2857f, 0.4286f, -0.8571f));
}
Hopefully I've provided enoug infomation. Let me know if there's anything else I can provide that may be helpful in solving this issue.
Thanks.