diff options
-rw-r--r-- | CMakeLists.txt | 26 | ||||
-rw-r--r-- | app/draw.cpp | 24 | ||||
-rw-r--r-- | app/draw.hpp | 21 | ||||
-rw-r--r-- | app/main.cpp | 17 | ||||
-rw-r--r-- | app/mainwindow.cpp | 8 | ||||
-rw-r--r-- | app/mainwindow.hpp | 19 | ||||
-rw-r--r-- | src/common.hpp | 6 | ||||
-rw-r--r-- | src/main.cpp | 23 | ||||
-rw-r--r-- | src/object.cpp | 89 | ||||
-rw-r--r-- | src/object.hpp | 34 | ||||
-rw-r--r-- | src/ray.cpp | 17 | ||||
-rw-r--r-- | src/ray.hpp | 15 | ||||
-rw-r--r-- | src/scene.cpp | 4 | ||||
-rw-r--r-- | src/scene.hpp | 4 | ||||
-rw-r--r-- | src/vector.cpp | 53 | ||||
-rw-r--r-- | src/vector.hpp | 20 | ||||
-rw-r--r-- | test/main.cpp | 2 | ||||
-rw-r--r-- | test/object.cpp | 36 | ||||
-rw-r--r-- | test/vector.cpp | 31 |
19 files changed, 404 insertions, 45 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f895da..95e098b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,16 +2,30 @@ cmake_minimum_required(VERSION 3.10) project(pathtracing) +# cmake options +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(CMAKE_AUTOMOC ON) + set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED True) +# Compiler options +set(CMAKE_CXX_FLAGS "-Wall -Wextra") +set(CMAKE_CXX_FLAGS_DEBUG "-g") +set(CMAKE_CXX_FLAGS_RELEASE "-O3") + +FILE(GLOB app_sources ${CMAKE_SOURCE_DIR}/app/*.cpp) FILE(GLOB sources ${CMAKE_SOURCE_DIR}/src/*.cpp) +FILE(GLOB test_sources ${CMAKE_SOURCE_DIR}/test/*.cpp) -add_executable(pathtracing ${sources}) +add_executable(pathtracing ${sources} ${app_sources}) -set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake_modules" ${CMAKE_MODULE_PATH}) +# Catch2 unittests +add_custom_target(test) +add_executable(run_test EXCLUDE_FROM_ALL ${sources} ${test_sources}) +target_include_directories(run_test PUBLIC src) +add_dependencies(test run_test) -# Detect and add SFML -find_package(SFML 2 REQUIRED system window graphics) -include_directories(${SFML_INCLUDE_DIR}) -target_link_libraries(pathtracing ${SFML_LIBRARIES} ${SFML_DEPENDENCIES}) +# Qt +find_package(Qt5 COMPONENTS Widgets REQUIRED) +target_link_libraries(pathtracing Qt5::Widgets) diff --git a/app/draw.cpp b/app/draw.cpp new file mode 100644 index 0000000..2f3a947 --- /dev/null +++ b/app/draw.cpp @@ -0,0 +1,24 @@ +#include "draw.hpp" +#include <qpainter.h> +#include <qglobal.h> +#include <qimage.h> +#include <qrgb.h> +#include <qwindowdefs.h> +#include <iostream> + +DrawWidget::DrawWidget(unsigned width, unsigned height) : QWidget() { + m_width = width; + m_height = height; + m_drawbuffer = new QRgb[width * height]; + + m_img = QImage((uchar*)m_drawbuffer, width, height, QImage::Format_ARGB32); +} + +void DrawWidget::paintEvent(QPaintEvent*) { + QPainter painter(this); + painter.drawImage(0, 0, m_img); +} + +DrawWidget::~DrawWidget() { + delete[] m_drawbuffer; +} diff --git a/app/draw.hpp b/app/draw.hpp new file mode 100644 index 0000000..a00de79 --- /dev/null +++ b/app/draw.hpp @@ -0,0 +1,21 @@ +#ifndef DRAW_H +#define DRAW_H + +#include <qimage.h> +#include <qwidget.h> + +class DrawWidget : public QWidget { + public: + DrawWidget(unsigned width, unsigned height); + void paintEvent(QPaintEvent*); + + QRgb *m_drawbuffer; + unsigned m_width, m_height; + + ~DrawWidget(); + private: + QImage m_img; + unsigned char i; +}; + +#endif diff --git a/app/main.cpp b/app/main.cpp new file mode 100644 index 0000000..15262f2 --- /dev/null +++ b/app/main.cpp @@ -0,0 +1,17 @@ +#include <iostream> +#include <qapplication.h> +#include <qpushbutton.h> + +#include "mainwindow.hpp" + +using namespace std; + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + + MainWindow main; + main.show(); + + return a.exec(); +} diff --git a/app/mainwindow.cpp b/app/mainwindow.cpp new file mode 100644 index 0000000..e5f9bfb --- /dev/null +++ b/app/mainwindow.cpp @@ -0,0 +1,8 @@ +#include "mainwindow.hpp" + +MainWindow::MainWindow() + : drawer(500, 500) { + + setCentralWidget(&drawer); + +} diff --git a/app/mainwindow.hpp b/app/mainwindow.hpp new file mode 100644 index 0000000..a6c1be3 --- /dev/null +++ b/app/mainwindow.hpp @@ -0,0 +1,19 @@ +#ifndef MAIN_H +#define MAIN_H + +#include <QMainWindow> + +#include "draw.hpp" + +class MainWindow : public QMainWindow { + Q_OBJECT + + public: + MainWindow(); + + private: + DrawWidget drawer; + +}; + +#endif diff --git a/src/common.hpp b/src/common.hpp new file mode 100644 index 0000000..fd219bf --- /dev/null +++ b/src/common.hpp @@ -0,0 +1,6 @@ +#ifndef COMMON_H +#define COMMON_H + +#define ZERO_APPROX 1e-6 + +#endif diff --git a/src/main.cpp b/src/main.cpp deleted file mode 100644 index 3c15429..0000000 --- a/src/main.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include <SFML/Graphics/CircleShape.hpp> -#include <SFML/Graphics/RenderWindow.hpp> -#include <SFML/Window/Event.hpp> -#include <SFML/Window/VideoMode.hpp> -#include <iostream> - -#include <SFML/Graphics.hpp> - -using namespace std; - -int main() -{ - cout << "Hello World!" << endl; - - sf::RenderWindow window(sf::VideoMode(200, 200), "Yaah working"); - sf::CircleShape shape(100.f); - - window.clear(); - window.draw(shape); - window.display(); - - return 0; -} diff --git a/src/object.cpp b/src/object.cpp new file mode 100644 index 0000000..e88ae13 --- /dev/null +++ b/src/object.cpp @@ -0,0 +1,89 @@ +#include "object.hpp" + +#include <math.h> +#include "common.hpp" + +Sphere::Sphere(Vec3d center, double radius) { + m_center = center; + m_radius = radius; +} + +Plane::Plane(Vec3d start, Vec3d norm) { + m_start = start; + m_norm = norm; + + m_norm.normalize(); +} + +void Object::setMaterial(std::shared_ptr<Material> m) { + m_mat = m; +} + +Vec3d Sphere::norm_at(const Vec3d &point, const Vec3d&) const { + auto res = point - m_center; + res.normalize(); + return res; +} + +// https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-sphere-intersection +double Sphere::intersect(const Ray &ray, bool skip_dist) const { + // Calculate O - C used multiple places + auto oc = ray.m_start - m_center; + // Calculate components of quadratic formula + // a = 1 when ray.direction is a unit vector + auto a = 1; + auto b = 2 * ray.m_direction.dot(oc); + auto c = oc.dot(oc) - m_radius * m_radius; + + // Solve quadratic function + auto discr = b * b - 4 * a * c; + if (discr < 0) { + // No solution + return -1; + } + if (skip_dist) { + // Do not calculate distance + return 1; + } + + auto q = (b > 0) ? + -0.5 * (b + sqrt(discr)): + -0.5 * (b - sqrt(discr)); + auto t1 = q; // Assuming a = 1 + auto t0 = c / q; + + // Find correct result + if (t0 <= ZERO_APPROX) { + t0 = t1; + } + + if (t0 <= ZERO_APPROX) { + return -1; + } + + return t0; +} + +Vec3d Plane::norm_at(const Vec3d&, const Vec3d &indir) const { + auto scale = m_norm.dot(indir); + return scale > 0 ? -m_norm : m_norm; +} + +// https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-plane-and-ray-disk-intersection +// Requires that vectors are normalized +// Skip dist is ignored as distance must be calculated +double Plane::intersect(const Ray &ray, bool) const { + // If ray is parallel + auto nr = m_norm.dot(ray.m_direction); + if (abs(nr) < ZERO_APPROX) { + return -1; + } + + // Calculate distance + auto dist = m_norm.dot(m_start - ray.m_start) / nr; + if (dist < 0) { + return -1; + } + + return dist; +} diff --git a/src/object.hpp b/src/object.hpp index 56c6968..3194dac 100644 --- a/src/object.hpp +++ b/src/object.hpp @@ -3,17 +3,41 @@ #include <memory> #include "vector.hpp" +#include "ray.hpp" class Material { - Vec3d color; - - double defuse; - double emissive; }; class Object { public: - std::shared_ptr<Material> m; + void setMaterial(std::shared_ptr<Material> m); + + std::shared_ptr<Material> m_mat; + + virtual Vec3d norm_at(const Vec3d &point, const Vec3d &indir) const = 0; + virtual double intersect(const Ray &ray, bool skip_dist) const = 0; +}; + +class Sphere : Object { + public: + Sphere(Vec3d center, double radius); + Vec3d norm_at(const Vec3d &point, const Vec3d &indir) const; + double intersect(const Ray &ray, bool skip_dist) const; + + private: + Vec3d m_center; + double m_radius; +}; + +class Plane : Object { + public: + Plane(Vec3d start, Vec3d norm); + Vec3d norm_at(const Vec3d &point, const Vec3d &indir) const; + double intersect(const Ray &ray, bool skip_dist) const; + + private: + Vec3d m_start; + Vec3d m_norm; }; #endif diff --git a/src/ray.cpp b/src/ray.cpp new file mode 100644 index 0000000..7bc6201 --- /dev/null +++ b/src/ray.cpp @@ -0,0 +1,17 @@ +#include "ray.hpp" + +Ray::Ray(Vec3d start, Vec3d direction, bool normalize) { + m_start = start; + m_direction = direction; + + if (normalize) { + m_direction.normalize(); + } +} + +Ray::Ray(Vec3d a, Vec3d b) { + m_start = a; + m_direction = b - a; + + m_direction.normalize(); +} diff --git a/src/ray.hpp b/src/ray.hpp new file mode 100644 index 0000000..6341d44 --- /dev/null +++ b/src/ray.hpp @@ -0,0 +1,15 @@ +#ifndef RAY_H +#define RAY_H + +#include "vector.hpp" + +class Ray { + public: + Ray(Vec3d start, Vec3d direction, bool normalize); + Ray(Vec3d a, Vec3d b); + + Vec3d m_start; + Vec3d m_direction; +}; + +#endif diff --git a/src/scene.cpp b/src/scene.cpp index d65dca7..d866fe4 100644 --- a/src/scene.cpp +++ b/src/scene.cpp @@ -1,5 +1,5 @@ #include "scene.hpp" -void Scene::addObject(Object obj) { - objs.push_back(obj); +void Scene::addObject(Object &obj) { + objs.push_back(&obj); } diff --git a/src/scene.hpp b/src/scene.hpp index 6e3f33e..a06ccb4 100644 --- a/src/scene.hpp +++ b/src/scene.hpp @@ -6,9 +6,9 @@ class Scene { public: - void addObject(Object obj); + void addObject(Object &obj); - std::vector<Object> objs; + std::vector<Object*> objs; }; diff --git a/src/vector.cpp b/src/vector.cpp new file mode 100644 index 0000000..070f956 --- /dev/null +++ b/src/vector.cpp @@ -0,0 +1,53 @@ +#include "vector.hpp" + +#include <math.h> + +Vec3d::Vec3d() { + set(0, 0, 0); +} + +Vec3d::Vec3d(double x, double y, double z) { + set(x, y, z); +} + +void Vec3d::set(double x, double y, double z) { + m_x = x; + m_y = y; + m_z = z; +} + +void Vec3d::normalize() { + auto len = length(); + if (len == 0) { + throw "Normalizing zero vector"; + } + + m_x /= len; + m_y /= len; + m_z /= len; +} + +double Vec3d::length() const { + return sqrt(m_x * m_x + m_y * m_y + m_z * m_z); +} + +double Vec3d::dot(const Vec3d &vec) const { + return m_x * vec.m_x + m_y * vec.m_y + m_z * vec.m_z; +} + +Vec3d Vec3d::operator-(const Vec3d &vec) const { + return Vec3d( + m_x - vec.m_x, + m_y - vec.m_y, + m_z - vec.m_z + ); +} + +Vec3d Vec3d::operator-() const { + return Vec3d( + -m_x, + -m_y, + -m_z + ); +} + diff --git a/src/vector.hpp b/src/vector.hpp index 6d15f34..76eb883 100644 --- a/src/vector.hpp +++ b/src/vector.hpp @@ -2,17 +2,23 @@ #define VECTOR_H class Vec3d { - Vec3d(); - Vec3d(double x, double y, double z); + public: + Vec3d(); + Vec3d(double x, double y, double z); - void set(double x, double y, double z); - void normalize(); + void set(double x, double y, double z); + void normalize(); - double length(); + double length() const; + double dot(const Vec3d &vec) const; - Vec3d cross(const Vec3d &vec); + Vec3d cross(const Vec3d &vec) const; - // Operators + // Operators + Vec3d operator-(const Vec3d &vec) const; + Vec3d operator-() const; + + double m_x, m_y, m_z; }; #endif diff --git a/test/main.cpp b/test/main.cpp new file mode 100644 index 0000000..4ed06df --- /dev/null +++ b/test/main.cpp @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN +#include <catch2/catch.hpp> diff --git a/test/object.cpp b/test/object.cpp new file mode 100644 index 0000000..bbce055 --- /dev/null +++ b/test/object.cpp @@ -0,0 +1,36 @@ +#include <object.hpp> +#include <ray.hpp> +#include <common.hpp> +#include <vector.hpp> + +#include <catch2/catch.hpp> +#include <math.h> + +TEST_CASE("Sphere normal at", "[sphere]") { + auto sph = Sphere(Vec3d(2, 3, 4), 2); + + auto norm = sph.norm_at(Vec3d(2, 3, 2), Vec3d()); + REQUIRE(norm.m_x == 0); + REQUIRE(norm.m_y == 0); + REQUIRE(norm.m_z == -1); +} + +TEST_CASE("Sphere intersect", "[sphere]") { + auto sph = Sphere(Vec3d(2, 3, 4), 2); + auto ray = Ray(Vec3d(1, 0, 0), Vec3d(0, 1, 1.5), true); + + auto dist = sph.intersect(ray, false); + REQUIRE(abs(dist - 3.28) < 0.01); +} + +TEST_CASE("Plane intersect", "[plane]") { + auto pln = Plane(Vec3d(3, 4, 2), Vec3d(-6, -3, -2)); + auto ray = Ray(Vec3d(0, 0, 0), Vec3d(-2, -1, 5)); + + auto dist = pln.intersect(ray, false); + REQUIRE(dist == -1); + + ray = Ray(Vec3d(-2, -2, 0), Vec3d(-2, -1, 5)); + dist = pln.intersect(ray, false); + REQUIRE(abs(dist - 20.4) < 0.1); +} diff --git a/test/vector.cpp b/test/vector.cpp new file mode 100644 index 0000000..61648c6 --- /dev/null +++ b/test/vector.cpp @@ -0,0 +1,31 @@ +#include <vector.hpp> +#include <common.hpp> +#include <catch2/catch.hpp> + +TEST_CASE( "Vector length", "[vector]" ) { + auto vec = Vec3d(2, 4, 4); + REQUIRE(vec.length() == 6); + vec.set(0, 0, 0); + REQUIRE(vec.length() == 0); + vec.set(0, 3.5, 0); + REQUIRE(vec.length() == 3.5); +} + +TEST_CASE("Vector_normal", "[vector]") { + auto vec = Vec3d(4, 5, 4545); + REQUIRE(vec.length() != 1.0); + vec.normalize(); + REQUIRE(vec.length() - 1.0 < ZERO_APPROX); + vec.set(0, 0, 0); + REQUIRE_THROWS(vec.normalize()); +} + +TEST_CASE("Vector dot", "[vector]") { + auto a = Vec3d(4, 5, 6); + auto b = Vec3d(1, 2, 3); + REQUIRE(a.dot(b) == 32); + a.set(0, 0, 0); + REQUIRE(a.dot(b) == 0); + a.set(0, 5, 0); + REQUIRE(a.dot(b) == 10); +} |