Skip to content

Instantly share code, notes, and snippets.

@snipsnipsnip
Last active August 24, 2019 16:11
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save snipsnipsnip/1369770 to your computer and use it in GitHub Desktop.
SDLでレイトレーサもどき
#include "SDL.h"
#include <windows.h>
#include <process.h>
#include <stdexcept>
#include <algorithm>
#include <functional>
#include <memory>
#include <vector>
#include <cstdarg>
#include <cmath>
#include <cctype>
#include <cstdlib>
#include <ctime>
/*
left drag: move position
left double click: jump position
right drag: shift initial c
right click: change mode
right click + wheel up/down: change mode
left click + wheel up/down: adjust iterations
wheel up/down: zoom
wheel drag: zoom
wheel click: reset
*/
#ifndef M_PI
# define M_PI (atan(1.0) * 4)
#endif
#ifdef _MSC_VER
# define LOG(...) print_log('L', __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__)
#else
# define LOG(...) print_log('L', __FILE__, __LINE__, __func__, __VA_ARGS__)
#endif
void print_log(char level, const char *file, unsigned int line, const char *func, const char *format, ...)
{
va_list args;
// fprintf(stderr, "[%c]", level);
fprintf(stderr, "%s(%d)%s ", file, line, func);
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
fputc('\n', stderr);
}
class Thread
{
HANDLE thread;
unsigned int id;
public:
template <typename T>
explicit Thread(T *t)
{
thread = reinterpret_cast<HANDLE>(_beginthreadex(0, 0, call<T>, t, CREATE_SUSPENDED, &id));
if (thread == NULL)
{
throw std::runtime_error("couldn't create thread");
}
}
~Thread()
{
CloseHandle(thread);
}
bool start()
{
return thread != NULL && ResumeThread(thread) != 0;
}
bool join()
{
return thread != NULL && GetCurrentThreadId() != id && WaitForSingleObject(thread, INFINITE) == WAIT_OBJECT_0;
}
private:
Thread(const Thread &);
void operator =(const Thread &);
template <typename T>
static unsigned int __stdcall call(void *param)
{
(*static_cast<T *>(param))();
return 0;
}
};
typedef double VectorElem;
struct Vector
{
VectorElem x, y, z;
Vector() : x(0), y(0), z(0) {}
explicit Vector(VectorElem e) : x(e), y(e), z(e) {}
Vector(const Vector &v) : x(v.x), y(v.y), z(v.z) {}
Vector(VectorElem x, VectorElem y) : x(x), y(y), z(0) {}
Vector(VectorElem x, VectorElem y, VectorElem z) : x(x), y(y), z(z) {}
bool operator ==(const Vector &v) const
{
return x == v.x && y == v.y && z == v.z;
}
bool operator !=(const Vector &v) const
{
return !(*this == v);
}
// ���[�v�p�B
// v[0] == v[X] == v.x
// v[1] == v[Y] == v.y
// v[2] == v[Z] == v.z
const VectorElem &operator [](int i) const
{
return ((VectorElem *)this)[i];
}
VectorElem &operator [](int i)
{
return ((VectorElem *)this)[i];
}
VectorElem norm() const
{
return *this * *this;
}
VectorElem len() const
{
return sqrt(norm());
}
Vector &rotate_x(VectorElem rad)
{
VectorElem c = cos(rad);
VectorElem s = sin(rad);
VectorElem vy = y;
y = c * vy - s * z;
z = s * vy + c * z;
return *this;
}
Vector &rotate_y(VectorElem rad)
{
VectorElem c = cos(rad);
VectorElem s = sin(rad);
VectorElem vx = x;
x = c * vx + s * z;
z = -s * vx + c * z;
return *this;
}
Vector &normalize()
{
VectorElem n = norm();
if (n > 0) { *this /= sqrt(n); }
return *this;
}
Vector normalized() const
{
return Vector(*this).normalize();
}
void cut()
{
if (x < 0) { x = 0; }
if (y < 0) { y = 0; }
if (z < 0) { z = 0; }
}
// �l�����Z
Vector &operator +=(const Vector &b)
{
x += b.x;
y += b.y;
z += b.z;
return *this;
}
Vector &operator -=(const Vector &b)
{
x -= b.x;
y -= b.y;
z -= b.z;
return *this;
}
Vector &operator *=(VectorElem s)
{
x *= s;
y *= s;
z *= s;
return *this;
}
Vector &operator /=(VectorElem s)
{
x /= s;
y /= s;
z /= s;
return *this;
}
Vector operator +(const Vector &b) const
{
return Vector(*this) += b;
}
Vector operator -(const Vector &b) const
{
return Vector(*this) -= b;
}
Vector operator *(VectorElem s) const
{
return Vector(*this) *= s;
}
Vector operator /(VectorElem s) const
{
return Vector(*this) /= s;
}
Vector operator +() const
{
return *this;
}
Vector operator -() const
{
return *this * -1;
}
// ����
VectorElem operator *(const Vector &b) const
{
return x * b.x + y * b.y + z * b.z;
}
// �O��
Vector operator ^(const Vector &b) const
{
return Vector(y * b.z - z * b.y, z * b.x - x * b.z, x * b.y - y * b.x);
}
// �S���t���b�g�Ɋ|����
Vector operator %(const Vector &b) const
{
return Vector(x * b.x, y * b.y, z * b.z);
}
};
inline Vector operator *(VectorElem s, const Vector &v)
{
return v * s;
}
inline Vector operator /(VectorElem s, const Vector &v)
{
return Vector(v.x == 0 ? 0 : s / v.x, v.y == 0 ? 0 : s / v.y, v.z == 0 ? 0 : s / v.z);
}
void draw_dot(SDL_Surface *screen, int x, int y, Uint8 r, Uint8 g, Uint8 b)
{
Uint32 pixel = SDL_MapRGB(screen->format, r, g, b);
int bpp = screen->format->BytesPerPixel;
Uint8 *p = ((Uint8 *)screen->pixels) + y * screen->pitch + x * bpp;
switch (bpp)
{
case 1:
*p = pixel;
break;
case 2:
*(Uint16 *)p = pixel;
break;
case 3:
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
p[0] = (pixel >> 16) & 0xff;
p[1] = (pixel >> 8) & 0xff;
p[2] = pixel & 0xff;
#else
p[0] = pixel & 0xff;
p[1] = (pixel >> 8) & 0xff;
p[2] = (pixel >> 16) & 0xff;
#endif
break;
case 4:
*(Uint32 *)p = pixel;
break;
}
}
void draw_dot_clipped(SDL_Surface *screen, int x, int y, Uint8 r, Uint8 g, Uint8 b)
{
if (screen->w <= x || screen->h <= y) { return; }
draw_dot(screen, x, y, r, g, b);
}
/*
5x5 bitmap font by shinh (http://d.hatena.ne.jp/shinichiro_h/20060814#1155567183)
Licensed under the New BSD License.
*/
void draw_letter(SDL_Surface *screen, int x, int y, char c)
{
static const Uint32 font[] =
{
0x00000000, 0x00401084, 0x0000014a, 0x00afabea, 0x01fa7cbf, 0x01111111,
0x0126c8a2, 0x00000084, 0x00821088, 0x00221082, 0x015711d5, 0x00427c84,
0x00220000, 0x00007c00, 0x00400000, 0x00111110, 0x00e9d72e, 0x00421084,
0x01f2222e, 0x00e8b22e, 0x008fa54c, 0x01f87c3f, 0x00e8bc2e, 0x0042221f,
0x00e8ba2e, 0x00e87a2e, 0x00020080, 0x00220080, 0x00820888, 0x000f83e0,
0x00222082, 0x0042222e, 0x00ead72e, 0x011fc544, 0x00f8be2f, 0x00e8862e,
0x00f8c62f, 0x01f0fc3f, 0x0010bc3f, 0x00e8e42e, 0x0118fe31, 0x00e2108e,
0x00e8c210, 0x01149d31, 0x01f08421, 0x0118d771, 0x011cd671, 0x00e8c62e,
0x0010be2f, 0x01ecc62e, 0x0114be2f, 0x00f8383e, 0x0042109f, 0x00e8c631,
0x00454631, 0x00aad6b5, 0x01151151, 0x00421151, 0x01f1111f, 0x00e1084e,
0x01041041, 0x00e4210e, 0x00000144, 0x01f00000, 0x00000082, 0x0164a4c0,
0x00749c20, 0x00e085c0, 0x00e4b908, 0x0060bd26, 0x0042388c, 0x00c8724c,
0x00a51842, 0x00420080, 0x00621004, 0x00a32842, 0x00421086, 0x015ab800,
0x00949800, 0x0064a4c0, 0x0013a4c0, 0x008724c0, 0x00108da0, 0x0064104c,
0x00c23880, 0x0164a520, 0x00452800, 0x00aad400, 0x00a22800, 0x00111140,
0x00e221c0, 0x00c2088c, 0x00421084, 0x00622086, 0x000022a2,
};
Uint32 d = font[toupper(c) - ' '];
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 5; j++)
{
if ((d >> (i * 5 + j)) & 1)
{
draw_dot_clipped(screen, x + j, y + i, 255, 255, 255);
// top
if (i == 0 || !((d >> ((i - 1) * 5 + j)) & 1))
{
draw_dot_clipped(screen, x + j, y + i - 1, 0, 0, 0);
}
// bottom
if (i == 4 || !((d >> ((i + 1) * 5 + j)) & 1))
{
draw_dot_clipped(screen, x + j, y + i + 1, 0, 0, 0);
}
// left
if (j == 0 || !((d >> (i * 5 + j - 1)) & 1))
{
draw_dot_clipped(screen, x + j - 1, y + i, 0, 0, 0);
}
// right
if (j == 4 || !((d >> (i * 5 + j + 1)) & 1))
{
draw_dot_clipped(screen, x + j + 1, y + i, 0, 0, 0);
}
}
}
}
}
void draw_message(SDL_Surface *screen, int x, int y, const char *message)
{
for (int i = 0; message[i]; i++)
{
if (isprint(message[i]))
{
draw_letter(screen, x + i * 8, y, message[i]);
}
}
}
static const int SCENE_MODES = 3;
class Scene
{
SDL_Surface *screen;
Vector eye;
Vector light_dir;
Vector eye_focus;
double lightx, lighty;
int thread_count;
int mode;
public:
void set_screen(SDL_Surface *screen)
{
this->screen = screen;
}
void reset()
{
eye = Vector();
eye_focus = Vector(0, 0, -0.3);
lightx = M_PI / 2;
lighty = 0;
thread_count = 2;
mode = 0;
update_light();
}
template <typename F>
void report(F print)
{
char line[64];
sprintf(line, "light direction: (%.2lf,%.2lf,%.2lf)", light_dir.x, light_dir.y, light_dir.z);
print(line);
sprintf(line, "eye: (%.2lf,%.2lf,%.2lf)", eye.x, eye.y, eye.z);
print(line);
sprintf(line, "eye rotation: (%.2lf,%.2lf)", eye_focus.x, eye_focus.y);
print(line);
print("");
sprintf(line, "%d threads", thread_count);
print(line);
static const char * const mode_names[] = { "colored mirror", "red", "mirror" };
SDL_COMPILE_TIME_ASSERT(mode_names, SDL_arraysize(mode_names) == SCENE_MODES);
sprintf(line, "mode: %s", mode_names[mode]);
print(line);
}
void pan(int dx, int dy, int dz)
{
Vector d(-dx, dy, dz);
d *= 0.01;
d.rotate_x(eye_focus.y).rotate_y(eye_focus.x);
eye += d;
}
void rotate_camera(int dx, int dy)
{
eye_focus.x -= dx * 0.01;
eye_focus.y += dy * 0.01;
}
void rotate_light(int dx, int dy)
{
lightx -= dx * 0.01;
lighty -= dy * 0.01;
update_light();
}
void zoom(int dz)
{
eye_focus.z += dz * 0.005;
}
void next_mode()
{
mode = (mode + 1) % SCENE_MODES;
}
void add_threads(int dn)
{
thread_count += dn;
thread_count = SDL_max(1, thread_count);
}
void render()
{
std::vector<std::function<void()>> jobs;
std::vector<std::shared_ptr<Thread>> threads;
const int n = thread_count;
jobs.reserve(n);
threads.reserve(n);
for (int i=0; i<n; i++)
{
jobs.push_back([=]
{
const int from = screen->h * i / n;
const int to = screen->h * (i + 1) / n;
for (int y=from; y<to; y++)
{
for (int x=0; x<screen->w; x++)
{
Vector c = calc(x, y);
draw_dot(screen, x, y, (Uint8)(c.x * 255), (Uint8)(c.y * 255), (Uint8)(c.z * 255));
}
}
});
threads.push_back(std::make_shared<Thread>(&jobs[i]));
}
for_each(threads.begin(), threads.end(), std::mem_fn(&Thread::start));
for_each(threads.begin(), threads.end(), std::mem_fn(&Thread::join));
}
private:
Vector polar(double x, double y)
{
return Vector(cos(y) * cos(x), sin(y), cos(y) * sin(x));
}
void update_light()
{
light_dir = polar(lightx, lighty);
}
Vector calc(int sx, int sy)
{
double px = sx / (double)screen->w - 0.5;
double py = -(sy - 0.5 * screen->h) / (double)screen->w;
Vector to(px, py, eye_focus.z);
to.normalize().rotate_x(eye_focus.y).rotate_y(eye_focus.x);
return trace_ray(to);
}
double sphere_det(const Vector &eye, const Vector &to)
{
double r = 0.5;
Vector op(0, 0, -1);
Vector p = op - eye;
double tp = to * p;
return r * r - p * p + tp * tp;
}
Vector trace_ray(const Vector &to)
{
double light = 0.8;
double r = 0.5;
Vector op(0, 0, -1);
Vector p = op - eye;
double tp = to * p;
double det = r * r - p * p + tp * tp;
if (det < 0 || eye.y < -r)
{
return ground(eye, to);
}
double ta = tp - sqrt(det);
double tb = tp + sqrt(det);
double t = ta < 0 ? tb : ta < tb ? ta : tb;
if (t < 0) { return ground(eye, to); }
Vector n = to * t - p;
n.normalize();
if (mode == 0)
{
return (ground(op, n) + ball(light, to, n)) / 2;
}
else if (mode == 1)
{
return ground(op, n);
}
else
{
return ball(light, to, n);
}
}
Vector ball(double light, const Vector &to, const Vector &n)
{
Vector ambient(1, 0, 0);
Vector diffuse(1, 0, 0);
Vector specular(0.8, 0.8, 0.8);
Vector ia = ambient * light;
Vector id = diffuse * (light * (light_dir * n));
Vector spec = n * (2 * (light_dir * n)) - light_dir;
Vector is = specular * (light * (-spec * to));
ia.cut();
id.cut();
is.cut();
Vector c = (id + ia + is) / 3;
return c;
}
Vector ground(const Vector &eye, const Vector &to)
{
double ax = (-10 - eye.x) / to.x;
double bx = (10 - eye.x) / to.x;
double az = (-10 - eye.z) / to.z;
double bz = (10 - eye.z) / to.z;
double ty = (-0.5 - eye.y) / to.y;
if (bx < ax) { std::swap(ax, bx); }
if (bz < az) { std::swap(az, bz); }
if (ty > 0 && ax < ty && ty < bx && az < ty && ty < bz)
{
Vector p = eye + to * ty;
bool c = sin(p.x * 10) < 0 || sin(p.z * 10) >= 0;
if (sphere_det(p, light_dir) >= 0)
{
return c ? Vector(0.2) : Vector(0.4);
}
return Vector(c ? 0.5 : 0.8);
}
return Vector(0.9, 0.8, 0.7);
}
};
class Loop
{
SDL_Surface *screen;
Scene scene;
bool is_help_visible;
bool ignore_next_middle_up;
bool ignore_next_right_up;
public:
void start()
{
init();
while (pump())
{
}
}
private:
void init()
{
SDL_Init(SDL_INIT_VIDEO);
init_video(640, 480);
is_help_visible = true;
ignore_next_middle_up = false;
ignore_next_right_up = false;
reset();
draw();
}
void draw()
{
scene.render();
draw_help();
SDL_Flip(screen);
}
void reset()
{
if (ignore_next_middle_up)
{
ignore_next_middle_up = false;
return;
}
scene.reset();
}
void draw_help()
{
static const char * const lines[] =
{
"left drag: rotate light direction",
"right drag: rotate camera around",
"right click: change mode",
"hold left + wheel up/down: zoom in/out",
"hold right + wheel up/down: inc/dec threads",
"middle drag: move camera in local xy plane",
"wheel up/down: camera forward/backward",
"wheel click: reset",
"h: toggle this help",
};
if (!is_help_visible) { return; }
int x = 10;
int y = 10;
auto print = [&](const char *line)
{
draw_message(screen, x, y, line);
y += 7;
};
std::for_each(lines, lines + SDL_arraysize(lines), print);
print("");
scene.report(print);
}
void init_video(int w, int h)
{
screen = SDL_SetVideoMode(w, h, 0, SDL_DOUBLEBUF | SDL_HWSURFACE | SDL_RESIZABLE);
scene.set_screen(screen);
SDL_WM_SetCaption("raytrace", "raytrace");
}
void handle(const SDL_Event &event)
{
switch (event.type)
{
case SDL_KEYUP:
if (event.key.keysym.sym == SDLK_h)
{
is_help_visible = !is_help_visible;
draw();
}
break;
case SDL_VIDEORESIZE:
init_video(event.resize.w, event.resize.h);
draw();
break;
case SDL_MOUSEMOTION:
if (event.motion.state & SDL_BUTTON_LMASK)
{
scene.rotate_light(event.motion.xrel, event.motion.yrel);
draw();
}
else if (event.motion.state & SDL_BUTTON_RMASK)
{
scene.rotate_camera(event.motion.xrel, event.motion.yrel);
ignore_next_right_up = true;
draw();
}
else if (event.motion.state & SDL_BUTTON_MMASK)
{
ignore_next_middle_up = true;
scene.pan(event.motion.xrel, event.motion.yrel, 0);
draw();
}
break;
case SDL_MOUSEBUTTONUP:
switch (event.button.button)
{
case SDL_BUTTON_LEFT:
break;
case SDL_BUTTON_RIGHT:
if (ignore_next_right_up)
{
ignore_next_right_up = false;
}
else
{
scene.next_mode();
draw();
}
break;
case SDL_BUTTON_MIDDLE:
reset();
draw();
break;
case SDL_BUTTON_WHEELUP:
if (SDL_GetMouseState(NULL, NULL) & SDL_BUTTON_LMASK)
{
scene.zoom(10);
}
else if (SDL_GetMouseState(NULL, NULL) & SDL_BUTTON_RMASK)
{
scene.add_threads(1);
}
else
{
scene.pan(0, 0, -10);
}
draw();
break;
case SDL_BUTTON_WHEELDOWN:
if (SDL_GetMouseState(NULL, NULL) & SDL_BUTTON_LMASK)
{
scene.zoom(-10);
}
else if (SDL_GetMouseState(NULL, NULL) & SDL_BUTTON_RMASK)
{
scene.add_threads(-1);
}
else
{
scene.pan(0, 0, 10);
}
draw();
break;
}
break;
default:
break;
}
}
bool pump()
{
SDL_Event event;
while (SDL_WaitEvent(&event))
{
switch (event.type)
{
case SDL_KEYUP:
if (event.key.keysym.sym == SDLK_ESCAPE ||
(event.key.keysym.mod & KMOD_ALT) && event.key.keysym.sym == SDLK_F4)
{
return false;
}
handle(event);
break;
case SDL_QUIT:
return false;
default:
handle(event);
break;
}
}
return true;
}
};
int main(int argc, char **argv)
{
class SDLContext
{
public:
~SDLContext()
{
SDL_Quit();
}
};
SDLContext sdl;
Loop loop;
srand((unsigned int)time(NULL));
loop.start();
return 0;
}
@snipsnipsnip
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment