#include #include #include #include #include #include #include #include #include #include #include FT_FREETYPE_H #include #include "math/vec2.hpp" #include "math/vec3.hpp" #include "math/vec4.hpp" #include "math/mat3x3.hpp" #include "math/mat4x4.hpp" using vec2 = vec<2, float>; using vec3 = vec<3, float>; using vec4 = vec<4, float>; using mat3x3 = mat<3, 3, float>; using mat4x4 = mat<4, 4, float>; using vertex_position = vec<3, float>; using vertex_normal = vec<3, float>; using vertex_texture = vec<2, float>; #include "model_collision.h" struct glyph { int32_t width; int32_t height; int32_t horiBearingX; int32_t horiBearingY; int32_t horiAdvance; SDL_Texture * texture; }; static struct glyph glyphs[0x80 - 0x20] = {0}; int32_t face_height; int load_outline_char(SDL_Renderer * renderer, const FT_Face face, const FT_Int32 load_flags, const FT_Render_Mode render_mode, const FT_ULong char_code) { FT_Error error; FT_UInt glyph_index = FT_Get_Char_Index(face, char_code); error = FT_Load_Glyph(face, glyph_index, load_flags); if (error) { printf("FT_Load_Glyph %s\n", FT_Error_String(error)); return -1; } error = FT_Render_Glyph(face->glyph, render_mode); if (error) { printf("FT_Render_Glyph %s\n", FT_Error_String(error)); return -1; } struct glyph * glyph = &glyphs[char_code - 0x20]; glyph->width = face->glyph->bitmap.width; glyph->height = face->glyph->bitmap.rows; glyph->horiBearingX = face->glyph->metrics.horiBearingX; glyph->horiBearingY = face->glyph->metrics.horiBearingY; glyph->horiAdvance = face->glyph->metrics.horiAdvance; if (face->glyph->bitmap.pitch != 0) { SDL_Surface * surface = SDL_CreateSurface(face->glyph->bitmap.width, face->glyph->bitmap.rows, SDL_PIXELFORMAT_RGBA8888); SDL_LockSurface(surface); for (unsigned int y = 0; y < face->glyph->bitmap.rows; y++) { for (unsigned int x = 0; x < face->glyph->bitmap.width; x++) { int s_ix = y * face->glyph->bitmap.pitch + x; int d_ix = y * surface->pitch + x * 4; uint8_t gray = face->glyph->bitmap.buffer[s_ix]; ((uint8_t *)surface->pixels)[d_ix + 0] = gray; ((uint8_t *)surface->pixels)[d_ix + 1] = gray; ((uint8_t *)surface->pixels)[d_ix + 2] = gray; ((uint8_t *)surface->pixels)[d_ix + 3] = 255; } } SDL_UnlockSurface(surface); if (glyph->texture != NULL) SDL_DestroyTexture(glyph->texture); glyph->texture = SDL_CreateTextureFromSurface(renderer, surface); if (glyph->texture == NULL) { printf("%s\n", SDL_GetError()); } assert(glyph->texture != NULL); SDL_DestroySurface(surface); } return 0; } int load_font(SDL_Renderer * renderer, int font_size) { FT_Library library; FT_Face face; FT_Error error; error = FT_Init_FreeType(&library); if (error) { printf("FT_Init_FreeType\n"); return -1; } error = FT_New_Face(library, "/home/bilbo/timer/DejaVuSansMono.ttf", 0, &face); if (error) { printf("FT_New_Face\n"); return -1; } error = FT_Set_Pixel_Sizes(face, 0, font_size); if (error) { printf("FT_Set_Pixel_Sizes: %s %d\n", FT_Error_String(error), error); return -1; } for (int char_code = 0x20; char_code <= 0x7f; char_code++) { load_outline_char(renderer, face, FT_LOAD_DEFAULT, FT_RENDER_MODE_NORMAL, char_code); } printf("loaded size %ld\n", face->size->metrics.height >> 6); face_height = face->size->metrics.height; return 0; } int32_t render_text(SDL_Renderer * renderer, int32_t x, int32_t y, const char * s, int length) { int32_t x_advance = x; int32_t y_advance = y; for (int i = 0; i < length; i++) { char c = s[i]; struct glyph * glyph = &glyphs[c - 0x20]; x_advance += glyph->horiAdvance; if (glyph->texture != NULL) { float x = (float)(x_advance + glyph->horiBearingX) / 64.f; float y = (float)(y_advance - glyph->horiBearingY) / 64.f; SDL_FRect srect = { .x = 0.f, .y = 0.f, .w = (float)glyph->width, .h = (float)glyph->height }; SDL_FRect drect = { .x = x, .y = y, .w = (float)glyph->width, .h = (float)glyph->height }; SDL_RenderTexture(renderer, glyph->texture, &srect, &drect); } } return x_advance; } struct line3 { vec3 a; vec3 b; }; constexpr int num_lines = 5; struct state { struct line3 line[num_lines]; vec3 normal; vec3 mouse_position; }; struct state state = { .line = { {{ 0, -0.5, 0 }, { 0, 0.5, 0 }}, {{ 0, -0.5, -0.5 }, { 0, 0.5, -0.5 }}, {{ 0, -0.5, 0.5 }, { 0, 0.5, 0.5 }}, {{ 0, 0.5, 0.5 }, { 0, 0.5, -0.5 }}, {{ 0, -0.5, 0.5 }, { 0, -0.5, -0.5 }}, }, .normal = { -1, 0, 0 }, }; struct edge_normal { struct line3 edge; vec3 normal; }; struct edge_normal quad[4] = { { .edge = {{-1, -1, 0}, { 1, -1, 0}}, .normal = {0, -1, 0}, }, { .edge = {{ 1, -1, 0}, { 1, 1, 0}}, .normal = {1, 0, 0}, }, { .edge = {{ 1, 1, 0}, {-1, 1, 0}}, .normal = {0, 1, 0}, }, { .edge = {{-1, 1, 0}, {-1, -1, 0}}, .normal = {-1, 0, 0}, }, }; static int window_width = 1; static int window_height = 1; static inline int max(int a, int b) { return (a > b) ? a : b; } static inline int min(int a, int b) { return (a > b) ? b : a; } const float deg = 0.017453292519943295; float deg45 = 0.7853981633974483; static float vtheta = 0.5; mat4x4 trans; vec3 screen_transform_vertex(vec3 v) { float dim = ((float)min(window_height, window_width)) / 2.0f; return { v.x * dim + window_width / 2.0f, v.y * dim + window_height / 2.0f, v.z, }; } vec3 _transform_vertex(vec3 v, float w) { vec4 v4 = {v.x, v.y, v.z, w}; vec4 v4t = trans * v4; return {v4t.x, v4t.y, v4t.z}; } vec3 transform_vertex(vec3 v, float scale) { return screen_transform_vertex(_transform_vertex(v * scale, 1.0f)); } vec3 inverse_transform(float x, float y) { float dim = ((float)min(window_height, window_width)) / 2.0f; assert(dim != 0); return { -(window_width - 2 * x) / (2 * dim), -(window_height - 2 * y) / (2 * dim), 0, }; } struct line3 transform_line(struct line3 line, float scale) { return { transform_vertex(line.a, scale), transform_vertex(line.b, scale), }; } static inline void render_line(SDL_Renderer * renderer, struct line3 line) { assert(SDL_RenderLine(renderer, line.a.x, line.a.y, line.b.x, line.b.y)); } static inline void render_line_vtx(SDL_Renderer * renderer, vec3 a, vec3 b) { assert(SDL_RenderLine(renderer, a.x, a.y, b.x, b.y)); } void render_basis(SDL_Renderer * renderer) { // magenta: Z assert(SDL_SetRenderDrawColorFloat(renderer, 1, 0, 1, 1)); render_line(renderer, transform_line({{0, 0, 0}, {0, 0, 1}}, 1.0f)); // yellow: Y assert(SDL_SetRenderDrawColorFloat(renderer, 1, 1, 0, 1)); render_line(renderer, transform_line({{0, 0, 0}, {0, 1, 0}}, 1.0f)); // cyan: X assert(SDL_SetRenderDrawColorFloat(renderer, 0, 1, 1, 1)); render_line(renderer, transform_line({{0, 0, 0}, {1, 0, 0}}, 1.0f)); vec3 z = transform_vertex({0, 0, 1}, 1.1); vec3 y = transform_vertex({0, 1, 0}, 1.1); vec3 x = transform_vertex({1, 0, 0}, 1.1); render_text(renderer, ((z.x - 30) * 64), ((z.y + 10) * 64), "+z", 2); render_text(renderer, ((y.x - 30) * 64), ((y.y + 10) * 64), "+y", 2); render_text(renderer, ((x.x - 30) * 64), ((x.y + 10) * 64), "+x", 2); } vec3 update_light(SDL_Renderer * renderer) { static float ltheta = 0; vec3 light_origin = {0, 0, 0}; vec3 light_pos = {1, 1, 1}; mat3x3 rot = { cos(ltheta), -sin(ltheta), 0, sin(ltheta), cos(ltheta), 0, 0, 0, 1, }; light_pos = rot * light_pos; ltheta += deg / 4; vec3 light_vec = light_origin - light_pos; assert(SDL_SetRenderDrawColorFloat(renderer, 0, 1, 0, 1)); render_line(renderer, transform_line({light_origin, light_pos}, 0.5f)); return light_vec; } void _render_quad(SDL_Renderer * renderer, vec3 light_vec) { for (int i = 0; i < 4; i++) { float d = dot(light_vec, quad[i].normal); if (d > 0) assert(SDL_SetRenderDrawColorFloat(renderer, 1, 1, 1, 1)); else assert(SDL_SetRenderDrawColorFloat(renderer, 0, 0, 1, 1)); render_line(renderer, transform_line(quad[i].edge, 0.25f)); vec3 origin = (quad[i].edge.a + quad[i].edge.b) / 2.0f; assert(SDL_SetRenderDrawColorFloat(renderer, 1, 0, 0, 1)); render_line(renderer, transform_line({origin, origin + quad[i].normal}, 0.25f)); } } void set_edge_coloring(uint8_t * edge_coloring, const int edge_stride, float l_dot_n, int a, int b) { bool d = l_dot_n > 0; int ma = min(a, b); int mb = max(a, b); int bit = 1 << ((int)d); edge_coloring[ma * edge_stride + mb] |= bit; } mat4x4 translate(const vec3 v) { return (mat4x4){ 1, 0, 0, v.x, 0, 1, 0, v.y, 0, 0, 1, v.z, 0, 0, 0, 1, }; } void render_quad(SDL_Renderer * renderer, const vec3 * position, const vec3 * normal, const quadrilateral * quadrilateral, const vec3 light_vec, uint8_t * edge_coloring, const int edge_stride) { vec3 n = normal[quadrilateral->a.normal]; float l_dot_n = dot(light_vec, n); if (l_dot_n > 0) assert(SDL_SetRenderDrawColorFloat(renderer, 1, 1, 1, 1)); else assert(SDL_SetRenderDrawColorFloat(renderer, 0, 0, 1, 1)); float scale = 0.5f; set_edge_coloring(edge_coloring, edge_stride, l_dot_n, quadrilateral->a.position, quadrilateral->b.position); set_edge_coloring(edge_coloring, edge_stride, l_dot_n, quadrilateral->b.position, quadrilateral->c.position); set_edge_coloring(edge_coloring, edge_stride, l_dot_n, quadrilateral->c.position, quadrilateral->d.position); set_edge_coloring(edge_coloring, edge_stride, l_dot_n, quadrilateral->d.position, quadrilateral->a.position); vec3 ap = position[quadrilateral->a.position]; vec3 bp = position[quadrilateral->b.position]; vec3 cp = position[quadrilateral->c.position]; vec3 dp = position[quadrilateral->d.position]; vec3 n10 = n * 0.1f; vec3 a = transform_vertex(ap + n10, scale); vec3 b = transform_vertex(bp + n10, scale); vec3 c = transform_vertex(cp + n10, scale); vec3 d = transform_vertex(dp + n10, scale); render_line_vtx(renderer, a, b); render_line_vtx(renderer, b, c); render_line_vtx(renderer, c, d); render_line_vtx(renderer, d, a); assert(SDL_SetRenderDrawColorFloat(renderer, 1, 0, 0, 1)); vec3 origin = (position[quadrilateral->a.position] + position[quadrilateral->b.position] + position[quadrilateral->c.position] + position[quadrilateral->d.position]) / 4.0f; vec3 origin_t = transform_vertex(origin, scale); vec3 origin_n_t = transform_vertex(origin + n, scale); render_line_vtx(renderer, origin_t, origin_n_t); } void render_silhouette(SDL_Renderer * renderer, const vec3 * position, const uint8_t * edge_coloring, const int edge_stride) { assert(SDL_SetRenderDrawColorFloat(renderer, 1, 0.5, 0, 1)); for (int a = 0; a < edge_stride; a++) { for (int b = 0; b < edge_stride; b++) { uint8_t coloring = edge_coloring[a * edge_stride + b]; if (coloring == 0b11) { vec3 ap = position[a]; vec3 bp = position[b]; float scale = 0.5f; vec3 av = transform_vertex(ap, scale); vec3 bv = transform_vertex(bp, scale); render_line_vtx(renderer, av, bv); } } } } mat3x3 rotate_to(vec3 old_normal, vec3 new_normal) { vec3 s = cross(old_normal, new_normal); float c = dot(old_normal, new_normal); mat3x3 i = mat3x3(); mat3x3 v = { 0, -s.z, s.y, s.z, 0, -s.x, -s.y, s.x, 0, }; float mag = magnitude(s); mat3x3 v2 = v * v; float c_mag2 = (1 - c) / (mag * mag); mat3x3 v2_c_mag2 = (v2 * c_mag2); mat3x3 t = i + v + v2_c_mag2; return t; } mat4x4 look_at(vec3 eye, vec3 center, vec3 up) { vec3 x; vec3 y; vec3 z; z = eye - center; z = z / magnitude(z); y = up; x = cross(y, z); y = cross(z, x); x = x / magnitude(x); y = y / magnitude(y); mat4x4 t = { x.x, x.y, x.z, -dot(x, eye), y.x, y.y, y.z, -dot(y, eye), z.x, z.y, z.z, -dot(z, eye), 0, 0, 0, 1.0f }; return t; } void render_lines(SDL_Renderer * renderer) { // line assert(SDL_SetRenderDrawColorFloat(renderer, 1, 0.5, 0.5, 1)); for (int i = 0; i < num_lines; i++) { struct line3 tl = transform_line(state.line[i], 1.0f); render_line(renderer, tl); } // normal assert(SDL_SetRenderDrawColorFloat(renderer, 1, 0, 0, 1)); struct line3 normal_line = {{0, 0, 0}, state.normal}; render_line(renderer, transform_line(normal_line, 0.5f)); // mouse assert(SDL_SetRenderDrawColorFloat(renderer, 0, 0, 1, 1)); struct line3 mouse_line = {{0, 0, 0}, state.mouse_position}; render_line(renderer, transform_line(mouse_line, 0.5f)); // foo mat3x3 t = rotate_to(state.normal, -state.mouse_position); vec3 nr = t * state.normal; //mat4x4 t = look_at({0, 0, 0}, state.mouse_position - state.normal, {0, 0, 1}); //vec4 nr4 = t * (vec4){state.normal.x, state.normal.y, state.normal.z, 0}; //vec3 nr = {nr.x, nr.y, nr.z}; assert(SDL_SetRenderDrawColorFloat(renderer, 0, 1, 0, 1)); struct line3 nr_line = {{0, 0, 0}, nr}; render_line(renderer, transform_line(nr_line, 0.5f)); { assert(SDL_SetRenderDrawColorFloat(renderer, 0.5, 1, 0.5, 1)); for (int i = 0; i < num_lines; i++) { struct line3 l = state.line[i]; /* vec4 a4 = t * l.a; vec4 b4 = t * l.b; vec3 a = {a4.x, a4.y, a4.z}; vec3 b = {b4.x, b4.y, b4.z}; */ vec3 a = t * l.a; vec3 b = t * l.b; struct line3 tl = {a, b}; render_line(renderer, transform_line(tl, 1.0f)); } } } void render_text_state(SDL_Renderer * renderer) { int32_t x_advance = 10 << 6; int32_t y_advance = face_height; x_advance = render_text(renderer, x_advance, y_advance, "dot: ", 5); //float d = dot(state.normal, state.mouse_position); /* float d = dot({0, 1}, state.mouse_position); char buf[64]; int len = snprintf(buf, 64, "%.03f", d); x_advance = render_text(renderer, x_advance, y_advance, buf, len); */ } bool collided[256] = {0}; void render_collision(SDL_Renderer * renderer) { const struct model * model = &haunted_mansion_collision_model; const struct object * object = &haunted_mansion_collision_house_coll_display; float scale = 1.f; for (int i = 0; i < object->line_count; i++) { const union line * line = &object->line[i]; if (collided[i]) SDL_SetRenderDrawColor(renderer, 255, 100, 100, 255); else SDL_SetRenderDrawColor(renderer, 100, 100, 255, 255); render_line_vtx(renderer, transform_vertex(model->position[line->a], scale), transform_vertex(model->position[line->b], scale)); } } static float theta = 0; void update_mouse_position() { vec3 pos = {0, 1, 1}; pos = pos / magnitude(pos); mat3x3 rot1 = { cos(theta), -sin(theta), 0, sin(theta), cos(theta), 0, 0, 0, 1, }; state.mouse_position = rot1 * pos; theta += deg; } vec2 line_intersection(vec2 a1, vec2 a2, vec2 b1, vec2 b2) { float x1 = a1.x; float y1 = a1.y; float x2 = a2.x; float y2 = a2.y; float x3 = b1.x; float y3 = b1.y; float x4 = b2.x; float y4 = b2.y; float x1x2 = x1 - x2; float x1x3 = x1 - x3; float x3x4 = x3 - x4; float y1y2 = y1 - y2; float y1y3 = y1 - y3; float y3y4 = y3 - y4; float div = 1.0f / (x1x2 * y3y4 - y1y2 * x3x4); float t = (x1x3 * y3y4 - y1y3 * x3x4) * div; float u = -(x1x2 * y1y3 - y1y2 * x1x3) * div; return {t, u}; } bool line_has_collision(vec3 a1, vec3 a2) { const struct model * model = &haunted_mansion_collision_model; const struct object * object = &haunted_mansion_collision_house_coll_display; for (int i = 0; i < object->line_count; i++) { const union line * line = &object->line[i]; vec3 b1 = _transform_vertex(model->position[line->a], 1.0f); vec3 b2 = _transform_vertex(model->position[line->b], 1.0f); vec2 tu = line_intersection({a1.x, a1.y}, {a2.x, a2.y}, {b1.x, b1.y}, {b2.x, b2.y}); if (tu.x >= 0.0f && tu.x <= 1.0f && tu.y >= 0.0f && tu.y <= 1.0f) { collided[i] = true; return true; } } return false; } void move(SDL_Renderer * renderer, vec3 direction) { SDL_SetRenderDrawColor(renderer, 128, 255, 50, 255); render_line_vtx(renderer, screen_transform_vertex({0, 0, 0}), screen_transform_vertex(-direction * 2.0f)); if (!line_has_collision({0, 0, 0}, -direction * 1.0f)) trans = translate(direction) * trans; } int main() { SDL_Window * window; SDL_Renderer * renderer; bool success = SDL_Init(SDL_INIT_VIDEO); if (!success) printf("error: `%s`\n", SDL_GetError()); assert(success == true); window = SDL_CreateWindow("sandbox", 640, // w 480, // h SDL_WINDOW_RESIZABLE);// | SDL_WINDOW_MAXIMIZED); int num_drivers = SDL_GetNumRenderDrivers(); printf("available drivers:\n"); for (int i = 0; i < num_drivers; i++) { const char * s = SDL_GetRenderDriver(i); printf(" %s\n", s); } renderer = SDL_CreateRenderer(window, "opengles2"); SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_PropertiesID props = SDL_GetRendererProperties(renderer); const char * name = SDL_GetRendererName(renderer); assert(name != NULL); printf("renderer: %s\n", name); const SDL_PixelFormat * formats = (const SDL_PixelFormat *)SDL_GetPointerProperty(props, SDL_PROP_RENDERER_TEXTURE_FORMATS_POINTER, NULL); assert(formats != NULL); while (*formats != SDL_PIXELFORMAT_UNKNOWN) { printf("%s\n", SDL_GetPixelFormatName(*formats++)); } uint64_t ticks = SDL_GetTicks(); int font_size = 25; load_font(renderer, font_size); trans = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, }; trans = trans * 0.2f; mat4x4 rot1 = { cos(deg45), -sin(deg45), 0, 0, sin(deg45), cos(deg45), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, }; mat4x4 rot2 = { 1, 0, 0, 0, 0, cos(deg45), -sin(deg45), 0, 0, sin(deg45), cos(deg45), 0, 0, 0, 0, 1, }; mat4x4 rot3 = { cos(-deg45), 0, sin(-deg45), 0, 0, 1, 0, 0, sin(-deg45), 0, cos(-deg45), 0, 0, 0, 0, 1, }; (void)rot1; (void)rot2; (void)rot3; trans = rot2 * rot1 * trans; while (1) { SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderClear(renderer); bool success = SDL_GetWindowSizeInPixels(window, &window_width, &window_height); assert(success == true); render_text_state(renderer); render_basis(renderer); render_collision(renderer); SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_EVENT_QUIT: goto exit; case SDL_EVENT_KEY_DOWN: if (event.key.key == SDLK_ESCAPE) goto exit; if (event.key.key == SDLK_LEFT) move(renderer, {0.03, 0.0, 0.0}); if (event.key.key == SDLK_RIGHT) move(renderer, {-0.03, 0.0, 0.0}); if (event.key.key == SDLK_UP) move(renderer, {0.0, 0.03, 0.0}); if (event.key.key == SDLK_DOWN) move(renderer, {0.0, -0.03, 0.0}); break; case SDL_EVENT_MOUSE_BUTTON_DOWN: if (event.button.button == 1) { //vec3 mv = inverse_transform(event.button.x, event.button.y); //float m = magnitude(mv); //state.mouse_position = mv / m; } break; default: break; } } while (SDL_GetTicks() - ticks < (1000 / 60)) { SDL_Delay(1); } SDL_RenderPresent(renderer); ticks = SDL_GetTicks(); //update_mouse_position(); //vtheta += deg / 10; } exit: SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); }