SDLでレイトレーサもどき
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
tumblr article