Skip to content

Instantly share code, notes, and snippets.

@branw
Created May 21, 2020 17:39
Show Gist options
  • Save branw/e170a5c31bf62aceb5d5255fe9648704 to your computer and use it in GitHub Desktop.
Save branw/e170a5c31bf62aceb5d5255fe9648704 to your computer and use it in GitHub Desktop.
Basic C++17 ray-tracer that renders onto Notepad's multiline edit control
#include <Windows.h>
#include <iostream>
#include <sstream>
#include <tuple>
#include <optional>
#include <cmath>
#include <string>
#include <thread>
#include <chrono>
// Frames per second
// Noticible flickering above 25 FPS
double const fps = 25;
// Number of columns
double const width = 80;
// Number of rows
double const height = 40;
// Field of view in degrees
auto const fov = 40;
// ASCII characters representing cells from dark to light
// From http://paulbourke.net/dataformats/asciiart/
char const shades[] = "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\\|()1{}[]?-_+~<>i!lI;:,\" ^ `'. ";
struct Vector3 {
double x, y, z;
Vector3(double x = 0, double y = 0, double z = 0) : x{ x }, y{ y }, z{ z } {}
double length() const {
return sqrt(x * x + y * y + z * z);
}
void normalize() {
auto len = length();
x /= len;
y /= len;
z /= len;
}
double dot(Vector3 other) const {
return x * other.x + y * other.y + z * other.z;
}
Vector3 cross(Vector3 other) const {
return {
y * other.z - z * other.y,
z * other.x - x * other.z,
x * other.y - y * other.x
};
}
Vector3 operator+(Vector3 other) const {
return { x + other.x, y + other.y, z + other.z };
}
Vector3 operator-(Vector3 other) const {
return { x - other.x, y - other.y, z - other.z };
}
Vector3 operator*(double other) const {
return { x * other, y * other, z * other };
}
};
struct Ray {
Vector3 position;
Vector3 direction;
Ray(Vector3 position, Vector3 direction) : position{ position }, direction{ direction } {
this->direction.normalize();
}
};
struct Sphere {
Vector3 position;
double radius;
std::optional<Ray> intersect(Ray ray) const {
// Create a cross-section of the sphere co-planar to the ray
auto v = position - ray.position;
auto b = v.dot(ray.direction);
auto d2 = b * b - v.dot(v) + radius * radius;
// No intersection
if (d2 < 0) return {};
// Two possible solutions to the quadratic: pick closest
auto d = sqrt(d2);
auto t0 = b - d, t1 = b + d;
auto t = (t0 > 0 ? t0 : t1);
auto intersection_pos = ray.position + ray.direction * t;
return Ray{ intersection_pos, intersection_pos - position };
}
};
auto const pi = atan(1) * 4;
auto const fov_factor = tan((fov * (pi / 180)) / 2);
auto const aspect_ratio = (width / height) / 2;
int main() {
// Launch Notepad
char cmd[256] = { 0 };
snprintf(cmd, sizeof(cmd), "notepad");
PROCESS_INFORMATION proc_info = { 0 };
STARTUPINFOA startup_info = { 0 };
startup_info.cb = sizeof(STARTUPINFO);
if (!CreateProcessA(nullptr, cmd, NULL, NULL, TRUE, NULL, NULL, NULL,
&startup_info, &proc_info)) {
return 1;
}
CloseHandle(proc_info.hThread);
auto desired_pid = GetProcessId(proc_info.hProcess);
// Find the Notepad edit control
HWND window_handle = nullptr, edit_control_handle = nullptr;
for (;;) {
auto tuple = std::make_tuple(desired_pid, &window_handle, &edit_control_handle);
if (!EnumWindows([](HWND hWnd, LPARAM lParam) -> BOOL {
auto [desired_pid, hWndParent, hWndEdit] = *reinterpret_cast<decltype(&tuple)>(lParam);
// Skip other processes
DWORD pid = 0;
GetWindowThreadProcessId(hWnd, &pid);
if (pid != desired_pid) return TRUE;
// Try to find the "Edit" control
*hWndEdit = FindWindowExA(hWnd, NULL, "Edit", NULL);
if (*hWndEdit == NULL) return TRUE;
// Save the parent's handle so we can resize later
*hWndParent = hWnd;
return FALSE;
}, reinterpret_cast<LPARAM>(&tuple))) break;
}
Vector3 eye_pos{ 5, 0, 0 };
auto time = 0.f;
auto time_step = 1 / fps;
std::string buffer;
for (;;) {
// Check if Notepad was closed
DWORD exit_code = 0;
if (!GetExitCodeProcess(proc_info.hProcess, &exit_code) || exit_code != STILL_ACTIVE) {
return 0;
}
// Move the light source around
auto light_pos = Vector3{
10,//cos(time * pi * 2) * 10,
cos(time * pi * 3) * 10,
5 };//sin(time * pi * 2) * 10 };
// Pan the ball
Sphere ball{ Vector3{ 0, 10 * cos(time * pi), 5 }, 1 };
// Calculate eye angles
auto eye_dir = ball.position - eye_pos;
eye_dir.normalize();
auto eye_right = eye_dir.cross(Vector3{ 0, -1, 0 });
eye_right.normalize();
auto eye_up = eye_right.cross(eye_dir);
eye_up.normalize();
// Ray trace
for (auto y = 0; y < height; y++) {
for (auto x = 0; x < width; x++) {
auto look_pos = eye_pos + eye_dir +
eye_right * fov_factor * aspect_ratio * (x / width - 0.5) +
eye_up * fov_factor * (y / height - 0.5);
Ray test_ray{ eye_pos, look_pos - eye_pos };
if (auto intersection = ball.intersect(test_ray)) {
auto light_dir = light_pos - intersection->position;
light_dir.normalize();
double diffuse = intersection->direction.dot(light_dir);
if (diffuse <= 0) {
diffuse = 0;
}
int shade_index = (diffuse * diffuse * diffuse) * (sizeof(shades) - 1);
buffer += shades[shade_index];
}
else {
buffer += ' ';
}
}
buffer += '\n';
}
// Write the buffer to Notepad
SendMessageA(edit_control_handle, WM_SETTEXT,
buffer.length(), reinterpret_cast<LPARAM>(buffer.c_str()));
buffer.clear();
time += time_step;
std::this_thread::sleep_for(std::chrono::milliseconds(static_cast<int>(time_step * 1000)));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment