From cd74a29aba0c23850d6d9e435f0a8dec2efa0ffa Mon Sep 17 00:00:00 2001 From: Carlos Sanchez Date: Sat, 7 Sep 2024 11:40:24 -0400 Subject: [PATCH] Add maze for ps2 --- .gitignore | 1 + haloo3d | 2 +- ps2/Makefile | 44 ++ ps2/maze.c | 1370 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1416 insertions(+), 1 deletion(-) create mode 100644 ps2/Makefile create mode 100644 ps2/maze.c diff --git a/.gitignore b/.gitignore index 822a65c..c673d35 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,5 @@ callgrind.* # Binaries *.exe +*.elf a.out diff --git a/haloo3d b/haloo3d index 599b884..9fbc094 160000 --- a/haloo3d +++ b/haloo3d @@ -1 +1 @@ -Subproject commit 599b88475b557b1b7e502872b87928dcaf64fe7c +Subproject commit 9fbc0948f182cd46c4aaff43b632f92f72fb4fb2 diff --git a/ps2/Makefile b/ps2/Makefile new file mode 100644 index 0000000..4f2a54c --- /dev/null +++ b/ps2/Makefile @@ -0,0 +1,44 @@ +# _____ ___ ____ ___ ____ +# ____| | ____| | | |____| +# | ___| |____ ___| ____| | \ PS2DEV Open Source Project. +#----------------------------------------------------------------------- +# Copyright 2001-2022, ps2dev - http://www.ps2dev.org +# Licenced under Academic Free License version 2.0 +# Review ps2sdk README & LICENSE files for further details. + +EE_BIN = maze.elf + +# KERNEL_NOPATCH = 1 +# NEWLIB_NANO = 1 + +EE_OBJS = maze.o +EE_CFLAGS += -fdata-sections -ffunction-sections -I$(PS2SDK)/ports/include -Wformat=0 +EE_LDFLAGS += -L$(PS2SDK)/ports/lib -L$(GSKIT)/lib -lSDL2 -lgskit -ldmakit -lps2_drivers -lm -Wl,--gc-sections + +ifeq ($(DUMMY_TIMEZONE), 1) + EE_CFLAGS += -DDUMMY_TIMEZONE +endif + +ifeq ($(DUMMY_LIBC_INIT), 1) + EE_CFLAGS += -DDUMMY_LIBC_INIT +endif + +ifeq ($(KERNEL_NOPATCH), 1) + EE_CFLAGS += -DKERNEL_NOPATCH +endif + +ifeq ($(DEBUG), 1) + EE_CFLAGS += -DDEBUG -O0 -g +else + EE_CFLAGS += -Os + EE_LDFLAGS += -s +endif + +all: $(EE_BIN) + +clean: + rm -rf $(EE_OBJS) $(EE_BIN) + +# Include makefiles +include $(PS2SDK)/samples/Makefile.pref +include $(PS2SDK)/samples/Makefile.eeglobal diff --git a/ps2/maze.c b/ps2/maze.c new file mode 100644 index 0000000..5a56ad1 --- /dev/null +++ b/ps2/maze.c @@ -0,0 +1,1370 @@ +// clang-format off +// #define H3DEBUG_SKIPWHOLETRI +// #define H3DEBUG_NOCLIPPING +// #define H3DEBUG_SKIPTRIPIX +#define H3D_VOLATILE_FLOATS +// #include +#define MATHC_USE_UNIONS +#define MATHC_NO_STRUCT_FUNCTIONS +#include "../haloo3d/lib/mathc.c" +#define FNL_IMPL +#include "../haloo3d/lib/FastNoiseLite.h" +#include "../haloo3d/haloo3d.c" +#include "../haloo3d/haloo3dex_easy.c" +#include "../haloo3d/haloo3dex_gen.c" +//#include "../haloo3d/haloo3dex_img.c" +#include "../haloo3d/haloo3dex_obj.c" +#include "../haloo3d/haloo3dex_print.c" +// clang-format on + +#include "../resources/mazeendsprite.h" +#include "../resources/mousesprite.h" +#include "../resources/specwall.h" +#include "../resources/tetrahedron.h" + +#include "SDL2/SDL.h" + +#include "../ecs2.h" +// #include "keys.h" +#include "../maze_ecstypes.h" + +#include + +// INteresting flags for debugging +#define FASTFILL +#define NUMMICE 1 +// #define MOUSELOGGING +#define PLAYERLOGGING +// #define NOWALLS + +#define WIDTH 160 +#define HEIGHT 120 +#define ASPECT ((float)WIDTH / HEIGHT) +#define SWIDTH 640 +#define SHEIGHT 480 +#define NEARCLIP 0.1 +#define FARCLIP 100.0 +#define LIGHTANG -MPI / 4.0 +#define AVGWEIGHT 0.85 +// Try 0.5 and 3.5 or something +#define DITHERSTART 2.5 +#define DITHEREND 3.5 + +// Game options +#define MAZESIZE 15 +#define MAZEENDGAP (MAZESIZE / 5) +#define HSCALE 1.5 +#define MOUSESCALE 0.24 +#define MOUSESPEED 0.45 // Player speed is 0.5. Lower is faster +#define PAINTINGODDS 20 +// Some arbitrarily large number, up to you +#define MAZEHRAND 100 +// The pool of numbers to choose from when checking if a mouse +// will turn left or right randomly. Higher = less chance to turn +#define MOUSETURNRAND 4 +// The horizontal choice will be this out of MAZEHRAND. +// So, if MAZEHRAND is 100, setting this to 80 will mean +// an 8 / 10 chance to go horizontal +#define MAZEHBIAS 60 +#define PRINTMAZE +// Max amount of flip polys to generate in maze. actual amount can be lower, +// but there will always at least be 1 +#define MAXFLIPPOLYS 5 +// Min space between flip polys +#define FLIPPOLYBUFFER 3 + +// Maze grows in the positive direction +#define MAZENORTH 1 +#define MAZEEAST 2 +#define MAZEVISIT 4 +#define MAZEFLIP 8 + +#define MAZETYPEFLIP 1 + +// 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 PAINTINGNAME "painting" + +#define NUMPOLYS 1 +const char POLYNAMES[NUMPOLYS][20] = {"tetrahedron"}; + +// Store all the values users can change at the beginning +float fov = 90.0; +float minlight = 0.15; +float speed = 1.0; +int fps = 24; +uint16_t sky = 0xF644; + +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) { + switch (move) { + case DIREAST: + return (maze[x + y * size] & MAZEEAST) == 0; + case DIRWEST: + return (x > 0) && ((maze[x - 1 + y * size] & MAZEEAST) == 0); + case DIRNORTH: + return (maze[x + y * size] & MAZENORTH) == 0; + case DIRSOUTH: + return (y > 0) && ((maze[x + (y - 1) * size] & MAZENORTH) == 0); + default: + 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; + } + + start->x = rand() % size; + start->y = rand() % size; + + // Extremely simple sidewinder algorithm. Because the maze grows northeast, we + // actually have to start from the end and work backwards + for (int y = size - 1; y >= 0; y--) { + if (y == size - 1) { + for (int x = 0; x < size - 1; x++) { + maze[x + y * size] &= ~MAZEEAST; + } + continue; + } + int xs = 0; + for (int x = 0; x < size; x++) { + // If we decide to stop (or it's the end), find a place in our + // current span to carve north + if ((x == size - 1) || ((rand() % MAZEHRAND) > MAZEHBIAS)) { + maze[xs + (rand() % (x - xs + 1)) + y * size] &= ~MAZENORTH; + xs = x + 1; + } else { + maze[x + y * size] &= ~MAZEEAST; + } + } + } + + do { + end->x = rand() % size; + end->y = rand() % size; + } while (abs(end->x - start->x) < MAZEENDGAP && + abs(end->y - start->y) < MAZEENDGAP); + +#ifdef PRINTMAZE + char line[MAZESIZE * 4]; // IDK, just in case + // -1 because we draw the top line + for (int y = -1; y < size; y++) { + for (int x = size - 1; x >= 0; x--) { + line[x * 2] = (y == -1 || maze[x + y * size] & MAZENORTH) ? '_' : ' '; + line[x * 2 + 1] = + (y == -1 || (maze[x + y * size] & MAZEEAST) == 0) ? ' ' : '|'; + } + line[size * 2] = 0; + eprintf("|%s\n", line); + } +#endif + + eprintf("Maze generate: %d,%d -> %d,%d\n", start->x, start->y, end->x, + end->y); +} + +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, H3D_EASYOBJSTATE_NOTRANS); + 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, + .diefunc = NULL}; + // 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. +} + +void kill_flippoly(haloo3d_obj_instance *obj) { free(obj->lighting); } + +void create_flippoly(struct vec2i mazepos, haloo3d_easyinstancer *ins, + mecs *ecs, ecs_world *world) { + // Create the render instance, set up the ecs instance, etc. + haloo3d_obj_instance *poly = + haloo3d_easyinstantiate(ins, POLYNAMES[0], H3D_EASYOBJSTATE_NOTRANS); + vec3(poly->scale.v, 0.15, world->scaleto, 0.15); + vec3(poly->pos.v, (mazepos.x + 0.5) * world->state->cellsize, 0.35, + (mazepos.y + 0.5) * world->state->cellsize); + ecs_eid id = mecs_newentity(ecs, 0); + mallocordie(poly->lighting, sizeof(struct vec3)); + // vec3(poly->lighting->v, 0, 0, -1); //-MPI / 4, -1); + vec3(poly->lighting->v, 0, -MPI / 4, -1); + ECS_SETCOMPONENT(ecs, id, ecs_placement){ + .pos = {.x = (mazepos.x + 0.5) * world->state->cellsize, + .y = 0.35, + .z = (mazepos.y + 0.5) * world->state->cellsize}, + .rot = {.x = 0, .y = MPI * 0.3}}; + ECS_SETCOMPONENT(ecs, id, ecs_syncgrow){.obj = poly, + .scale = &world->scaleto, + .basescale = poly->scale.x, + .timer = &world->scaletimer}; + ECS_SETCOMPONENT(ecs, id, ecs_dieoninit){.obj = poly, + .render = ins->render, + .ws = world->state, + .diefunc = kill_flippoly}; + ECS_SETCOMPONENT(ecs, id, ecs_autorotate){ + .dest = {.x = MPI * 60 * 60 * 24, .y = 0}, .timer = fps * 60 * 60 * 24}; + ECS_SETCOMPONENT(ecs, id, ecs_object) poly; + ECS_SETCOMPONENT(ecs, id, ecs_mazeentity){.type = MAZETYPEFLIP, + .mpos = mazepos}; +} + +// haloo3d_fb *paintingt; + +// 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) { + // PS2ONLY: reset the painting texture + // uint16_t *oldf = paintingt->buffer; + // init_paintingtexture(paintingt); + // free(oldf); + eprintf("Generated new painting texture\n"); + // 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)); + } +} + +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)->pos = p->pos; + (*cam)->yaw = p->rot.x; + (*cam)->pitch = p->rot.y; +} + +void sys_object(ecs_object *obj, ecs_placement *p) { + (*obj)->pos = p->pos; + YAWP2VEC(p->rot.x, p->rot.y, (*obj)->lookvec.v); + // eprintf("OBJ: %f %f %f (%f %f %f)\n", (*obj)->pos.x, (*obj)->pos.y, + // (*obj)->pos.z, (*obj)->lookvec.x, (*obj)->lookvec.y, + // (*obj)->lookvec.z); +} + +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); + if (die->diefunc) { + die->diefunc(die->obj); + } + // 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); + // SUPER simple flip poly generation + for (int i = 0; i < MAXFLIPPOLYS; i++) { + struct vec2i m = {.x = rand() % w->state->size, + .y = rand() % w->state->size}; + if (m.x == w->state->start.x && m.y == w->state->start.y) { + continue; + } + for (int yc = m.y - FLIPPOLYBUFFER; yc <= m.y + FLIPPOLYBUFFER; yc++) { + if (yc < 0 || yc >= w->state->size) + continue; + for (int xc = m.x - FLIPPOLYBUFFER; xc <= m.x + FLIPPOLYBUFFER; xc++) { + if (xc < 0 || xc >= w->state->size) + continue; + if (w->state->maze[m.x + m.y * w->state->size] & MAZEFLIP) { + goto SKIPFLIPPOLYADD; + } + } + } + eprintf("ADDING FLIPPOLY TO %d,%d\n", m.x, m.y); + create_flippoly(m, w->instancer, *ecs, w); + w->state->maze[m.x + m.y * w->state->size] |= MAZEFLIP; + SKIPFLIPPOLYADD:; + } + haloo3d_easyrender_calctotals(w->instancer->render); + 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, SAI_FLIP, SAI_HOLD }; + +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, mecs **ecs, ecs_camera *cam) { + 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) { + // Reset up vector (even though I like starting upside down, it's a + // bit jarring) + vec3((*cam)->up.v, 0, 1, 0); + 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->mpos, 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) { +#ifdef PLAYERLOGGING + eprintf("TURNING BY %f\n", smartai->rotchange); +#endif + 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) { +#ifdef PLAYERLOGGING + eprintf("SMARTAI: %f TIMER: %d DIR: %d POS: (%f, %f)\n", + smartai->rotchange, smartai->timer, smartai->dir, p->pos.x, + p->pos.z); +#endif + if (smartai_mazeend(smartai)) { + return; + } + int mazeind = smartai->mpos.x + smartai->mpos.y * smartai->ws->size; + // I'm being SUPER lazy and we don't do proper collision detection with + // ecs, we just detect the tile and assign objects to tiles + // clang-format off + if (smartai->ws->maze[mazeind] & MAZEFLIP) { + ecs_eid eids[ECS_MAXENTITIES]; + int count = mecs_query(*ecs, ecs_mazeentity_fl | ecs_placement_fl, eids); + eprintf("CHECKING MAZEFLIP (%d entities)\n", count); + for(int i = 0; i < count; i++) { + ecs_mazeentity * mze = &ECS_GETCOMPONENT(*ecs, eids[i], ecs_mazeentity); + if(mze->mpos.x == smartai->mpos.x && mze->mpos.y == smartai->mpos.y) { + eprintf("FLIPPING WORLD\n"); + smartai->state = SAI_FLIP; + smartai->timer = 0; + // To be SUPER lazy, we just remove the flag and move the object + // somewhere else + ecs_placement * pl = &ECS_GETCOMPONENT(*ecs, eids[i], ecs_placement); + pl->pos.y = -10000; + smartai->ws->maze[mazeind] &= ~MAZEFLIP; + smartai->upsidedown = !smartai->upsidedown; + return; + } + } + } + // clang-format on + // 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); +#ifdef PLAYERLOGGING + eprintf("WILL TURN RIGHT TO: %d\n", rightdir); +#endif + } 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); +#ifdef PLAYERLOGGING + eprintf("WILL TURN LEFT (stuck) TO: %d\n", leftdir); +#endif + } + } + } + } + break; + case SAI_FLIP: + smartai->timer++; + mfloat_t towards = smartai->upsidedown ? -1 : 1; + if (smartai->timer >= actiontime) { + eprintf("FLIP COMPLETE\n"); + smartai->timer = actiontime / 8; + smartai->state = SAI_HOLD; + vec3((*cam)->up.v, 0, towards, 0); + } else { + mfloat_t angle = MPI * smartai->timer / actiontime; + mfloat_t y = towards * -cos(angle); + mfloat_t x = towards * -sin(angle); + switch (smartai->dir) { + case DIRNORTH: + vec3((*cam)->up.v, -x, y, 0); + break; + case DIRSOUTH: + vec3((*cam)->up.v, x, y, 0); + break; + case DIREAST: + vec3((*cam)->up.v, 0, y, x); + break; + case DIRWEST: + vec3((*cam)->up.v, 0, y, -x); + break; + } + } + break; + case SAI_HOLD: + smartai->timer--; + if (smartai->timer <= 0) { + eprintf("HOLD COMPLETE\n"); + smartai->state = SAI_GAMEPLAY; + } + break; + } +} + +void sys_mouseai(ecs_mouseai *mouseai, ecs_placement *p, ecs_autonav *anav) { + const int actiontime = fps * MOUSESPEED / speed; + switch (mouseai->state) { + case SAI_INIT: // Just reuse smartai state + // Here, we wait until the world state is spinup, in which + // case we can spawn and face + if (mouseai->ws->state == WSTATE_SPINUP) { + mouseai->mpos = (struct vec2i){.x = rand() % mouseai->ws->size, + .y = rand() % mouseai->ws->size}; + mouseai->dir = 1; + maze_to_pos(&mouseai->mpos, p->pos.v, mouseai->ws->cellsize); + // Reset autonav + autorotate + anav->dest = p->pos; + anav->timer = 0; + mouseai->state = SAI_READY; +#ifdef MOUSELOGGING + eprintf("MOUSE READY: %f %f, waiting for spinup\n", anav->dest.x, + anav->dest.z); +#endif + } + break; + case SAI_READY: + if (mouseai->ws->state == WSTATE_GAMEPLAY) { + mouseai->state = SAI_GAMEPLAY; +#ifdef MOUSELOGGING + eprintf("MOUSE STARTING GAMEPLAY\n"); +#endif + } + break; + case SAI_GAMEPLAY: + // Normal gameplay: move through the maze, etc. + if (mouseai->ws->state != WSTATE_GAMEPLAY) { +#ifdef MOUSELOGGING + eprintf("GAMEPLAY OVER, MOUSE RESETTING\n"); +#endif + mouseai->state = SAI_INIT; + anav->timer = 0; // Stop moving + return; + } + // Only decide to do things if you're not moving anymore. Movement is the + // most important thing + if (anav->timer == 0) { +#ifdef MOUSELOGGING + eprintf("MOUSEAI DIR: %d POS: (%f, %f)\n", mouseai->dir, p->pos.x, + p->pos.z); +#endif + // Look left or right randomly. You can affect how "straight" the mouse + // moves by increasing the MOUSETURNRAND + uint8_t ndir; + switch (rand() % MOUSETURNRAND) { + case 0: + ndir = TURNRIGHT(mouseai->dir); + break; + case 1: + ndir = TURNLEFT(mouseai->dir); + break; + default: + ndir = mouseai->dir; + } + // If the direction is valid, switch to it. This means that if the mouse + // is faced with just one new direction while walking down a hallway, + // there's only a CHANCE it will move that way. This ALSO allows the mouse + // to handle hitting a wall, as they will eventually choose a valid + // direction + if (maze_connected(mouseai->ws->maze, mouseai->mpos.x, mouseai->mpos.y, + mouseai->ws->size, ndir)) { + mouseai->dir = ndir; + } + // Now, regardless of what happened above, make sure the current direction + // we're moving is valid before we step that direction + if (maze_connected(mouseai->ws->maze, mouseai->mpos.x, mouseai->mpos.y, + mouseai->ws->size, mouseai->dir)) { + struct vec2i movement = dirtovec(mouseai->dir); + mouseai->mpos.x += movement.x; + mouseai->mpos.y += movement.y; + anav->timer = actiontime; + anav->dest.x = p->pos.x + mouseai->ws->cellsize * movement.x; + anav->dest.z = p->pos.z + mouseai->ws->cellsize * movement.y; + } else { + // Choose a direction at random if we can't move + mouseai->dir = (1 << (rand() % 4)); + } + } + 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_SYSTEM2(mecs, sys_object, ecs_object, ecs_placement); +ECS_SYSTEM6(mecs, sys_smartai, ecs_smartai, ecs_placement, ecs_autonav, + ecs_autorotate, mecs, ecs_camera); +ECS_SYSTEM3(mecs, sys_mouseai, ecs_mouseai, ecs_placement, ecs_autonav); + +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); + // Fill buffer with 0 + memset(startt->buffer, 0, startt->width * startt->height * sizeof(uint16_t)); + 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); +} + +void init_endtexture(haloo3d_fb *endt) { + endt->width = mazeendsprite_width; + endt->height = mazeendsprite_height; + // We "promise" we won't modify the sprite... + endt->buffer = (uint16_t *)mazeendsprite_data; +} + +void init_mousetexture(haloo3d_fb *mouset) { + mouset->width = mousesprite_width; + mouset->height = mousesprite_height; + // We "promise" we won't modify mouse sprite... + mouset->buffer = (uint16_t *)mousesprite_data; +} + +void init_paintingtexture(haloo3d_fb *endt) { + endt->width = specwall_width; + endt->height = specwall_height; + // We "promise" we won't modify the sprite... + endt->buffer = (uint16_t *)specwall_data; + // haloo3d_fb_init_tex(endt, 16, 16); + // 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]; + } + } +} + +static inline void unigi_platform_color_16_to_32(uint16_t color, uint8_t *a, + uint8_t *r, uint8_t *g, + uint8_t *b) { + *a = (color >> 12) & 0xF; + *a = *a << 4; + *r = (color >> 8) & 0xF; + *r = *r << 4; + *g = (color >> 4) & 0xF; + *g = *g << 4; + *b = color & 0xF; + *b = *b << 4; +} + +uint16_t *sdlcolors; + +void unigi_graphics_init(SDL_Surface *surface) { + // int bpp = surface->format->BytesPerPixel; + sdlcolors = malloc(4096 * 2); + printf("BYTESPERPIXEL: %d\n", surface->format->BytesPerPixel); + printf("RMASK, GMASK, BMASK: %x, %x, %x\n", surface->format->Rmask, + surface->format->Gmask, surface->format->Bmask); + + uint16_t color = 0; + while (color < 4096) { + uint16_t r = ((color >> 8) & 0xF) / 15.0 * 255; + uint16_t g = ((color >> 4) & 0xF) / 15.0 * 255; + uint16_t b = (color & 0xF) / 15.0 * 255; + // uint32_t col = SDL_MapRGB(surface->format, r / 2, g / 2, b / 2); + sdlcolors[color] = SDL_MapRGB(surface->format, r / 2, g / 2, b / 2); + // uint8_t *col8 = (uint8_t *)&col; + // for (int i = 0; i < bpp; i++) { + // sdlcolors[color * bpp + i] = col8[i]; + // } + // sdlcolors[color] = SDL_MapRGB(surface->format, r, g, b); + // 0x8000 | (b << 10) | (g << 5) | (r); + ++color; + } +} + +void haloo3d_fb_fill2(haloo3d_fb *dst, haloo3d_fb *src) { + int scalex = dst->width / src->width; + int scaley = dst->height / src->height; + int scale = scalex < scaley ? scalex : scaley; + if (scale == 0) { + return; + } + int newwidth = scale * src->width; + int newheight = scale * src->height; + int dstofsx = (dst->width - newwidth) / 2; + int dstofsy = (dst->height - newheight) / 2; + // Need a step per y of src and a step per y of dst + uint16_t *dbuf_y = &dst->buffer[dstofsx + dstofsy * dst->width]; + uint16_t *sbuf_y = src->buffer; + uint16_t *sbuf_ye = src->buffer + src->width * src->height; + // Iterate over original image + while (sbuf_y < sbuf_ye) { + for (int sy = 0; sy < scale; sy++) { + uint16_t *sbuf = sbuf_y; + uint16_t *sbufe = sbuf_y + src->width; + uint16_t *dbuf = dbuf_y + dstofsx; + while (sbuf < sbufe) { + uint16_t sdlcol = sdlcolors[(*sbuf) & 0xFFF]; + // uint16_t sdlcol = ((uint16_t *)sdlcolors)[(*sbuf) & 0xFFF]; + for (int sx = 0; sx < scale; sx++) { + *dbuf = sdlcol; + dbuf++; + } + sbuf++; + } + dbuf_y += dst->width; + } + sbuf_y += src->width; + } +} + +int main() { // int argc, char **argv) { + + srand(clock()); + + printf("SDL_Init\n"); + SDL_Init(SDL_INIT_VIDEO); + printf("SDL_Window\n"); + SDL_Window *window = SDL_CreateWindow("game", 0, 0, SWIDTH, SHEIGHT, 0); + // SDL_WINDOW_FULLSCREEN_DESKTOP); // | SDL_WINDOW_ALLOW_HIGHDPI); + // surface->format->format = SDL_PIXELFORMAT_BGR24; + + // fsFlag = SDL_WINDOW_FULLSCREEN; + // SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN); + // SDL_DestroyWindow(window); + + // window = SDL_CreateWindow("game", 0, 0, SWIDTH, SHEIGHT, + // SDL_WINDOW_ALLOW_HIGHDPI); SDL_SetWindowFullscreen(window, + // SDL_WINDOW_FULLSCREEN); + + SDL_Surface *surface = SDL_GetWindowSurface(window); + unigi_graphics_init(surface); + + haloo3d_easystore storage; + haloo3d_easystore_init(&storage); + + haloo3d_fb screen; + screen.width = SWIDTH; + screen.height = SHEIGHT; + screen.buffer = surface->pixels; + + haloo3d_easyrender render; + haloo3d_easyrender_init(&render, WIDTH, HEIGHT); + render.camera.pos.y = 0.5; + render.tprint.fb = &screen; + render.autolightfix = 1; + render.rendersettings.ditherclose = DITHERSTART; + render.rendersettings.ditherfar = DITHEREND; + // render.rendersettings.flags &= ~(H3DR_TEXTURED); + render.rendersettings.pctminsize = 0; + // render.rendersettings.flags = (H3DR_PCT | H3DR_TEXTURED | H3DR_TRANSPARENCY + // | H3DR_LIGHTING | H3DR_DITHERTRI); + 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 *mouseo = haloo3d_easystore_addobj(&storage, "mouse"); + 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 *mouset = haloo3d_easystore_addtex(&storage, "mouse"); + haloo3d_fb *paintingt = haloo3d_easystore_addtex(&storage, PAINTINGNAME); + + for (int i = 0; i < NUMPOLYS; i++) { + haloo3d_fb *polyt = haloo3d_easystore_addtex(&storage, POLYNAMES[i]); + haloo3d_obj *polyo = haloo3d_easystore_addobj(&storage, POLYNAMES[i]); + switch (i) { + default: + haloo3d_obj_loadstring(polyo, tetrahedron_string); + break; + } + haloo3d_gen_solidtex(polyt, 0xFDDD); + } + + 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_mousetexture(mouset); + 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)); + + // WARN: the order you draw these things can matter greatly! + haloo3d_obj_instance *walli = haloo3d_easyrender_addinstance( + &render, wallo, wallt, H3D_EASYOBJSTATE_NOTRANS); + haloo3d_obj_instance *floori = haloo3d_easyrender_addinstance( + &render, planeo, floort, H3D_EASYOBJSTATE_NOTRANS); + haloo3d_obj_instance *ceili = haloo3d_easyrender_addinstance( + &render, planeo, ceilt, H3D_EASYOBJSTATE_NOTRANS); + haloo3d_obj_instance *starti = + haloo3d_easyrender_addinstance(&render, starto, startt, 0); + haloo3d_obj_instance *endi = + haloo3d_easyrender_addinstance(&render, endo, endt, 0); + init_mazeinstances(floori, ceili, walli); + init_billboard(starti, 1.0); + init_billboard(endi, 0.25); + eprintf("Setup all static object instances\n"); + + int totaldrawn = 0; + + eprintf("Scene has %d tris, %d verts\n", render.totalfaces, + render.totalverts); + + // 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, +#ifdef NOWALLS + .basescale = 0, // can't set to scale because hscale +#else + .basescale = 1, +#endif + .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) & 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, + .upsidedown = 0, + .startmarker = starti}; + + for (int i = 0; i < NUMMICE; i++) { + haloo3d_obj_instance *mousei = + haloo3d_easyrender_addinstance(&render, mouseo, mouset, 0); + init_billboard(mousei, MOUSESCALE); + // Mouse should be near the floor + mousei->pos.y = MOUSESCALE; + ecs_eid mouseid = mecs_newentity(&ecs, 0); + eprintf("Mouse eid: %d\n", mouseid); + ECS_SETCOMPONENT(&ecs, mouseid, ecs_placement){.pos = mousei->pos, + .rot = {.x = 0, .y = 0}}; + ECS_SETCOMPONENT(&ecs, mouseid, ecs_autonav){.timer = 0}; + ECS_SETCOMPONENT(&ecs, mouseid, ecs_object) mousei; + ECS_SETCOMPONENT(&ecs, mouseid, ecs_mouseai){.state = SAI_INIT, + .ws = &wstate}; + ECS_SETCOMPONENT(&ecs, mouseid, ecs_billboard){ + .obj = mousei, .lookat = &render.camera.pos}; + ECS_SETCOMPONENT(&ecs, mouseid, ecs_syncgrow){.obj = mousei, + .scale = &eworld->scaleto, + .basescale = mousei->scale.x, + .timer = &eworld->scaletimer}; + } + + // ----------------------------------- + // 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); + + // --------------------------- + // 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_mouseai_run(&ecs, i); + sys_syncgrow_run(&ecs, i); + sys_autonav_run(&ecs, i); + sys_autorotate_run(&ecs, i); + sys_camera_run(&ecs, i); + // NOTE: must run object BEFORE billboard so that billboard + // overrides the rotation from object (we want this) + sys_object_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, minlight); + } + } + + haloo3d_easytimer_start(&filltimer); +#ifdef FASTFILL + haloo3d_fb_fill2(&screen, &render.window); + // for (int y = 0; y < 255; y++) { + // for (int x = 0; x < 50; x++) { + // haloo3d_fb_set(&screen, x + 250, y + 100, + // SDL_MapRGB(surface->format, y, 0, 0)); + // } + // } + // for (int y = 0; y < 64; y++) { + // for (int x = 0; x < 64; x++) { + // haloo3d_fb_set(&screen, x + 250, y + 250, sdlcolors[x + y * 64]); + // } + // } +#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, + "\n\n Pframe: %05.2f (%05.2f)\n MinMax: %05.2f / %05.2f\n PSDLFl: " + "%05.2f (%05.2f)\n Fill: %05.2f " + "(%05.2f)\n Logic: %05.2f (%05.2f)\n Tris: %d / %d\n Verts: " + "%d\n WState: %d", + frametimer.last * 1000, frametimer.sum * 1000, frametimer.min * 1000, + frametimer.max * 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); + SDL_UpdateWindowSurface(window); + haloo3d_easytimer_end(&sdltimer); + + haloo3d_easytimer_end(&frametimer); + + float waittime = (1.0 / fps) - frametimer.last; + if (waittime > 0) { + SDL_Delay(waittime * 1000); + } + } + + // 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); +}