#include "twn_game_api.h" #include "state.h" #include "twn_vec.h" #include /* planned features: */ /* grid-based, bounded space (65536 / POINTS_PER_METER meters), which allows for efficient storage */ /* 65534 point limit, 16 object limit, 2048 faces per object, 32 textures per object */ /* triangles and quads only */ /* texture painting */ /* bones with mesh animations, snapping to grid, with no weights */ /* billboard render to specified angles */ /* 1 point light primitive lighting */ static State state; static bool init; static uint8_t new_object(const char *name) { if (state.objects_sz >= OBJECT_LIMIT) return INVALID_OBJECT; state.objects_sz++; state.objects = SDL_realloc(state.objects, state.objects_sz * sizeof (*state.objects)); SDL_memset(&state.objects[state.objects_sz-1], 0, sizeof (Object)); state.objects[state.objects_sz-1].name = SDL_strdup(name); return state.objects_sz-1; } static uint16_t new_point(int16_t x, int16_t y, int16_t z) { if (state.points_sz >= POINT_LIMIT) return INVALID_POINT; state.points_sz++; state.points[state.points_sz-1] = (Point){x, y, z}; return state.points_sz-1; } static uint16_t push_face(uint8_t object, uint16_t p0, uint16_t p1, uint16_t p2, uint16_t p3, uint8_t texture) { Object *o = &state.objects[object]; o->faces_sz++; o->faces[o->faces_sz-1] = (Face) {p0, p1, p2, p3, texture}; return o->faces_sz-1; } static uint8_t push_texture(uint8_t object, char *texture) { /* TODO: search and combine if it already exists */ Object *o = &state.objects[object]; o->textures_sz++; o->textures[o->textures_sz-1] = SDL_strdup(texture); return o->textures_sz-1; } static inline Vec3 point_to_vec3(uint8_t object, uint16_t point) { Object *o = &state.objects[object]; return (Vec3){ (o->position.x + state.points[point].x) / (float)POINTS_PER_METER, (o->position.y + state.points[point].y) / (float)POINTS_PER_METER, (o->position.z + state.points[point].z) / (float)POINTS_PER_METER }; } static void render_object(uint8_t object) { Object *o = &state.objects[object]; if (o->is_invisible) return; for (uint16_t fi = 0; fi < o->faces_sz; ++fi) { Face *f = &o->faces[fi]; /* quads */ if (f->p[3] != INVALID_POINT) { Vec3 p0 = point_to_vec3(object, f->p[0]); Vec3 p1 = point_to_vec3(object, f->p[1]); Vec3 p2 = point_to_vec3(object, f->p[2]); Vec3 p3 = point_to_vec3(object, f->p[3]); if (state.solid_display_mode) draw_quad(o->textures[f->texture], p0, p1, p2, p3, (Rect){0,0,32,32}, (Color){255,255,255,255} ); else { draw_line_3d(p0, p1, 1, (Color){255,255,255,255}); draw_line_3d(p1, p2, 1, (Color){255,255,255,255}); draw_line_3d(p2, p3, 1, (Color){255,255,255,255}); draw_line_3d(p3, p0, 1, (Color){255,255,255,255}); } } else { /* triangles */ /* TODO: */ SDL_assert_always(false); } } } static uint8_t new_cube(Point pos, Point size) { uint8_t object = new_object("cube"); uint16_t p0 = new_point(pos.x - size.x / 2, pos.y - size.y / 2, pos.z - size.z / 2); uint16_t p1 = new_point(pos.x - size.x / 2, pos.y + size.y / 2, pos.z - size.z / 2); uint16_t p2 = new_point(pos.x + size.x / 2, pos.y + size.y / 2, pos.z - size.z / 2); uint16_t p3 = new_point(pos.x + size.x / 2, pos.y - size.y / 2, pos.z - size.z / 2); uint16_t p4 = new_point(pos.x - size.x / 2, pos.y - size.y / 2, pos.z + size.z / 2); uint16_t p5 = new_point(pos.x - size.x / 2, pos.y + size.y / 2, pos.z + size.z / 2); uint16_t p6 = new_point(pos.x + size.x / 2, pos.y + size.y / 2, pos.z + size.z / 2); uint16_t p7 = new_point(pos.x + size.x / 2, pos.y - size.y / 2, pos.z + size.z / 2); uint8_t tex = push_texture(object, "/data/placeholder.png"); push_face(object, p0, p1, p2, p3, tex); push_face(object, p7, p6, p5, p4, tex); push_face(object, p4, p5, p1, p0, tex); push_face(object, p6, p7, p3, p2, tex); push_face(object, p2, p1, p5, p6, tex); push_face(object, p0, p3, p7, p4, tex); return object; } static void process_camera_movement(void) { input_action("camera_rotate_left", "A"); input_action("camera_rotate_right", "D"); input_action("camera_rotate_up", "W"); input_action("camera_rotate_down", "S"); float horizontal_rotation = 0; float vertical_rotation = 0; if (input_action_pressed("camera_rotate_left")) horizontal_rotation -= CAMERA_ROTATION_SPEED; if (input_action_pressed("camera_rotate_right")) horizontal_rotation += CAMERA_ROTATION_SPEED; if (input_action_pressed("camera_rotate_up")) vertical_rotation -= CAMERA_ROTATION_SPEED; if (input_action_pressed("camera_rotate_down")) vertical_rotation += CAMERA_ROTATION_SPEED; Vec3 front = vec3_cross(state.camera_direction, (Vec3){0,1,0}); state.camera_position = vec3_rotate(state.camera_position, horizontal_rotation, (Vec3){0,1,0}); state.camera_direction = vec3_rotate(state.camera_direction, horizontal_rotation, (Vec3){0,1,0}); Vec3 new_rot = vec3_rotate(state.camera_direction, vertical_rotation, front); /* only apply if it's in limits */ if (fabsf(vec3_dot(new_rot, (Vec3){0,-1,0})) <= 0.99f) { state.camera_position = vec3_rotate(state.camera_position, vertical_rotation, front); state.camera_direction = new_rot; } } static bool find_closest_point(uint8_t* object_result, uint16_t *point_result) { DrawCameraUnprojectResult pos_and_ray = draw_camera_unproject( ctx.mouse_position, state.camera_position, state.camera_direction, (Vec3){0, 1, 0}, state.camera_is_orthographic ? 0 : CAMERA_FOV, state.camera_zoom, 100 ); /* step over every selectable object and find points closest to the view ray */ /* by constructing triangles and finding their height, from perpendicular */ uint16_t closest_point = INVALID_POINT; uint8_t closest_obj = INVALID_OBJECT; float closest_distance = INFINITY; for (uint8_t obj = 0; obj < state.objects_sz; ++obj) { Object *o = &state.objects[obj]; if (o->is_invisible) continue; /* TODO: is it possible to skip repeated points? does it matter? */ /* as we limit the point could we could actually have bool array preallocated for this */ for (uint16_t fi = 0; fi < o->faces_sz; ++fi) { Face *f = &o->faces[fi]; for (uint16_t pi = 0; pi < 4; ++pi) { Vec3 p = point_to_vec3(obj, f->p[pi]); Vec3 d = vec3_sub(pos_and_ray.position, p); Vec3 b = vec3_cross(d, pos_and_ray.direction); float ray_dist = vec3_length(b); if (ray_dist > ((float)SELECTION_SPHERE_RADIUS / POINTS_PER_METER)) continue; Vec3 i = vec3_add(p, b); float dist = vec3_length(i); if (dist < closest_distance) { closest_distance = dist; closest_obj = obj; closest_point = f->p[pi]; } } } } if (closest_point == INVALID_POINT) return false; if (object_result) *object_result = closest_obj; if (point_result) *point_result = closest_point; return true; } void game_tick(void) { if (!init) { /* default state */ new_cube((Point){0,0,0}, (Point){POINTS_PER_METER,POINTS_PER_METER,POINTS_PER_METER}); state.camera_position = (Vec3){2,1,2}; state.camera_direction = vec3_norm(((Vec3){-2,-1,-2})); state.camera_zoom = 1.0f; init = true; } input_action("toggle_display_mode", "Q"); input_action("toggle_projection", "E"); if (input_action_just_pressed("toggle_display_mode")) { audio_play("/data/click.wav", NULL, false, 0.7f, 0.0f); state.solid_display_mode = !state.solid_display_mode; } if (input_action_just_pressed("toggle_projection")) { audio_play("/data/pop.wav", NULL, false, 0.8f, 0.0f); state.camera_is_orthographic = !state.camera_is_orthographic; } process_camera_movement(); uint16_t point_select; uint8_t obj_select; if (find_closest_point(&obj_select, &point_select)) draw_billboard("/data/point.png", point_to_vec3(obj_select, point_select), (Vec2){0.05f, 0.05f}, (Rect){0,0,16,16}, (Color){255,255,255,255}, false); /* axis helpers */ /* idea: black out inactives when dragging points */ /* idea: double selection of axes for diagonal edits */ draw_line_3d((Vec3){0}, (Vec3){0,0,256}, 1, (Color){255,0,0,125}); draw_billboard("/data/x.png", (Vec3){0,0,2}, (Vec2){0.1f, 0.1f}, (Rect){0}, (Color){255,0,0,255}, false); draw_line_3d((Vec3){0}, (Vec3){256,0,0}, 1, (Color){0,0,255,125}); draw_billboard("/data/y.png", (Vec3){2,0,0}, (Vec2){0.1f, 0.1f}, (Rect){0}, (Color){0,0,255,255}, false); draw_line_3d((Vec3){0}, (Vec3){0,256,0}, 1, (Color){75,125,25,125}); draw_billboard("/data/z.png", (Vec3){0,1.5f,0}, (Vec2){0.1f, 0.1f}, (Rect){0}, (Color){75,125,25,255}, false); for (uint8_t obj = 0; obj < state.objects_sz; ++obj) render_object(obj); draw_text("twndel\x03", (Vec2){0, 2}, 32, (Color){255,255,255,200}, NULL); draw_camera( state.camera_position, state.camera_direction, (Vec3){0, 1, 0}, state.camera_is_orthographic ? 0 : CAMERA_FOV, state.camera_zoom, 100 ); } void game_end(void) { for (uint8_t obj = 0; obj < state.objects_sz; ++obj) { Object *o = &state.objects[obj]; for (uint8_t t = 0; t < o->textures_sz; ++t) SDL_free(o->textures[t]); SDL_free(o->name); } if (state.objects_sz != 0) SDL_free(state.objects); }