summaryrefslogtreecommitdiff
path: root/hitlist.h
blob: 1ad040ab39b2c5e8645bf215f67424ffe1cb2bf2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
//
// Created by Keuin on 2022/4/12.
//

#ifndef RT_HITLIST_H
#define RT_HITLIST_H

#include "viewport.h"
#include "timer.h"
#include "bitmap.h"
#include "ray.h"
#include "vec.h"
#include "object.h"
#include <cstdlib>
#include <memory>
#include <limits>
#include <vector>
#include <iostream>
#include <cstdint>

//#define T_SIMPLE_COLOR
//#define T_NORM_VISUAL
#define T_DIFFUSE

// log ray traces to stderr
//#define LOG_TRACE

#ifdef LOG_TRACE
#define TRACELOG(...) (fprintf(stderr, __VA_ARGS__))
#else
#define TRACELOG(...)
#endif

// A world, T is color depth
class hitlist {
    std::vector<std::shared_ptr<object>> objects;

public:
    hitlist() = default;

    hitlist(hitlist &other) = delete; // do not copy the world

    // Add an object to the world.
    void add_object(std::shared_ptr<object> &&obj) {
        objects.push_back(std::move(obj));
    }

    // Given a ray, compute the color.
    template<typename T>
    pixel<T> color(ray3d r, random_uv_gen_3d &ruvg, uint_fast32_t max_recursion_depth = 64) const {
        assert(r.decay().is_one());
        TRACELOG("+++ start tracing (limit=%zu)\n", max_recursion_depth);
        TRACELOG("    ray: [%10f,%10f,%10f], decay=[%10f,%10f,%10f]\n",
                 r.direction().x, r.direction().y, r.direction().z, r.decay().x, r.decay().y, r.decay().z);
        while (max_recursion_depth-- > 0) {
            // Detect hits
            bool hit = false;
            double hit_t = std::numeric_limits<double>::infinity();
            std::shared_ptr<object> hit_obj;
            // Check the nearest object we hit
            for (const auto &obj: objects) {
                double t_;
                // Fix the Shadow Acne problem
                // Some diffused rays starts off from a point on the surface,
                // while hit the surface very quickly at some small t like +-1e-10 or so.
                // This decays the ray by accident, causing black pixels on the output image.
                // We simply drop hits very near the surface
                // (i.e. t is a very small positive number) to solve this problem.
                static constexpr double t_min = 1e-8;
                if (obj->hit(r, t_, t_min) && t_ < hit_t) {
                    hit = true;
                    hit_t = t_;
                    hit_obj = obj;
                }
            }
            if (hit) {
#ifdef LOG_TRACE
                const auto _hit_p_ = r.at(hit_t);
                const auto _rn_ = dot(r.direction(), hit_obj->normal_vector(_hit_p_));
                TRACELOG("    hit object <%p> at [%10f,%10f,%10f], t=%10f, surface=%10f (%s)\n",
                         hit_obj.get(), _hit_p_.x, _hit_p_.y, _hit_p_.z,
                         hit_t, _rn_, (_rn_ < 0) ? "outside" : "inside");
#endif
#ifdef T_SIMPLE_COLOR
                // simply returns color of the object
                return hit_obj->color();
#endif
#ifdef T_NORM_VISUAL
                // normal vector on hit point
                const auto nv = hit_obj->normal_vector(r.at(hit_t));
                // visualize normal vector at hit point
                return pixel<T>::from_normalized(nv);
#endif
#ifdef T_DIFFUSE
                const auto &materi = hit_obj->material();
                if (materi.scatter(r, *hit_obj, hit_t, ruvg)) {
                    TRACELOG("    scattered: [%10f,%10f,%10f], decay=[%10f,%10f,%10f]\n",
                             r.direction().x, r.direction().y, r.direction().z,
                             r.decay().x, r.decay().y, r.decay().z);
                    continue; // The ray is scatted by an object. Continue processing the scattered ray.
                } else {
                    TRACELOG("--- finish (absorbed by object <%p>)\n", hit_obj.get());
                    return pixel<T>::black(); // The ray is absorbed by an object completely. Return black.
                }
#endif
            }

            // Does not hit anything. Get background color (infinity)
            const auto u = (r.direction().y + 1.0) * 0.5;
            const auto c = mix(
                    pixel<T>::from_normalized(1.0, 1.0, 1.0),
                    pixel<T>::from_normalized(0.5, 0.7, 1.0),
                    1.0 - u,
                    u
            );
#ifdef T_DIFFUSE
#ifdef LOG_TRACE
            const auto co = pixel8b::from(r.hit(c));
            TRACELOG("    result: [%d, %d, %d]\n", co.r, co.g, co.b);
            TRACELOG("--- finish (reached infinity)\n");
#endif
            return r.hit(c);
#else
            TRACELOG("--- finish (hit)\n");
            return c;
#endif
        }
        TRACELOG("--- finish (max search depth)\n");
        return pixel<T>::black(); // reached recursion time limit, very little light
    }
};

#endif //RT_HITLIST_H