#include <chrono>
#include <iomanip>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
enum class Scene { ROTATE_BH, STATIC_BH, WORMHOLE };
static constexpr Scene scene = Scene::ROTATE_BH;
static constexpr double M = 0.5;
static constexpr double J = 0.249;
static constexpr double b = 3.0;
void status_bar(int64_t current, int64_t total, const std::string& start_info) {
auto precision = std::cout.precision();
static auto prev_time = std::chrono::high_resolution_clock::now();
static auto prev = current - 1;
static auto prev2 = prev;
static auto prev2_time = prev_time;
auto curr_time = std::chrono::high_resolution_clock::now();
double percent = 100.0 * (double)(current + 1) / (double)total;
std::string str = "[";
for (int i = 0; i < 50; ++i) {
if (percent >= i * 2)
str += "=";
else
str += " ";
}
str += "]";
auto time =
current != prev
? (total - current) * (curr_time - prev_time) / (current - prev)
: (total - current) * (curr_time - prev2_time) / (current - prev2);
if (current != prev && prev != prev2) {
prev2 = prev;
prev2_time = prev_time;
}
prev = current;
prev_time = curr_time;
if (current != total) {
using namespace std::chrono_literals;
std::cout << start_info << " " << std::fixed << std::setprecision(1)
<< percent << "% " << str << " Time Remaining: ";
if (std::chrono::duration_cast<std::chrono::seconds>(time).
count() >
300)
std::cout << std::chrono::duration_cast<std::chrono::minutes>(time)
.count()
<< " min";
else
std::cout << std::chrono::duration_cast<std::chrono::seconds>(time)
.count()
<< " s";
std::cout << std::string(5, ' ') << '\r';
} else
std::cout << "\rDone!" << std::string(120, ' ') << std::endl;
std::cout << std::setprecision(precision) << std::defaultfloat;
}
}
}
std::cout << string << std::endl;
}
double radians(
double degrees) {
return degrees *
af::Pi / 180.0; }
throw make_error("Arrays must have the same dimensions");
else if (lhs.
dims()[0] != 3)
throw make_error("Arrays must have 3 principal coordintes");
0,
}
throw make_error("Arrays must have 3 principal coordintes");
return transformed_pos;
}
throw make_error("Arrays must have the same dimensions");
else if (pos.
dims()[0] != 3)
throw make_error("Arrays must have 3 principal coordintes");
af::array ur = (ux * x + uy * y + uz * z) / r;
r;
return transformed_vel;
}
throw make_error("Arrays must have the same dimensions");
else if (pos.
dims()[0] != 3)
throw make_error("Arrays must have 3 principal coordintes");
return transformed_vel;
}
throw make_error("Arrays must have 3 principal coordintes");
auto a = J / M;
auto diff = x * x + y * y + z * z - a * a;
return transformed_pos;
}
throw make_error("Arrays must have 3 principal coordintes");
auto a = J / M;
return transformed_pos;
}
throw make_error("Arrays must have the same dimensions");
else if (pos.
dims()[0] != 3)
throw make_error("Arrays must have 3 principal coordintes");
double a = J / M;
return transformed_vel;
}
throw make_error("Arrays must have the same dimensions");
else if (pos.
dims()[0] != 3)
throw make_error("Arrays must have 3 principal coordintes");
auto a = J / M;
auto diff = x * x + y * y + z * z - a * a;
af::array ur = ((ux * x + uy * y) * r + uz * ra * z / r) /
af::array up = (uy * x - ux * y) / (x * x + y * y);
return transformed_vel;
}
return transformed_pos;
}
return res;
}
throw make_error("Arrays must have 4 principal coordinates");
af::array gtt, gtr, gto, gtp, grt, grr, gro, grp, got, gor, goo, gop, gpt,
gpr, gpo, gpp;
switch (scene) {
case Scene::ROTATE_BH: {
auto rs = 2.0 * M;
auto a = J / M;
auto delta = (r - rs) * r + a * a;
gtt = 1.0 - r * rs / sigma;
grr = -sigma / delta;
goo = -sigma;
gpp =
break;
}
case Scene::STATIC_BH: {
gtt = 1.0 - 2.0 * M / r;
grr = -1.0 / (1.0 - 2.0 * M / r);
goo = -r * r;
break;
}
case Scene::WORMHOLE: {
goo = -(r * r + b * b);
break;
}
default: throw;
}
0,
af::join(1, gtt, gtr, gto, gtp),
af::join(1, gtr, grr, gro, grp),
return res;
}
throw make_error(
"Position and lhs velocity must have the same dimensions");
throw make_error(
"Position and rhs velocity must have the same dimensions");
else if (rhs.
dims()[0] != 4)
throw make_error("Arrays must have 4 principal coordinates");
}
return dot_product(pos, vel, vel);
}
double abs_diff) {
double arr[4] = {0.0};
arr[index] = 1.0;
auto pos_diff = pos4 * rel_diff + abs_diff;
return (-metric4(pos4 + h4 * 2.0) + metric4(pos4 + h4) * 8.0 -
metric4(pos4 - h4) * 8.0 + metric4(pos4 - h4 * 2.0)) /
(h * 12.0);
}
auto dr = partials(pos4, 1, 1e-6, 1e-12);
auto dtheta = partials(pos4, 2, 1e-6, 1e-12);
auto dphi = partials(pos4, 3, 1e-6, 1e-12);
auto christoffels = -0.5 * (p1 + p2 - p3);
}
struct Camera {
double fov;
double focal_length;
uint32_t width;
uint32_t height;
double aspect_ratio;
double focal_length_, uint32_t viewport_width_,
uint32_t viewport_height_)
: position(position_)
, lookat(lookat_)
, fov(fov_)
, focal_length(focal_length_)
, width(viewport_width_)
, height(viewport_height_) {
auto global_vertical =
af::array(3, {0.0, 0.0, 1.0});
direction = normalize3(lookat - position);
horizontal = normalize3(cross_product(direction, global_vertical));
vertical = normalize3(cross_product(direction, horizontal));
aspect_ratio = (double)width / (double)height;
}
std::pair<af::array, af::array> generate_viewport_4rays() {
auto& camera_direction = direction;
auto& camera_horizontal = horizontal;
auto& camera_vertical = vertical;
auto& camera_position = position;
auto vfov = fov;
double viewport_height = 2.0 * focal_length * std::tan(vfov / 2.0);
double viewport_width = aspect_ratio * viewport_height;
viewport_rays +=
(width - 1) -
0.5) *
viewport_width * camera_horizontal;
viewport_rays +=
(height - 1) -
0.5) *
viewport_height * camera_vertical;
viewport_rays += focal_length * camera_direction;
af::array viewport_position = viewport_rays + camera_position;
if (scene != Scene::ROTATE_BH)
viewport_sph_pos = cart_to_sph_position(viewport_position);
else
viewport_sph_pos = cart_to_oblate_position(viewport_position);
viewport_rays = normalize3(viewport_rays);
if (scene != Scene::ROTATE_BH)
camera_sph_pos = cart_to_sph_position(camera_position);
else
camera_sph_pos = cart_to_oblate_position(camera_position);
double camera_velocity =
1.0 /
if (scene != Scene::ROTATE_BH)
vv = cart_to_sph_velocity(viewport_rays, viewport_position);
else
vv = cart_to_oblate_velocity(viewport_rays, viewport_position);
auto viewport_sph_rays4 =
vv * viewport_vel * camera_velocity);
return {viewport_rays_pos4, viewport_rays_vel4};
}
};
struct Object {
virtual std::pair<HasHit, HitPos> intersect(
};
struct AccretionDisk : public Object {
double inner_radius;
double outter_radius;
double inner_radius, double outter_radius)
: disk_color(
af::array(3, {209.f, 77.f, 0.f}))
, center(center)
, normal(normal)
, inner_radius(inner_radius)
, outter_radius(outter_radius) {
}
std::pair<HasHit, HitPos> intersect(
af::array a = dot3(normal, center - ray_begin);
af::array b = dot3(normal, ray_end - ray_begin);
af::array plane_intersect = (ray_end - ray_begin) * t + ray_begin;
af::array dist = norm3(plane_intersect - center);
has_hit =
af::moddims((dist < outter_radius) && (t <= 1.0) &&
(t > 0.0) && (dist > inner_radius),
hit_pos = plane_intersect;
return {has_hit, hit_pos};
}
auto pair = intersect(ray_begin, ray_end);
auto val = 1.f - (norm3(pos - center).T() - inner_radius) /
(outter_radius - inner_radius);
disk_color.
T() * 1.5f * (val * val * (val * -2.f + 3.f)).
as(
f32);
}
};
struct Background {
Background(
const af::array& image_) { image = image_; }
auto spherical_dir = cart_to_sph_position(ray_dir);
auto img_height = image.
dims()[0];
auto img_width = image.
dims()[1];
auto x = (p /
af::Pi + 1.0) * img_width / 2.0;
auto y = (o /
af::Pi) * img_height;
af::interpType::AF_INTERP_CUBIC_SPLINE);
return colors;
}
};
uint32_t height) {
255.0)
255.f;
}
const std::vector<std::unique_ptr<Object> >& objects,
const Background& background, uint32_t width,
uint32_t height, double time, double tol,
uint32_t checks = 10) {
uint32_t lines = initial_pos.
dims()[1];
auto def_step = 0.5 *
pow(tol, 0.25);
auto selected = t < time;
auto pos = initial_pos;
auto vel = initial_vel;
af::Window window{(int)width, (
int)height,
"Black Hole Raytracing"};
if (scene != Scene::ROTATE_BH)
else
end_pos = begin_pos;
int i = 0;
while (t.
dims()[1] != 0 && af::anyTrue<bool>(t < time) &&
af::anyTrue<bool>(dt != 0.0)) {
status_bar((lines - t.
dims()[1]) * time +
time * lines, "Progress:");
auto dt2 = dt * dt;
auto k1 = geodesics(pos, vel);
auto k2 = geodesics(pos + vel * dt / 4.0 + k1 * dt2 / 32.0,
vel + k1 * dt / 4.0);
auto k3 = geodesics(pos + vel * dt / 2.0 + (k1 + k2) * dt2 / 16.0,
vel + k2 * dt / 2.0);
auto k4 = geodesics(pos + vel * dt + (k1 - k2 + k3 * 2.0) * dt2 / 4.0,
vel + (k1 - k2 * 2.0 + 2.0 * k3) * dt);
auto diff4 = (k1 + k2 * 8.0 + k3 * 2.0 + k4) / 24.0;
auto diff3 = (k2 * 8.0 + k4) / 18.0;
auto rdt2 = rdt * rdt;
pos += vel * rdt + (k1 + k2 * 8.0 + k3 * 2.0 + k4) * rdt2 / 24.0;
vel += (k1 + k3 * 4.0 + k4) * rdt / 6.0;
t += rdt;
if (i % checks == (checks - 1)) {
if (scene != Scene::ROTATE_BH) {
} else {
}
for (const auto& obj : objects) {
obj->get_color(s_begin_pos, s_end_pos);
}
bg_col(index,
af::span) = background.get_color(ray_dir);
window.image(rearrange_image(result + bg_col, width, height));
begin_pos = end_pos;
}
switch (scene) {
case Scene::ROTATE_BH: {
auto a = J / M;
bh_nohit =
(pos(1,
af::span) > 1.01 * (M + std::sqrt(M * M - a * a)));
selected = bh_nohit && (t < time);
break;
}
case Scene::STATIC_BH: {
bh_nohit = pos(1,
af::span) > 2.0 * M * 1.01;
selected = bh_nohit && (t < time);
break;
}
case Scene::WORMHOLE: {
selected = (t < time);
}
default: break;
}
if (af::sum<float>(selected.as(
f32)) / (
float)index.dims()[0] < 0.75) {
if (scene == Scene::STATIC_BH || scene == Scene::ROTATE_BH)
index = index(selected);
}
++i;
}
result += bg_col;
return rearrange_image(result, width, height);
}
void raytracing(uint32_t width, uint32_t height) {
double vfov = radians(90.0);
double focal_length = 0.01;
double accretion_inner_radius = M * 3.0;
double accretion_outter_radius = M * 8.0;
double simulation_tolerance = 1e-6;
double max_simulation_time = 12.;
uint32_t num_steps_per_collide_check = 1;
auto bg_image =
af::loadimage(ASSETS_DIR
"/examples/images/westerlund.jpg",
true);
auto background = Background(bg_image);
std::vector<std::unique_ptr<Object> > objects;
if (scene != Scene::WORMHOLE)
objects.push_back(std::make_unique<AccretionDisk>(
accretion_inner_radius, accretion_outter_radius));
auto camera = Camera(camera_position, camera_lookat, vfov, focal_length,
width, height);
auto pair = camera.generate_viewport_4rays();
auto ray4_pos = pair.first;
auto ray4_vel = pair.second;
auto begin = std::chrono::high_resolution_clock::now();
auto image = generate_image(
ray4_pos, ray4_vel, objects, background, width, height,
max_simulation_time, simulation_tolerance, num_steps_per_collide_check);
auto end = std::chrono::high_resolution_clock::now();
std::cout
<< "\nSimulation took: "
<< std::chrono::duration_cast<std::chrono::seconds>(end - begin).count()
<< " s" << std::endl;
}
int main(int argc, char** argv) {
int device = argc > 1 ? std::atoi(argv[1]) : 0;
int width = argc > 2 ? std::atoi(argv[2]) : 200;
int height = argc > 3 ? std::atoi(argv[3]) : 200;
try {
std::cout << "** ArrayFire Black Hole Raytracing Demo\n\n";
raytracing(width, height);
std::cerr << e.
what() << std::endl;
return -1;
}
return 0;
}
Window object to render af::arrays.
A multi dimensional data container.
T scalar() const
Get the first element of the array as a scalar.
dim4 dims() const
Get dimensions of the array.
const array as(dtype type) const
Casts the array into another data type.
array T() const
Get the transposed the array.
Generic object that represents size and shape.
An ArrayFire exception class.
virtual const char * what() const
Returns an error message for the exception in a string format.
seq is used to create sequences for indexing af::array
@ f32
32-bit floating point values
@ f64
64-bit floating point values
AFAPI array abs(const array &in)
C++ Interface to calculate the absolute value.
AFAPI array acos(const array &in)
C++ Interface to evaluate the inverse cosine function.
AFAPI array atan2(const array &lhs, const array &rhs)
C++ Interface to evaluate the inverse tangent of two arrays.
AFAPI array clamp(const array &in, const array &lo, const array &hi)
AFAPI array cos(const array &in)
C++ Interface to evaluate the cosine function.
AFAPI array isNaN(const array &in)
C++ Interface to check if values are NaN.
AFAPI array pow(const array &base, const array &exponent)
C++ Interface to raise a base to a power (or exponent).
AFAPI array sin(const array &in)
C++ Interface to evaluate the sine function.
AFAPI array sqrt(const array &in)
C++ Interface to evaluate the square root.
AFAPI array tan(const array &in)
C++ Interface to evaluate the tangent function.
T dot(const array &lhs, const array &rhs, const matProp optLhs=AF_MAT_NONE, const matProp optRhs=AF_MAT_NONE)
C++ Interface to compute the dot product.
AFAPI array matmul(const array &lhs, const array &rhs, const matProp optLhs=AF_MAT_NONE, const matProp optRhs=AF_MAT_NONE)
C++ Interface to multiply two matrices.
array constant(T val, const dim4 &dims, const dtype ty=(af_dtype) dtype_traits< T >::ctype)
C++ Interface to generate an array with elements set to a specified value.
AFAPI array iota(const dim4 &dims, const dim4 &tile_dims=dim4(1), const dtype ty=f32)
C++ Interface to generate an array with [0, n-1] values modified to specified dimensions and tiling.
AFAPI void replace(array &a, const array &cond, const array &b)
C++ Interface to replace elements of an array with elements of another array.
AFAPI array select(const array &cond, const array &a, const array &b)
C++ Interface to select elements based on a conditional array.
AFAPI void deviceGC()
Call the garbage collection function in the memory manager.
AFAPI void setDevice(const int device)
Sets the current device.
AFAPI array loadimage(const char *filename, const bool is_color=false)
AFAPI void saveImage(const char *filename, const array &in)
C++ Interface for saving an image.
T det(const array &in)
C++ Interface to find the determinant of a matrix.
AFAPI array join(const int dim, const array &first, const array &second)
C++ Interface to join 2 arrays along a dimension.
AFAPI array moddims(const array &in, const dim4 &dims)
C++ Interface to modify the dimensions of an input array to a specified shape.
AFAPI array reorder(const array &in, const unsigned x, const unsigned y=1, const unsigned z=2, const unsigned w=3)
C++ Interface to reorder an array.
AFAPI array tile(const array &in, const unsigned x, const unsigned y=1, const unsigned z=1, const unsigned w=1)
C++ Interface to generate a tiled array.
AFAPI array count(const array &in, const int dim=-1)
C++ Interface to count non-zero values in an array along a given dimension.
AFAPI array max(const array &in, const int dim=-1)
C++ Interface to return the maximum along a given dimension.
AFAPI array sum(const array &in, const int dim=-1)
C++ Interface to sum array elements over a given dimension.
AFAPI array approx2(const array &in, const array &pos0, const array &pos1, const interpType method=AF_INTERP_LINEAR, const float off_grid=0.0f)
C++ Interface for data interpolation on two-dimensional signals.
AFAPI int end
A special value representing the last value of an axis.
AFAPI seq span
A special value representing the entire axis of an af::array.