#include "haloo3d/haloo3d.h" #include "haloo3d/haloo3dex_console.h" #include "haloo3d/haloo3dex_easy.h" #include "haloo3d/haloo3dex_gen.h" // #include "haloo3d/haloo3dex_img.h" #include "haloo3d/haloo3dex_obj.h" #include "haloo3d/haloo3dex_print.h" #include "unigi/unigi.headers/src/main.h" #include "unigi/unigi.platform.sdl1/src/main.c" #include "ecs2.h" #include "keys.h" #include // INteresting flags for performance comparisons #define FASTFILL #define WIDTH 480 #define HEIGHT 300 #define ASPECT ((float)WIDTH / HEIGHT) #define SCREENSCALE 2 #define SWIDTH (WIDTH * SCREENSCALE) #define SHEIGHT (HEIGHT * SCREENSCALE) #define NEARCLIP 0.01 #define FARCLIP 100.0 #define LIGHTANG -MPI / 4.0 #define AVGWEIGHT 0.85 // Game options #define MAZESIZE 15 #define HSCALE 1.5 // Maze grows in the positive direction #define MAZENORTH 1 #define MAZEEAST 2 #define MAZEVISIT 4 // When you rightshift these values, you "turn right". // NOTE: north in this case is "towards the screen" because it moves in the // positive direction. In this case, it's actually wound in the opposite // direction of what you'd expect #define DIRNORTH 8 #define DIRWEST 4 #define DIRSOUTH 2 #define DIREAST 1 #define TURNRIGHT(d) (d == 1 ? 8 : (d >> 1)) #define TURNLEFT(d) (d == 8 ? 1 : (d << 1)) #define STACKPUSH(s, t, v) s[t++] = v; // Store all the values users can change at the beginning float ditherstart = -1; float ditherend = 8; float fov = 90.0; float minlight = 0.25; int fps = 30; uint16_t sky = 0xF000; struct vec2i dirtovec(uint8_t dir) { struct vec2i result; switch (dir) { case DIREAST: vec2i(result.v, 1, 0); break; case DIRWEST: vec2i(result.v, -1, 0); break; case DIRNORTH: vec2i(result.v, 0, 1); break; case DIRSOUTH: vec2i(result.v, 0, -1); break; default: vec2i(result.v, 0, 0); } return result; } mfloat_t dirtoyaw(uint8_t dir) { switch (dir) { case DIREAST: return MPI_2; case DIRWEST: return -MPI_2; case DIRNORTH: return MPI; case DIRSOUTH: return 0; default: return 0; } } int maze_visited(uint8_t *maze, int x, int y, int size) { return (maze[x + y * size] & MAZEVISIT) > 0; } int maze_connected(uint8_t *maze, int x, int y, int size, uint8_t move) { // eprintf("CHECKING DIR %d at (%d,%d), it is %d\n", move, x, y, // maze[x + y * size]); if (move == DIREAST) { return (maze[x + y * size] & MAZEEAST) == 0; } else if (move == DIRWEST) { return (x > 0) && ((maze[x - 1 + y * size] & MAZEEAST) == 0); } else if (move == DIRNORTH) { return (maze[x + y * size] & MAZENORTH) == 0; } else if (move == DIRSOUTH) { return (y > 0) && ((maze[x + (y - 1) * size] & MAZENORTH) == 0); } return 0; } // Calculate which direction from the given position would have you // facing down the longest hallway uint8_t maze_longesthallway(uint8_t *maze, int size, int x, int y) { uint8_t face = DIRNORTH; uint8_t maxface = face; uint16_t maxdist = 0; while (face) { uint16_t dist = 0; int dx = x, dy = y; struct vec2i move = dirtovec(face); while (maze_connected(maze, dx, dy, size, face)) { dx += move.x; dy += move.y; dist++; } if (dist > maxdist) { maxface = face; maxdist = dist; } face >>= 1; } eprintf("MAXFACE: %d\n", maxface); return maxface; } // Generate a (square) maze. Utilize one bit of the maze (#2) to // indicate whether it is visited void maze_generate(uint8_t *maze, int size, struct vec2i *start, struct vec2i *end) { const int mazesquare = (size) * (size); for (int i = 0; i < mazesquare; i++) { maze[i] = MAZENORTH | MAZEEAST; } int *mazestack; mallocordie(mazestack, sizeof(int) * mazesquare); for (int i = 0; i < size * size; i++) { mazestack[i] = -1; } // Push current cell onto stack, mark as visited start->x = rand() % size; start->y = rand() % size; int x = start->x; int y = start->y; int mazetop = 0; int maxmazetop = 0; STACKPUSH(mazestack, mazetop, x + y * size); maze[x + y * size] |= MAZEVISIT; uint8_t visitable[4]; int visittop = 0; // Now let's make a maze! while (mazetop) { // The end of the maze is the furthest into the stack we go. This should // somewhat maximize the complexity of start to finish? if (mazetop > maxmazetop) { end->x = x; end->y = y; maxmazetop = mazetop; } mazetop--; visittop = 0; x = mazestack[mazetop] % size; y = mazestack[mazetop] / size; if (x > 0 && !maze_visited(maze, x - 1, y, size)) { visitable[visittop++] = DIRWEST; } if (x < size - 1 && !maze_visited(maze, x + 1, y, size)) { visitable[visittop++] = DIREAST; } if (y > 0 && !maze_visited(maze, x, y - 1, size)) { visitable[visittop++] = DIRSOUTH; } if (y < size - 1 && !maze_visited(maze, x, y + 1, size)) { visitable[visittop++] = DIRNORTH; } // You can generate a random location! if (visittop) { // Readd ourselves, we're moving STACKPUSH(mazestack, mazetop, x + y * size); uint8_t dir = visitable[rand() % visittop]; struct vec2i movedir = dirtovec(dir); int nx = x + movedir.x; int ny = y + movedir.y; // Trust that the visitable array is always valid if (dir == DIREAST) { // Tear down east wall maze[x + y * size] &= ~MAZEEAST; } else if (dir == DIRWEST) { // Move left and tear down east wall maze[(x - 1) + y * size] &= ~MAZEEAST; } else if (dir == DIRNORTH) { // tear down north wall maze[x + y * size] &= ~MAZENORTH; } else if (dir == DIRSOUTH) { // move down and tear down north wall maze[x + (y - 1) * size] &= ~MAZENORTH; } // Push onto stack and set visited STACKPUSH(mazestack, mazetop, nx + ny * size); maze[nx + ny * size] |= MAZEVISIT; } } eprintf("Maze generate: %d,%d -> %d,%d maxdepth: %d\n", start->x, start->y, end->x, end->y, maxmazetop); free(mazestack); } void maze_wall_generate(uint8_t *maze, int size, haloo3d_obj *obj) { // Reset ALL walls obj->numfaces = 0; // Simple: for each cell, we check if north or east is a wall. If so, // generate it. Also, generate walls for the south and west global wall for (int y = 0; y < size; y++) { for (int x = 0; x < size; x++) { if (!maze_connected(maze, x, y, size, DIREAST)) { haloo3d_gen_grid_quad(obj, x, y, dirtovec(DIREAST)); } if (!maze_connected(maze, x, y, size, DIRNORTH)) { haloo3d_gen_grid_quad(obj, x, y, dirtovec(DIRNORTH)); } } } for (int i = 0; i < size; i++) { haloo3d_gen_grid_quad(obj, i, 0, dirtovec(DIRSOUTH)); haloo3d_gen_grid_quad(obj, 0, i, dirtovec(DIRWEST)); } } // Turning into a blob? IDK. Maybe certain things should be // global state, which is this. typedef struct { // float timedelta; int fps; uint8_t state; uint8_t maze[MAZESIZE * MAZESIZE]; // int size; // A suggested start and end. The end is the actual // end, where we put the ending indicator struct vec2i start; struct vec2i end; // Some simple calcs for you to use mfloat_t cellsize; } worldstate; enum { WSTATE_INIT = 0, WSTATE_SPINUP = 1, WSTATE_GAMEPLAY = 2, WSTATE_GAMEOVER = 3, WSTATE_SPINDOWN = 4 }; // A component that stores a position and a facing direction // as an angle typedef struct { struct vec3 pos; struct vec2 rot; // x is yaw, y is pitch } ecs_placement; // A component representing movement through the scene. // typedef struct { // struct vec3 posvel; // mfloat_t yawvel; // } ecs_movement; // A component which links back to a camera typedef struct { haloo3d_camera *camera; } ecs_camera; // A component which allows automatic navigation // of position through the use of timers. typedef struct { struct vec3 dest; int timer; } ecs_autonav; // A component which allows automatic rotation // through the use of timers. typedef struct { struct vec2 dest; // x is yaw, y is pitch int timer; } ecs_autorotate; // Component which enables synchronized scale y from 0 to 1 or 1 to 0. It is // expected that an external source is modifying the value typedef struct { haloo3d_obj_instance *obj; mfloat_t *scale; int *timer; } ecs_syncgrow; // A component which has pointers back to global world state. // typedef struct { // worldstate *state; // } ecs_worldstate; // #define MAXGROWERS 10 // A component which holds onto data specifically for world // entities. You can think of it as private data so people // can't poke at the worldstate data typedef struct { worldstate *state; haloo3d_obj *wallmodel; int scaletimer; mfloat_t scaleto; } ecs_world; // void sys_move_placement(ecs_movement *m, ecs_placement *p) { // vec3_add(p->pos.v, p->pos.v, m->posvel.v); // p->yaw += m->yawvel; // } void sys_syncgrow(ecs_syncgrow *sg) { // Only perform logic when a timer is running if (*sg->timer) { if (*sg->timer == 1) { // Just jump right to it on the last frame. We don't run on frame 0 sg->obj->scale.y = *sg->scale; } else { mfloat_t scaleleft = *sg->scale - sg->obj->scale.y; sg->obj->scale.y += scaleleft / *sg->timer; } } } void sys_autonav(ecs_autonav *nav, ecs_placement *p) { // Only perform logic when the timer is running if (nav->timer) { // On the last frame, just set it outright, nothing else to do if (nav->timer == 1) { p->pos = nav->dest; } else { mfloat_t xdiff = nav->dest.x - p->pos.x; mfloat_t ydiff = nav->dest.y - p->pos.y; mfloat_t zdiff = nav->dest.z - p->pos.z; p->pos.x += xdiff / nav->timer; p->pos.y += ydiff / nav->timer; p->pos.z += zdiff / nav->timer; } nav->timer--; } } void sys_autorotate(ecs_autorotate *arot, ecs_placement *p) { // Only perform logic when the timer is running if (arot->timer) { // On the last frame, set it outright if (arot->timer == 1) { p->rot = arot->dest; } else { mfloat_t xdiff = arot->dest.x - p->rot.x; mfloat_t ydiff = arot->dest.y - p->rot.y; p->rot.x += xdiff / arot->timer; p->rot.y += ydiff / arot->timer; } arot->timer--; } } // update camera with placement value on entity void sys_camera(ecs_camera *cam, ecs_placement *p) { cam->camera->pos = p->pos; cam->camera->yaw = p->rot.x; cam->camera->pitch = p->rot.y; // eprintf("CAM: %f %f POS: %f %f %f\n", cam->camera->yaw, cam->camera->pitch, // cam->camera->pos.x, cam->camera->pos.y, cam->camera->pos.z); } void sys_world(ecs_world *w) { const int spinspeed = w->state->fps * 4 / 5; switch (w->state->state) { case WSTATE_INIT: maze_generate(w->state->maze, MAZESIZE, &w->state->start, &w->state->end); maze_wall_generate(w->state->maze, MAZESIZE, w->wallmodel); eprintf("INIT MAZE COMPLETE, spinning up walls\n"); w->state->state = WSTATE_SPINUP; w->scaletimer = spinspeed; w->scaleto = 1; break; case WSTATE_SPINUP: w->scaletimer--; if (w->scaletimer == 0) { eprintf("SPINUP COMPLETE, starting gameplay\n"); // Start gameplay. We won't know when it's done w->state->state = WSTATE_GAMEPLAY; } break; case WSTATE_GAMEOVER: w->scaletimer = spinspeed; w->scaleto = 0; w->state->state = WSTATE_SPINDOWN; eprintf("GAME OVER, spinning down\n"); break; case WSTATE_SPINDOWN: // Bring walls down, timer down w->scaletimer--; if (w->scaletimer == 0) { eprintf("SPINDOWN COMPLETE, reinitializing\n"); // Start gameplay. We won't know when it's done w->state->state = WSTATE_INIT; } break; } } enum { SAI_INIT, SAI_READY, SAI_GAMEPLAY, }; // State for tracking a smart ai moving through the maze. typedef struct { uint8_t state; uint8_t rotstate; uint8_t dir; uint32_t timer; struct vec2i mpos; worldstate *ws; } ecs_smartai; static void sys_smartai(ecs_smartai *smartai, ecs_placement *p, ecs_autonav *anav, ecs_autorotate *arot) { const int actiontime = smartai->ws->fps / 2; const int rotdelaytime = 2 * actiontime / 5; switch (smartai->state) { case SAI_INIT: // Here, we wait until the world state is spinup, in which // case we can spawn and face if (smartai->ws->state == WSTATE_SPINUP) { smartai->mpos = smartai->ws->start; p->pos.x = smartai->ws->cellsize * (smartai->ws->start.x + 0.5); p->pos.z = smartai->ws->cellsize * (smartai->ws->start.y + 0.5); // Reset autonav anav->dest = p->pos; anav->timer = 0; smartai->dir = maze_longesthallway(smartai->ws->maze, MAZESIZE, smartai->ws->start.x, smartai->ws->start.y); p->rot.x = dirtoyaw(smartai->dir); // reset autorotate arot->dest = p->rot; arot->timer = 0; smartai->state = SAI_READY; eprintf("PLAYER READY: %f %f (%f), waiting for spinup\n", anav->dest.x, anav->dest.z, arot->dest.x); } break; case SAI_READY: if (smartai->ws->state == WSTATE_GAMEPLAY) { smartai->state = SAI_GAMEPLAY; eprintf("PLAYER STARTING GAMEPLAY\n"); } break; case SAI_GAMEPLAY: // Normal gameplay: move through the maze, etc. // Some states are triggered based on the timer if (smartai->timer > 0) { smartai->timer--; } // The rotation is delayed to make it feel a bit more like the original // maze, which I think determined rotation and direction upon entering // a tile. I instead calculate that in the middle of the tile. It doesn't // really line up like it does on the windows screensaver but it's // close enough for me. if (smartai->timer == 0) { if (smartai->rotstate == 1) { eprintf("TURNING RIGHT\n"); arot->dest.x += MPI_2; arot->timer = actiontime; smartai->dir = TURNRIGHT(smartai->dir); smartai->rotstate = 0; } else if (smartai->rotstate == 2) { eprintf("TURNING LEFT\n"); arot->dest.x -= MPI_2; arot->timer = actiontime; smartai->dir = TURNLEFT(smartai->dir); smartai->rotstate = 0; } } // Only decide to do things if you're not moving anymore. Movement is the // most important thing if (anav->timer == 0) { eprintf("SMARTAI: %d TIMER: %d DIR: %d POS: (%f, %f)\n", smartai->rotstate, smartai->timer, smartai->dir, p->pos.x, p->pos.z); if (smartai->mpos.x == smartai->ws->end.x && smartai->mpos.y == smartai->ws->end.y) { eprintf("YOU WIN\n"); smartai->ws->state = WSTATE_GAMEOVER; smartai->state = SAI_INIT; return; } // Player can only move forward if there's nothing in front of them if (maze_connected(smartai->ws->maze, smartai->mpos.x, smartai->mpos.y, MAZESIZE, smartai->dir)) { struct vec2i movement = dirtovec(smartai->dir); smartai->mpos.x += movement.x; smartai->mpos.y += movement.y; anav->timer = actiontime; anav->dest.x = p->pos.x + smartai->ws->cellsize * movement.x; anav->dest.z = p->pos.z + smartai->ws->cellsize * movement.y; smartai->rotstate = 0; } // Figure out if a rotation should be scheduled if (smartai->timer <= 0) { // Ok we might be moving, we might not be. Let's go ahead and calculate // rotation based on the FUTURE direction we want to turn. uint8_t rightdir = TURNRIGHT(smartai->dir); uint8_t leftdir = TURNLEFT(smartai->dir); if (maze_connected(smartai->ws->maze, smartai->mpos.x, smartai->mpos.y, MAZESIZE, rightdir)) { // Always choose right over left smartai->rotstate = 1; smartai->timer = rotdelaytime; eprintf("WILL TURN RIGHT TO: %d\n", rightdir); } else if (!maze_connected(smartai->ws->maze, smartai->mpos.x, smartai->mpos.y, MAZESIZE, smartai->dir)) { // We only move left if the player can't move forward or right smartai->rotstate = 2; smartai->timer = rotdelaytime; eprintf("WILL TURN LEFT (stuck) TO: %d\n", leftdir); } } } break; } } // Setup ECS system for our game ECS_START(mecs) // ECS_COMPONENT(ecs_worldstate); ECS_COMPONENT(ecs_world); ECS_COMPONENT(ecs_autonav); ECS_COMPONENT(ecs_autorotate); ECS_COMPONENT(ecs_placement); ECS_COMPONENT(ecs_camera); ECS_COMPONENT(ecs_smartai); ECS_COMPONENT(ecs_syncgrow); ECS_END(mecs) // And then a copy of the components here... that sucksssss ECS_CID(ecs_worldstate, 0); ECS_CID(ecs_world, 1); ECS_CID(ecs_autonav, 2); ECS_CID(ecs_autorotate, 3); ECS_CID(ecs_placement, 4); ECS_CID(ecs_camera, 5); ECS_CID(ecs_smartai, 6); ECS_CID(ecs_syncgrow, 7); ECS_SYSTEM1(mecs, sys_world, ecs_world); ECS_SYSTEM1(mecs, sys_syncgrow, ecs_syncgrow); ECS_SYSTEM2(mecs, sys_autonav, ecs_autonav, ecs_placement); ECS_SYSTEM2(mecs, sys_autorotate, ecs_autorotate, ecs_placement); ECS_SYSTEM2(mecs, sys_camera, ecs_camera, ecs_placement); ECS_SYSTEM4(mecs, sys_smartai, ecs_smartai, ecs_placement, ecs_autonav, ecs_autorotate); void init_floortexture(haloo3d_fb *floort) { uint16_t cols[1] = {0xFD93}; haloo3d_fb_init_tex(floort, 64, 64); haloo3d_apply_alternating(floort, cols, 1); haloo3d_apply_noise(floort, NULL, 1.0 / 6); } void init_ceilingtexture(haloo3d_fb *ceilt) { uint16_t cols[1] = {0xFFFF}; haloo3d_fb_init_tex(ceilt, 64, 64); haloo3d_apply_alternating(ceilt, cols, 1); haloo3d_apply_noise(ceilt, NULL, 1.0 / 4); haloo3d_apply_brick(ceilt, 16, 8, 0xFAAA); } void init_walltexture(haloo3d_fb *wallt) { haloo3d_fb_init_tex(wallt, 64, 64); uint16_t wallcols[] = {0xFA22}; haloo3d_apply_alternating(wallt, wallcols, 1); haloo3d_apply_noise(wallt, NULL, 1.0 / 8); haloo3d_apply_brick(wallt, 18, 13, 0xFEEE); // haloo3d_apply_brick(wallt, 14, 8, 0xFD94); haloo3d_apply_noise(wallt, NULL, 1.0 / 8); } void init_starttexture(haloo3d_fb *startt) { haloo3d_fb_init_tex(startt, 64, 64); haloo3d_recti rect = {.x1 = 20, .x2 = 44, .y1 = 24, .y2 = 40}; haloo3d_apply_rect(startt, &rect, 0xF777, 1); } void init_mazeinstances(haloo3d_obj_instance *floori, haloo3d_obj_instance *ceili, haloo3d_obj_instance *walli, haloo3d_obj_instance *starti) { floori->cullbackface = 0; ceili->cullbackface = 0; walli->cullbackface = 0; starti->cullbackface = 0; vec3(floori->scale.v, HSCALE, 1, HSCALE); vec3(ceili->scale.v, HSCALE, 1, HSCALE); vec3(walli->scale.v, HSCALE, 0, HSCALE); vec3(starti->scale.v, 1, 0, 1); floori->pos.x += MAZESIZE / 2.0 * HSCALE; floori->pos.z += MAZESIZE / 2.0 * HSCALE; ceili->pos.x += MAZESIZE / 2.0 * HSCALE; ceili->pos.z += MAZESIZE / 2.0 * HSCALE; walli->pos.x += MAZESIZE / 2.0 * HSCALE; walli->pos.z += MAZESIZE / 2.0 * HSCALE; ceili->pos.y = 1; } int main() { // int argc, char **argv) { haloo3d_easystore storage; haloo3d_easystore_init(&storage); haloo3d_fb screen; haloo3d_fb_init(&screen, SWIDTH, SHEIGHT); haloo3d_easyrender render; haloo3d_easyrender_init(&render, WIDTH, HEIGHT); render.camera.pos.y = 0.5; render.tprint.fb = &screen; eprintf("Initialized renderer\n"); haloo3d_easytimer frametimer, sdltimer, filltimer, logictimer; haloo3d_easytimer_init(&frametimer, AVGWEIGHT); haloo3d_easytimer_init(&sdltimer, AVGWEIGHT); haloo3d_easytimer_init(&filltimer, AVGWEIGHT); haloo3d_easytimer_init(&logictimer, AVGWEIGHT); // Load the junk + generate stuff haloo3d_obj *flooro = haloo3d_easystore_addobj(&storage, "floor"); haloo3d_obj *ceilo = haloo3d_easystore_addobj(&storage, "ceiling"); haloo3d_obj *wallo = haloo3d_easystore_addobj(&storage, "walls"); haloo3d_obj *starto = haloo3d_easystore_addobj(&storage, "start"); haloo3d_fb *floort = haloo3d_easystore_addtex(&storage, "floor"); haloo3d_fb *ceilt = haloo3d_easystore_addtex(&storage, "ceiling"); haloo3d_fb *wallt = haloo3d_easystore_addtex(&storage, "walls"); haloo3d_fb *startt = haloo3d_easystore_addtex(&storage, "start"); haloo3d_gen_plane(flooro, MAZESIZE); haloo3d_gen_plane(ceilo, MAZESIZE); haloo3d_gen_grid(wallo, MAZESIZE, 0); init_floortexture(floort); init_ceilingtexture(ceilt); init_walltexture(wallt); init_starttexture(startt); haloo3d_gen_quad(starto, startt); eprintf("Initialized models and textures\n"); worldstate wstate; memset(&wstate, 0, sizeof(worldstate)); wstate.fps = fps; wstate.state = WSTATE_INIT; wstate.cellsize = HSCALE; // Lighting. Note that for performance, the lighting is always calculated // against the base model, and is thus not realistic if the object rotates in // the world. This can be fixed easily, since each object gets its own // lighting vector, which can easily be rotated in the opposite direction of // the model struct vec3 light; vec3(light.v, 0, -MCOS(LIGHTANG), MSIN(LIGHTANG)); haloo3d_obj_instance *floori = haloo3d_easyrender_addinstance(&render, flooro, floort); haloo3d_obj_instance *walli = haloo3d_easyrender_addinstance(&render, wallo, wallt); haloo3d_obj_instance *ceili = haloo3d_easyrender_addinstance(&render, ceilo, ceilt); haloo3d_obj_instance *starti = haloo3d_easyrender_addinstance(&render, starto, startt); init_mazeinstances(floori, ceili, walli, starti); eprintf("Setup all object instances\n"); unigi_type_event event; unigi_type_resolution res; res.width = SWIDTH; res.height = SHEIGHT; res.depth = 0; int totaldrawn = 0; eprintf("Scene has %d tris, %d verts\n", render.totalfaces, render.totalverts); // Init unigi system unigi_graphics_init(); unigi_window_create(res, "maze.exe"); // render.printbuf); // render.camera.pos.y = 4; // 5; // render.camera.pitch = MPI - 0.1; // 2.2; // ceili->pos.y = -10; haloo3d_debugconsole dc; haloo3d_debugconsole_init(&dc); haloo3d_debugconsole_set(&dc, "render/fps.i", &wstate.fps); haloo3d_debugconsole_set(&dc, "render/fov.f", &fov); haloo3d_debugconsole_set(&dc, "render/trifunc.i", &render.trifunc); haloo3d_debugconsole_set(&dc, "render/ditherstart.f", &ditherstart); haloo3d_debugconsole_set(&dc, "render/ditherend.f", &ditherend); haloo3d_debugconsole_set(&dc, "render/sky.u16x", &sky); haloo3d_debugconsole_set(&dc, "camera/pos_y.f", &render.camera.pos.y); haloo3d_debugconsole_set(&dc, "camera/pitch.f", &render.camera.pitch); haloo3d_debugconsole_set(&dc, "obj/ceil/pos_y.f", &ceili->pos.y); // Set up ECS entities. For this game, we mostly have global entities. mecs ecs; mecs_init(&ecs); ecs_eid worldid = mecs_newentity(&ecs, 0); eprintf("World eid: %d\n", worldid); ECS_SETCOMPONENT(&ecs, worldid, ecs_world){ .state = &wstate, .wallmodel = wallo, .scaletimer = 0}; ecs_world *eworld = ecs.c_ecs_world + worldid; // Setup some dynamic objects ecs_eid wallid = mecs_newentity(&ecs, 0); ECS_SETCOMPONENT(&ecs, wallid, ecs_syncgrow){ .obj = walli, .scale = &eworld->scaleto, .timer = &eworld->scaletimer}; ecs_eid startid = mecs_newentity(&ecs, 0); ECS_SETCOMPONENT(&ecs, startid, ecs_syncgrow){ .obj = starti, .scale = &eworld->scaleto, .timer = &eworld->scaletimer}; // Player is ofc most complicated ecs_eid playerid = mecs_newentity(&ecs, 0); eprintf("Player eid: %d\n", playerid); // ECS_SETCOMPONENT(&ecs, playerid, ecs_worldstate){.state = &wstate}; ECS_SETCOMPONENT(&ecs, playerid, ecs_placement){ .pos = render.camera.pos, .rot = {.x = render.camera.yaw, .y = render.camera.pitch}}; ECS_SETCOMPONENT(&ecs, playerid, ecs_camera){.camera = &render.camera}; ECS_SETCOMPONENT(&ecs, playerid, ecs_autonav){.timer = 0}; ECS_SETCOMPONENT(&ecs, playerid, ecs_autorotate){.timer = 0}; ECS_SETCOMPONENT(&ecs, playerid, ecs_smartai){ .state = SAI_INIT, .ws = &wstate, .rotstate = 0, .timer = 0}; // ----------------------------------- // Actual rendering // ----------------------------------- // ceili->texture = &render.window; while (1) { haloo3d_easytimer_start(&frametimer); // render.camera.yaw += 0.008; haloo3d_perspective(render.perspective, fov, ASPECT, NEARCLIP, FARCLIP); haloo3d_easyrender_beginframe(&render); haloo3d_fb_clear(&render.window, sky); // walli->scale.y = fabs(sin(3 * render.camera.yaw)); // render.camera.up.x = sin(render.camera.yaw); // render.camera.up.y = cos(render.camera.yaw); // walli->up.x = sin(3 * render.camera.yaw); // walli->up.y = cos(4 * render.camera.yaw); do { unigi_event_get(&event); switch (event.type) { case unigi_enum_event_input_keyboard: if (event.data.input_keyboard.down) { switch (event.data.input_keyboard.button) { case KEY_SPACE: haloo3d_debugconsole_beginprompt(&dc); break; default: exit(0); } } break; } } while (event.type != unigi_enum_event_none); // --------------------------- // Game logic? // --------------------------- haloo3d_easytimer_start(&logictimer); for (int i = 0; i < ECS_MAXENTITIES; i++) { sys_world_run(&ecs, i); sys_smartai_run(&ecs, i); sys_syncgrow_run(&ecs, i); sys_autonav_run(&ecs, i); sys_autorotate_run(&ecs, i); sys_camera_run(&ecs, i); } haloo3d_easytimer_end(&logictimer); starti->pos = render.camera.pos; starti->pos.z -= 1; totaldrawn = 0; haloo3d_obj_instance *object = NULL; // Iterate over objects while ((object = haloo3d_easyrender_nextinstance(&render, object)) != NULL) { // Setup final model matrix and the precalced vertices haloo3d_easyrender_beginmodel(&render, object); // Iterate over object faces for (int fi = 0; fi < object->model->numfaces; fi++) { totaldrawn += haloo3d_easyrender_renderface( &render, object, fi, ditherstart, ditherend, minlight); } } haloo3d_easytimer_start(&filltimer); #ifdef FASTFILL haloo3d_fb_fill(&screen, &render.window); #else haloo3d_recti texrect = {.x1 = 0, .y1 = 0, .x2 = WIDTH, .y2 = HEIGHT}; haloo3d_recti screenrect = {.x1 = 0, .y1 = 0, .x2 = SWIDTH, .y2 = SHEIGHT}; haloo3d_sprite(&screen, &render.window, texrect, screenrect); #endif haloo3d_easytimer_end(&filltimer); haloo3d_print(&render.tprint, "Pframe: %05.2f (%05.2f)\nPSDLFl: %05.2f " "(%05.2f)\nFill: %05.2f " "(%05.2f)\nLogic: %05.2f (%05.2f)\nTris: %d / %d\nVerts: " "%d\nWState: %d", frametimer.last * 1000, frametimer.sum * 1000, sdltimer.last * 1000, sdltimer.sum * 1000, filltimer.last * 1000, filltimer.sum * 1000, logictimer.last * 1000, logictimer.sum * 1000, totaldrawn, render.totalfaces, render.totalverts, wstate.state); haloo3d_easytimer_start(&sdltimer); unigi_graphics_blit(0, (unigi_type_color *)screen.buffer, res.width * res.height); unigi_graphics_flush(); haloo3d_easytimer_end(&sdltimer); haloo3d_easytimer_end(&frametimer); float waittime = (1.0 / fps) - frametimer.last; if (waittime > 0) { unigi_time_sleep(waittime * unigi_time_clocks_per_s); } } // Just to get the compiler to STOP COMPLAINING about unused mecs_deleteentity(&ecs, worldid); haloo3d_easystore_deleteallobj(&storage, haloo3d_obj_free); haloo3d_easystore_deletealltex(&storage, haloo3d_fb_free); }