#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 "maze_ecstypes.h" #include // INteresting flags for performance comparisons #define FASTFILL #define WIDTH 320 #define HEIGHT 200 #define ASPECT ((float)WIDTH / HEIGHT) #define SCREENSCALE 3 #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 MAZEENDGAP (MAZESIZE / 5) #define HSCALE 1.5 #define PAINTINGODDS 20 // 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; #define ENDTEXTURE "resources/mazeend.ppm" #define PAINTINGTEXTURE "resources/specwall.ppm" #define PAINTINGNAME "painting" // Store all the values users can change at the beginning float ditherstart = -1; float ditherend = 8; float fov = 90.0; float minlight = 0.25; float speed = 1.0; 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) { 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; } void maze_to_pos(struct vec2i *maze, mfloat_t *dest, mfloat_t cellsize) { dest[0] = cellsize * (maze->x + 0.5); dest[2] = cellsize * (maze->y + 0.5); } // 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; } } do { end->x = rand() % size; end->y = rand() % size; } while (abs(end->x - start->x) < MAZEENDGAP && abs(end->y - start->y) < MAZEENDGAP); eprintf("Maze generate: %d,%d -> %d,%d maxdepth: %d\n", start->x, start->y, end->x, end->y, maxmazetop); free(mazestack); } // int rand_painting(int size) { // return (rand() % ()) == 0; // } void create_painting(struct vec2i mazepos, uint8_t dir, haloo3d_easyinstancer *ins, mecs *ecs, ecs_world *world) { // Create the painting render instance, set up the ecs instance, etc. haloo3d_obj_instance *painting = haloo3d_easyinstantiate(ins, PAINTINGNAME); ecs_eid id = mecs_newentity(ecs, 0); ECS_SETCOMPONENT(ecs, id, ecs_syncgrow){.obj = painting, .scale = &world->scaleto, .basescale = 1, .timer = &world->scaletimer}; ECS_SETCOMPONENT(ecs, id, ecs_dieoninit){ .obj = painting, .render = ins->render, .ws = world->state}; // Fix up some things based on dir. switch (dir) { case DIRNORTH: mazepos.y++; break; case DIREAST: // East also needs the rotation from west mazepos.x++; // fall through case DIRWEST: vec3(painting->lookvec.v, 1, 0, 0); break; } // OK now that it's setup, we need to put it in the right spot and scale it vec3(painting->pos.v, mazepos.x * world->state->cellsize, 0, mazepos.y * world->state->cellsize); vec3(painting->scale.v, HSCALE, world->scaleto, HSCALE); // rotation is around the left edge. That's both where we put it AND // where the painting rotates around. } // Generate walls AND create paintings. Kind of doing too much void maze_wall_generate(uint8_t *maze, int size, haloo3d_obj *obj, haloo3d_easyinstancer *ins, mecs *ecs, ecs_world *world) { // 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++) { struct vec2i mazepos = {.x = x, .y = y}; if (!maze_connected(maze, x, y, size, DIREAST)) { haloo3d_gen_grid_quad(obj, x, y, dirtovec(DIREAST)); if ((rand() % PAINTINGODDS) == 0) { create_painting(mazepos, DIREAST, ins, ecs, world); } } if (!maze_connected(maze, x, y, size, DIRNORTH)) { haloo3d_gen_grid_quad(obj, x, y, dirtovec(DIRNORTH)); if ((rand() % PAINTINGODDS) == 0) { create_painting(mazepos, DIRNORTH, ins, ecs, world); } } } } 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)); } } // // A simple linked list of objects // struct objlist { // haloo3d_obj_instance *obj; // struct objlist *next; // }; // // // Add a new link at the given elem // struct objlist *objlist_add(struct objlist *elem, haloo3d_obj_instance *obj) // { // mallocordie(elem->next, sizeof(struct objlist *)); // elem->next->obj = obj; // elem->next->next = NULL; // return elem->next; // } // // // Free every single element starting at head // void objlist_free(struct objlist *head, haloo3d_easyrender *r) { // // void (*freefunc)(haloo3d_obj_instance *obj)) { // if (head) { // objlist_free(head->next, r); // haloo3d_easyrender_deleteinstance(r, head->obj); // // if (freefunc) { // // freefunc(head->obj); // //} // free(head); // } // } enum { WSTATE_INIT = 0, WSTATE_SPINUP = 1, WSTATE_GAMEPLAY = 2, WSTATE_GAMEOVER = 3, WSTATE_SPINDOWN = 4 }; void sys_billboard(ecs_billboard *bb) { // In our current system, the lookvec is the direction it wants to face, not a // "lookat" point. To lookat something, you simply get the vector pointing // towards it. Since we want them to never be looking up or down, y is luckily // 0. // NOTE: since we look towards the -z dir, we invert z. IDK if that's right // but things spawn looking in the same direction as the camera, so turning it // to face the player would make the texture backwards. bb->obj->lookvec.x = -(bb->lookat->x - bb->obj->pos.x); bb->obj->lookvec.y = 0; bb->obj->lookvec.z = -(bb->lookat->z - bb->obj->pos.z); } void sys_syncgrow(ecs_syncgrow *sg) { // Only perform logic when a timer is running if (*sg->timer) { mfloat_t scale = sg->basescale * *sg->scale; if (*sg->timer == 1) { // Just jump right to it on the last frame. We don't run on frame 0 sg->obj->scale.y = scale; } else { mfloat_t scaleleft = 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; } void sys_dieoninit(ecs_dieoninit *die, mecs **ecs) { if (die->ws->state == WSTATE_INIT) { ecs_eid id = mecs_eid(ecs); eprintf("DELETING SELF: %d\n", id); // Trivially delete the entity from the renderer haloo3d_easyrender_deleteinstance(die->render, die->obj); // Delete ourselves from existence mecs_deleteentity(*ecs, id); } } void sys_world(ecs_world *w, mecs **ecs) { const int spinspeed = fps * 4.0 / (5 * speed); switch (w->state->state) { case WSTATE_INIT: // We don't need to free anything created from previous runs; they delete // themselves maze_generate(w->state->maze, w->state->size, &w->state->start, &w->state->end); maze_wall_generate(w->state->maze, w->state->size, w->wallmodel, w->instancer, *ecs, w); eprintf("INIT MAZE COMPLETE, spinning up walls\n"); maze_to_pos(&w->state->end, w->endobj->pos.v, w->state->cellsize); 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"); // w->scaletimer = 1; 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, }; int smartai_mazeend(ecs_smartai *smartai) { 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 1; } return 0; } void sys_smartai(ecs_smartai *smartai, ecs_placement *p, ecs_autonav *anav, ecs_autorotate *arot) { const int actiontime = fps / (2 * speed); const int rotdelaytime = actiontime / 2; // 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; smartai->dir = maze_longesthallway(smartai->ws->maze, smartai->ws->size, smartai->ws->start.x, smartai->ws->start.y); maze_to_pos(&smartai->ws->start, p->pos.v, smartai->ws->cellsize); p->rot.x = dirtoyaw(smartai->dir); // Move startmarker to in front of player. struct vec2i lookdir = dirtovec(smartai->dir); smartai->startmarker->pos.x = p->pos.x + lookdir.x; smartai->startmarker->pos.z = p->pos.z + lookdir.y; // Reset autonav + autorotate anav->dest = p->pos; anav->timer = 0; 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 && smartai->rotchange) { eprintf("TURNING BY %f\n", smartai->rotchange); arot->dest.x += smartai->rotchange; smartai->rotchange = 0; arot->timer = actiontime; } // Only decide to do things if you're not moving anymore. Movement is the // most important thing if (anav->timer == 0) { eprintf("SMARTAI: %f TIMER: %d DIR: %d POS: (%f, %f)\n", smartai->rotchange, smartai->timer, smartai->dir, p->pos.x, p->pos.z); if (smartai_mazeend(smartai)) { 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, smartai->ws->size, 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; } // Figure out if a rotation should be scheduled if (!(smartai->mpos.x == smartai->ws->end.x && smartai->mpos.y == smartai->ws->end.y)) { // 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, smartai->ws->size, rightdir)) { // Always choose right over left smartai->rotchange += MPI_2; smartai->timer = rotdelaytime; smartai->dir = TURNRIGHT(smartai->dir); eprintf("WILL TURN RIGHT TO: %d\n", rightdir); } else { // This while loop lets us turn around if necessary, so reaching a // dead end isn't super painful waiting for two rotations while (!maze_connected(smartai->ws->maze, smartai->mpos.x, smartai->mpos.y, smartai->ws->size, smartai->dir)) { // We seem to have reached a wall. Do we need to turn ALL the way // around? We only move left if the player can't move forward or // right smartai->rotchange -= MPI_2; smartai->timer = rotdelaytime; smartai->dir = TURNLEFT(smartai->dir); eprintf("WILL TURN LEFT (stuck) TO: %d\n", leftdir); } } } } break; } } ECS_SYSTEM2(mecs, sys_world, ecs_world, mecs); ECS_SYSTEM2(mecs, sys_dieoninit, ecs_dieoninit, mecs); ECS_SYSTEM1(mecs, sys_syncgrow, ecs_syncgrow); ECS_SYSTEM1(mecs, sys_billboard, ecs_billboard); 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, 128); haloo3d_recti rect = {.x1 = 4, .x2 = 60, .y1 = 58, .y2 = 71}; // Create a temporary printing system just to print to this. IDK, maybe I'll // make this betterin the future haloo3d_print_tracker pt; const uint16_t bgcol = 0xF888; const uint16_t fgcol = 0xF222; char buf[64]; uint8_t dither[8]; haloo3d_print_initdefault(&pt, buf, sizeof(buf)); pt.bcolor = 0; // Full transparency pt.fb = startt; // haloo3d_getdither4x4(1.0, dither); // haloo3d_apply_fillrect(startt, &rect, 0xFAAA, dither); int pbx = rect.x2 - 42; int pby = rect.y1 + 2; for (int i = 0; i < 9; i++) { int x = -1 + (i % 3); int y = -1 + (i / 3); pt.fcolor = 0xFAAA; pt.x = pbx + x; pt.y = pby + y; haloo3d_print(&pt, "START"); } pt.fcolor = fgcol; pt.x = pbx; pt.y = pby; haloo3d_print(&pt, "START"); uint16_t binbows[4] = {0xF6C5, 0xFF63, 0xFFC0, 0xF48D}; haloo3d_getdither4x4(1, dither); for (int i = 0; i < 4; i++) { int x = rect.x1 + 2 + 5 * (i % 2); int y = rect.y1 + 2 + 5 * (i / 2); haloo3d_recti wrect = {.x1 = x, .x2 = x + 4, .y1 = y, .y2 = y + 4}; haloo3d_apply_fillrect(startt, wrect, binbows[i], dither); } haloo3d_apply_rect(startt, rect, bgcol, 1); rect.x1--; rect.x2++; rect.y1--; rect.y2++; haloo3d_apply_rect(startt, rect, fgcol, 1); } // We COULD generate the end texture maybe, but I don't want to void init_endtexture(haloo3d_fb *endt) { haloo3d_img_loadppmfile(endt, ENDTEXTURE); // Assume the corner of the image is the "transparent" color haloo3d_img_totransparent(endt, haloo3d_fb_get(endt, 0, 0)); } void init_paintingtexture(haloo3d_fb *endt) { haloo3d_img_loadppmfile(endt, PAINTINGTEXTURE); } void init_billboard(haloo3d_obj_instance *bb, mfloat_t scale) { // Haven't actually generated the object yet, oops. We don't let billboards // all share the same model, as they might require slightly different // requirements. struct vec3 center = {.x = 0, .y = 0.5, .z = 0}; haloo3d_gen_quad(bb->model, bb->texture, center); bb->cullbackface = 0; // This only works if bb is not centered at 0,0. We do this so that the // billboards "grow up" like the walls do, instead of just expanding out of // nothingness bb->pos.y = 0.5 * (1 - scale); vec3(bb->scale.v, scale, 0, scale); } void init_mazeinstances(haloo3d_obj_instance *floori, haloo3d_obj_instance *ceili, haloo3d_obj_instance *walli) { floori->cullbackface = 0; ceili->cullbackface = 0; walli->cullbackface = 0; vec3(floori->scale.v, HSCALE, 1, HSCALE); vec3(ceili->scale.v, HSCALE, 1, HSCALE); vec3(walli->scale.v, HSCALE, 0, HSCALE); 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; } // Given a pointer into an obj, fill it with everything required to make // the painting that clips with the wall (it's a cube) void create_paintingobj(haloo3d_obj *obj) { // 2 for each face; we have 4 faces (top and bottom don't need anything) haloo3d_gen_obj_prealloc(obj, 8, 8, 8); // ppm is 128x128 but 1 pixel along the top is the wall side const mfloat_t ptextop = 1.0 / 128.0; const mfloat_t thickness = 1.0 / 64.0; // box will be aligned along x/y axis, so it will be "facing" the negative z // dir like most other models. Box is topleft, topright, bottomleft, // bottomright, then same on other side. Box is not centered around 0, // intstead it is the same as a wall where iti starts from y=0 and goes to // y=1 vec4(obj->vertices[0].v, 0, 1, thickness, 1); vec4(obj->vertices[1].v, 1, 1, thickness, 1); vec4(obj->vertices[2].v, 0, 0, thickness, 1); vec4(obj->vertices[3].v, 1, 0, thickness, 1); vec4(obj->vertices[4].v, 0, 1, -thickness, 1); vec4(obj->vertices[5].v, 1, 1, -thickness, 1); vec4(obj->vertices[6].v, 0, 0, -thickness, 1); vec4(obj->vertices[7].v, 1, 0, -thickness, 1); // Now, the vtexture points. Some might be dupes, I don't care. First 4 are // the painting texture, next 4 are the wall texture vec3(obj->vtexture[0].v, 0.001, 0.999 - ptextop, 0); vec3(obj->vtexture[1].v, 0.999, 0.999 - ptextop, 0); vec3(obj->vtexture[2].v, 0.001, 0.001, 0); vec3(obj->vtexture[3].v, 0.999, 0.001, 0); vec3(obj->vtexture[4].v, 0.001, 0.999, 0); vec3(obj->vtexture[5].v, 0.999, 0.999, 0); vec3(obj->vtexture[6].v, 0.001, 1.0 - ptextop, 0); vec3(obj->vtexture[7].v, 0.999, 1.0 - ptextop, 0); // Preconstruct the simplified format of the face vertices and vtexture. // First 3 are vertices, next are vtexture // clang-format: off const uint8_t f[8][6] = { {0, 2, 3, 0, 2, 3}, // front face {0, 3, 1, 0, 3, 1}, {5, 7, 6, 0, 2, 3}, // back face {5, 6, 4, 0, 3, 1}, {1, 3, 7, 4, 6, 7}, // front side {1, 7, 5, 4, 7, 5}, {4, 6, 2, 4, 6, 7}, // back side {4, 2, 0, 4, 7, 5}, }; // clang-format: on for (int fi = 0; fi < obj->numfaces; fi++) { for (int v = 0; v < 3; v++) { obj->faces[fi][v].posi = f[fi][v]; obj->faces[fi][v].texi = f[fi][v + 3]; } } } 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_easyinstancer instancer = {.storage = &storage, .render = &render}; 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. The floor and ceiling use the SAME model haloo3d_obj *planeo = haloo3d_easystore_addobj(&storage, "plane"); haloo3d_obj *wallo = haloo3d_easystore_addobj(&storage, "walls"); haloo3d_obj *starto = haloo3d_easystore_addobj(&storage, "start"); haloo3d_obj *endo = haloo3d_easystore_addobj(&storage, "end"); haloo3d_obj *paintingo = haloo3d_easystore_addobj(&storage, PAINTINGNAME); 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_fb *endt = haloo3d_easystore_addtex(&storage, "end"); haloo3d_fb *paintingt = haloo3d_easystore_addtex(&storage, PAINTINGNAME); haloo3d_gen_plane(planeo, MAZESIZE); haloo3d_gen_grid(wallo, MAZESIZE, 0); create_paintingobj(paintingo); init_floortexture(floort); init_ceilingtexture(ceilt); init_walltexture(wallt); init_starttexture(startt); init_endtexture(endt); init_paintingtexture(paintingt); eprintf("Initialized models and textures\n"); worldstate wstate; memset(&wstate, 0, sizeof(worldstate)); // The maze won't change size (for now), so we can set it here to some // constant array uint8_t maze[MAZESIZE * MAZESIZE]; wstate.size = MAZESIZE; wstate.maze = maze; 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, planeo, floort); haloo3d_obj_instance *walli = haloo3d_easyrender_addinstance(&render, wallo, wallt); haloo3d_obj_instance *ceili = haloo3d_easyrender_addinstance(&render, planeo, ceilt); haloo3d_obj_instance *starti = haloo3d_easyrender_addinstance(&render, starto, startt); haloo3d_obj_instance *endi = haloo3d_easyrender_addinstance(&render, endo, endt); init_mazeinstances(floori, ceili, walli); init_billboard(starti, 1.0); init_billboard(endi, 0.25); eprintf("Setup all static 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, "game/speed.f", &speed); haloo3d_debugconsole_set(&dc, "render/fps.i", &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); eprintf("ECS sys size: %zu\n", sizeof(mecs)); ecs_eid worldid = mecs_newentity(&ecs, 0); eprintf("World eid: %d\n", worldid); ECS_SETCOMPONENT(&ecs, worldid, ecs_world){.state = &wstate, .wallmodel = wallo, .endobj = endi, .scaletimer = 0, .instancer = &instancer}; 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, .basescale = 1, .timer = &eworld->scaletimer}; ecs_eid startid = mecs_newentity(&ecs, 0); ECS_SETCOMPONENT(&ecs, startid, ecs_syncgrow){.obj = starti, .scale = &eworld->scaleto, .basescale = starti->scale.x, .timer = &eworld->scaletimer}; ECS_SETCOMPONENT(&ecs, startid, ecs_billboard){.obj = starti, .lookat = &render.camera.pos}; ecs_eid endid = mecs_newentity(&ecs, 0); ECS_SETCOMPONENT(&ecs, endid, ecs_syncgrow){.obj = endi, .scale = &eworld->scaleto, .basescale = endi->scale.x, .timer = &eworld->scaletimer}; ECS_SETCOMPONENT(&ecs, endid, ecs_billboard){.obj = endi, .lookat = &render.camera.pos}; // Player is ofc most complicated ecs_eid playerid = mecs_newentity(&ecs, 0); eprintf("Player eid: %d\n", playerid); 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, .rotchange = 0, .timer = 0, .startmarker = starti}; // ----------------------------------- // 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); sys_billboard_run(&ecs, i); sys_dieoninit_run(&ecs, i); } haloo3d_easytimer_end(&logictimer); 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); }