diff --git a/ecs2.h b/ecs2.h index 243374f..790751d 100644 --- a/ecs2.h +++ b/ecs2.h @@ -13,6 +13,10 @@ #define ECS_MAXENTITIES 1024 #endif +#ifndef ECS_FNTYPE +#define ECS_FNTYPE +#endif + // We reserve one slot to automatically point back to the ecs system. // This is the unusable 64th ID = 63 (your ids should start from 0) #define ECS_MAXCTYPES 63 @@ -24,6 +28,8 @@ typedef unsigned long long ecs_cid; typedef int ecs_eid; +// -------------- ECS BUILDERS ----------------------- + #define ECS_START(name) \ typedef struct name name; \ struct name { \ @@ -36,14 +42,28 @@ typedef int ecs_eid; ; \ const ecs_cid name##_fl = ECS_SELFFLAG; +// Create an ECS component within an ECS struct +#define ECS_COMPONENT(type) type c_##type[ECS_MAXENTITIES]; + +// Define the CIDs necessary to access a component (each component MUST +// have a unique id and I can't do that inside a macro without requiring +// extensions). Note: can't use enum, because we need the flag value too. +// (unless you have a compiler that supports iterating over macro varargs) +#define ECS_CID(type, id) \ + const int type##_id = id; \ + const ecs_cid type##_fl = (1ULL << id); + +// -------------- ECS EXTRA FUNCTIONS ----------------------- + // NOTE: these generate functions individually because some systems might // not allow unused functions and you may not need them all #define ECS_FN_INIT(name) \ /* Always initialize the ecs system when you create a new one! */ \ - static void name##_init(name *_ecs) { memset(_ecs, 0, sizeof(name)); } + ECS_FNTYPE void name##_init(name *_ecs) { memset(_ecs, 0, sizeof(name)); } + #define ECS_FN_NEWENTITY(name) \ /* Create an entity with the given base components (usually 0) */ \ - static ecs_eid name##_newentity(name *_ecs, ecs_cid basecomponents) { \ + ECS_FNTYPE ecs_eid name##_newentity(name *_ecs, ecs_cid basecomponents) { \ for (int i = 0; i < ECS_MAXENTITIES; i++) { \ ecs_eid id = _ecs->entitytop; \ _ecs->entitytop = (_ecs->entitytop + 1) % ECS_MAXENTITIES; \ @@ -55,20 +75,22 @@ typedef int ecs_eid; } \ return -1; \ } + #define ECS_FN_DELETEENTITY(name) \ /* Delete an entity by eid from the given ecs sys */ \ - static void name##_deleteentity(name *_ecs, ecs_eid eid) { \ + ECS_FNTYPE void name##_deleteentity(name *_ecs, ecs_eid eid) { \ if (eid >= 0 && eid < ECS_MAXENTITIES) \ _ecs->entities[eid] = 0; \ } + #define ECS_FN_QUERY(name) \ /* Whether an entity matches a given list of components by flag */ \ - static int name##_match(name *_ecs, ecs_eid _eid, ecs_cid _comps) { \ + ECS_FNTYPE int name##_match(name *_ecs, ecs_eid _eid, ecs_cid _comps) { \ ecs_cid _realcomps = ECS_SELFFLAG | _comps; \ return (_ecs->entities[_eid] & _realcomps) == _realcomps; \ } \ /* fill given list with eids of entities that match. careful with size */ \ - static int name##_query(name *_ecs, ecs_cid _comps, ecs_eid *out) { \ + ECS_FNTYPE int name##_query(name *_ecs, ecs_cid _comps, ecs_eid *out) { \ int count = 0; \ for (int i = 0; i < ECS_MAXENTITIES; i++) { \ if (name##_match(_ecs, i, _comps)) { \ @@ -77,25 +99,22 @@ typedef int ecs_eid; } \ return count; \ } + #define ECS_FN_EID(name) \ /* Calculate the eid from a double ecs pointer. Useful to get eid in sys */ \ - static ecs_eid name##_eid(name **_ecs) { \ + ECS_FNTYPE ecs_eid name##_eid(name **_ecs) { \ return (ecs_eid)(_ecs - (*_ecs)->c_##name); \ } -// Create an ECS component within an ECS struct -#define ECS_COMPONENT(type) type c_##type[ECS_MAXENTITIES]; +// ------------------ ECS GENERAL MACROS ---------------------------- + +#define ECS_COMPONENTS(_ecs, eid) (_ecs->entities[eid]) + +#define ECS_HASCOMPONENT(_ecs, eid, type) \ + ((_ecs->entities[eid] & type##_fl) != 0) #define ECS_GETCOMPONENT(_ecs, eid, type) (_ecs)->c_##type[eid] -// Define the CIDs necessary to access a component (each component MUST -// have a unique id and I can't do that inside a macro without requiring -// extensions). Note: can't use enum, because we need the flag value too. -// (unless you have a compiler that supports iterating over macro varargs) -#define ECS_CID(type, id) \ - const int type##_id = id; \ - const ecs_cid type##_fl = (1ULL << id); - // Set the given entity to have the given component (by type). Follow this // macro immediately with the struct initializer #define ECS_SETCOMPONENT(_ecs, eid, type) \ @@ -177,7 +196,7 @@ typedef int ecs_eid; // pre-pulled items from the entity component arrays. The new function is // named the same as the old one, just with _run appeneded #define ECS_SYSTEM1(ecsname, fname, type1) \ - void fname##_run(ecsname *_ecs, ecs_eid eid) { \ + ECS_FNTYPE void fname##_run(ecsname *_ecs, ecs_eid eid) { \ ecs_cid _ecsflags = type1##_fl; \ if (ecsname##_match(_ecs, eid, _ecsflags)) { \ fname(_ecs->c_##type1 + eid); \ @@ -188,7 +207,7 @@ typedef int ecs_eid; // pre-pulled items from the entity component arrays. The new function is // named the same as the old one, just with _run appeneded #define ECS_SYSTEM2(ecsname, fname, type1, type2) \ - void fname##_run(ecsname *_ecs, ecs_eid eid) { \ + ECS_FNTYPE void fname##_run(ecsname *_ecs, ecs_eid eid) { \ ecs_cid _ecsflags = type1##_fl | type2##_fl; \ if (ecsname##_match(_ecs, eid, _ecsflags)) { \ fname(_ecs->c_##type1 + eid, _ecs->c_##type2 + eid); \ @@ -199,7 +218,7 @@ typedef int ecs_eid; // pre-pulled items from the entity component arrays. The new function is // named the same as the old one, just with _run appeneded #define ECS_SYSTEM3(ecsname, fname, type1, type2, type3) \ - void fname##_run(ecsname *_ecs, ecs_eid eid) { \ + ECS_FNTYPE void fname##_run(ecsname *_ecs, ecs_eid eid) { \ ecs_cid _ecsflags = type1##_fl | type2##_fl | type3##_fl; \ if (ecsname##_match(_ecs, eid, _ecsflags)) { \ fname(_ecs->c_##type1 + eid, _ecs->c_##type2 + eid, \ @@ -211,7 +230,7 @@ typedef int ecs_eid; // pre-pulled items from the entity component arrays. The new function is // named the same as the old one, just with _run appeneded #define ECS_SYSTEM4(ecsname, fname, type1, type2, type3, type4) \ - void fname##_run(ecsname *_ecs, ecs_eid eid) { \ + ECS_FNTYPE void fname##_run(ecsname *_ecs, ecs_eid eid) { \ ecs_cid _ecsflags = type1##_fl | type2##_fl | type3##_fl | type4##_fl; \ if (ecsname##_match(_ecs, eid, _ecsflags)) { \ fname(_ecs->c_##type1 + eid, _ecs->c_##type2 + eid, \ @@ -223,7 +242,7 @@ typedef int ecs_eid; // pre-pulled items from the entity component arrays. The new function is // named the same as the old one, just with _run appeneded #define ECS_SYSTEM5(ecsname, fname, type1, type2, type3, type4, type5) \ - void fname##_run(ecsname *_ecs, ecs_eid eid) { \ + ECS_FNTYPE void fname##_run(ecsname *_ecs, ecs_eid eid) { \ ecs_cid _ecsflags = \ type1##_fl | type2##_fl | type3##_fl | type4##_fl | type5##_fl; \ if (ecsname##_match(_ecs, eid, _ecsflags)) { \ @@ -237,7 +256,7 @@ typedef int ecs_eid; // pre-pulled items from the entity component arrays. The new function is // named the same as the old one, just with _run appeneded #define ECS_SYSTEM6(ecsname, fname, type1, type2, type3, type4, type5, type6) \ - void fname##_run(ecsname *_ecs, ecs_eid eid) { \ + ECS_FNTYPE void fname##_run(ecsname *_ecs, ecs_eid eid) { \ ecs_cid _ecsflags = type1##_fl | type2##_fl | type3##_fl | type4##_fl | \ type5##_fl | type6##_fl; \ if (ecsname##_match(_ecs, eid, _ecsflags)) { \ diff --git a/haloo3d b/haloo3d index a07b4de..cb50aac 160000 --- a/haloo3d +++ b/haloo3d @@ -1 +1 @@ -Subproject commit a07b4de2e0753440f0fc664f717e9bc5f46193b9 +Subproject commit cb50aaccff0328f21b7c4b9a54ab59ab6b6800ac diff --git a/terrain.c b/terrain.c index 46f1b85..4b25768 100644 --- a/terrain.c +++ b/terrain.c @@ -7,7 +7,6 @@ #include "haloo3d/lib/mathc.h" #include "unigi/main.h" -#include "ecs2.h" // #include "keys.h" #include "terrain_ecstypes.h" @@ -25,8 +24,12 @@ // Lookvec for objects which all face forward along with the player #define DEFAULTLOOK \ { .x = 0, .y = 0, .z = -1 } -#define CHUNKSCALE \ - { .x = 1.0, .y = 1.0, .z = 1.0 } +// { .x = 1.0, .y = 1.0, .z = 1.0 } +#define CHUNKSIZE 8 // How big each chunk is (x and y, square) +#define CHUNKSCALE 1.0 / CHUNKSIZE +#define VIEWDISTANCE 5 +#define PLBOXEDGE VIEWDISTANCE * 2 + 1 +#define MAXCHUNKPERFRAME 10 // These are initial values but there may be ways to change it #define CAM_INITPITCH MPI_2 @@ -47,12 +50,128 @@ int fps = 30; #define PALETTEKEY "palette" +void fastface(haloo3d_facei face, uint16_t tex, uint16_t v0, uint16_t v1, + uint16_t v2) { + face[0].texi = tex; + face[1].texi = tex; + face[2].texi = tex; + face[0].posi = v0; + face[1].posi = v1; + face[2].posi = v2; +} + +void gen_terrain(struct vec2i pos, haloo3d_obj *model) { + eprintf("Generating terrain at %d,%d\n", pos.x, pos.y); + // Don't allow the model to have more than some amount of faces/vertices. + haloo3d_obj_resetfixed(model, CHUNKSIZE * CHUNKSIZE * 4, + CHUNKSIZE * CHUNKSIZE * 4); + // Generate the "ocean" floor + // haloo3d_gen_paletteuv(0xF26F, model->vtexture[model->numvtextures++].v); + struct vec3 seacol = haloo3d_gen_paletteuv(0xF000 | (rand() & 0xFFF)); + int seauv = haloo3d_obj_addvtexture(model, seacol); + eprintf("SEAUV: %f,%f\n", seacol.x, seacol.y); + // 0xF26F)); + // clang-format off + int bl = haloo3d_obj_addvertex(model, (struct vec4){.x = 0, .y = 0, .z = 0, .w = 1}); + int br = haloo3d_obj_addvertex(model, (struct vec4){.x = CHUNKSIZE, .y = 0, .z = 0, .w = 1}); + int tl = haloo3d_obj_addvertex(model, (struct vec4){.x = 0, .y = 0, .z = CHUNKSIZE, .w = 1}); + int tr = haloo3d_obj_addvertex(model, (struct vec4){.x = CHUNKSIZE, .y = 0, .z = CHUNKSIZE, .w = 1}); + // clang-format on + haloo3d_facei face; + fastface(face, seauv, bl, br, tl); + haloo3d_obj_addface(model, face); + fastface(face, seauv, br, tr, tl); + model->numfaces += 2; + haloo3d_obj_shrinktofit(model); +} + +// Generate the entity/components/terrain for the chunk at the given +// coordinates. +// NOTE: chunks are per x/y tile (terrain inside is just scaled +// down to fit inside the 1x1 box) +void gen_chunk(render_context *ctx, tecs *ecs, struct vec2i pos) { + // eprintf("Generating chunk at %d,%d\n", pos.x, pos.y); + ecs_eid id = tecs_newentity(ecs, 0); + ecs_playergarbage garbage; + sprintf(garbage.dynmodel, "e%d", id); + haloo3d_obj *model = + haloo3d_easystore_addobj(&ecs->storage, garbage.dynmodel); + gen_terrain(pos, model); + ECS_SETCOMPONENT(ecs, id, ecs_playergarbage) garbage; + ECS_SETCOMPONENT(ecs, id, ecs_chunk){.pos = pos}; + ECS_SETCOMPONENT(ecs, id, + ecs_placement){.up = DEFAULTUP, + .lookvec = DEFAULTLOOK, + .pos = {.x = pos.x, .y = 0, .z = pos.y}}; + // TODO: an easy place for optimization (if it's even needed) + // eprintf("TEX: %p\n", haloo3d_easystore_gettex(&ecs->storage, PALETTEKEY)); + ECS_SETCOMPONENT(ecs, id, ecs_object){ + .texture = haloo3d_easystore_gettex(&ecs->storage, PALETTEKEY), + .scale = {.x = CHUNKSCALE, .y = CHUNKSCALE, .z = CHUNKSCALE}, + .lighting = NULL, //&ecs->chunklight, + .model = model, + .cullbackface = 0, + .context = {ctx}, + .contextcount = 1}; +} + +void player_chunkload(ecs_placement *ppos, render_context *ctx, tecs *ecs) { + struct vec2i pchunk = {.x = floor(ppos->pos.x), .y = floor(ppos->pos.z)}; + // This is our little array of "filled chunks" around the player. We + // use this to figure out what to generate + ecs_eid surround[PLBOXEDGE][PLBOXEDGE] = {0}; + // Need to pull all existing objects that are killable + ecs_eid matches[ECS_MAXENTITIES]; + // Find + iterate over killable objects placed in world + int mcount = tecs_query( + ecs, ecs_playergarbage_fl | ecs_placement_fl | ecs_object_fl, matches); + eprintf("KILLABLE OBJS: %d PCHUNK: %d,%d\n", mcount, pchunk.x, pchunk.y); + for (int i = 0; i < mcount; i++) { + ecs_placement *placement = + &ECS_GETCOMPONENT(ecs, matches[i], ecs_placement); + ecs_object *obj = &ECS_GETCOMPONENT(ecs, matches[i], ecs_object); + // If this object is outside the view distance, we no longer have access to + // it + if (fabs(floor(placement->pos.x) - pchunk.x) > VIEWDISTANCE || + fabs(floor(placement->pos.y) - pchunk.y) > VIEWDISTANCE) { + // OK, reduce the chunk refcount, since we're no longer close to it + obj->contextcount--; + } + // This is specifically a chunk, it might be within our view radius. + if (ECS_HASCOMPONENT(ecs, matches[i], ecs_chunk)) { + ecs_chunk *chunk = &ECS_GETCOMPONENT(ecs, matches[i], ecs_chunk); + int surx = chunk->pos.x - pchunk.x + VIEWDISTANCE; + int sury = chunk->pos.y - pchunk.y + VIEWDISTANCE; + if (surx >= 0 && sury >= 0 && surx < PLBOXEDGE && sury < PLBOXEDGE) { + // eprintf("SUR: %d %d\n", surx, sury); + surround[sury][surx] = 1; // matches[i]; + } + } + } + int chunkgen = 0; + // We now have our view radius box and which chunks are not loaded. Up to + // some amount, let's load them + for (int y = pchunk.y - VIEWDISTANCE; y <= pchunk.y + VIEWDISTANCE; y++) { + for (int x = pchunk.x - VIEWDISTANCE; x <= pchunk.x + PLBOXEDGE; x++) { + int surx = x - pchunk.x + VIEWDISTANCE; + int sury = y - pchunk.y + VIEWDISTANCE; + if (!surround[sury][surx]) { + gen_chunk(ctx, ecs, (struct vec2i){.x = x, .y = y}); + //.y = pchunk.y - VIEWDISTANCE + y}); + if (++chunkgen >= MAXCHUNKPERFRAME) { + break; + } + } + } + } +} + // --------------------------------------------------- // The terrain ecs systems // --------------------------------------------------- // All initialization for a specific render context -void sys_rendercontext(ecs_rendercontext *erc, ecs_placement *p) { +void sys_rendercontext(ecs_rendercontext *erc, ecs_placement *p, tecs **ecs) { render_context *rc = *erc; struct vec3 lookat; mfloat_t cammatrix[MAT4_SIZE]; @@ -72,6 +191,10 @@ void sys_rendercontext(ecs_rendercontext *erc, ecs_placement *p) { haloo3d_my_lookat(cammatrix, p->pos.v, lookat.v, p->up.v); mat4_inverse(cammatrix, cammatrix); mat4_multiply(rc->precalc_screen, perspective, cammatrix); + + // Well, since we're here... might as well do the player-related stuff. + // After all, we can be sure this is a player... right? + player_chunkload(p, rc, *ecs); } // Apply rotation to lookvec of placement, overrides lookvec @@ -212,33 +335,6 @@ void sys_playergarbage(ecs_playergarbage *pg, ecs_object *ob, tecs **ecs) { } } -void gen_chunk(render_context *ctx, tecs *ecs, struct vec2i pos) { - eprintf("Generating chunk %d,%d\n", pos.x, pos.y); - // TODO: don't make the system generate components? Let someone else set - // ourselves up, then we just generate the model or... whatever... or is - // that bad? Maybe that's bad, since we'll suddenly be renderable without a - // model, ugh. - // haloo3d_gen_paletteuv(haloo3d_fb *fb, uint16_t col, struct vec2 *uvout); - ecs_eid id = tecs_newentity(ecs, 0); - ecs_playergarbage garbage; - sprintf(garbage.dynmodel, "e%d", id); - haloo3d_obj *model = - haloo3d_easystore_addobj(&ecs->storage, garbage.dynmodel); - ECS_SETCOMPONENT(ecs, id, ecs_playergarbage) garbage; - ECS_SETCOMPONENT(ecs, id, - ecs_placement){.up = DEFAULTUP, - .lookvec = DEFAULTLOOK, - .pos = {.x = pos.x, .y = 0, .z = pos.y}}; - // TODO: an easy place for optimization (if it's even needed) - ECS_SETCOMPONENT(ecs, id, ecs_object){ - .texture = haloo3d_easystore_gettex(&ecs->storage, PALETTEKEY), - .scale = CHUNKSCALE, - .lighting = &ecs->chunklight, - .model = model, - .cullbackface = 1, - .context = {ctx}, - .contextcount = 1}; -} // void sys_chunk(ecs_chunk *ch, ecs_object *o, tecs **ecs) { // // Is it really ok to have this check every single time? Seems kinda // wasteful if (ch->generation == 1) { @@ -277,7 +373,7 @@ int main() { // int argc, char **argv) { haloo3d_easytimer_init(&sdltimer, AVGWEIGHT); render_context context; - context.windowclear = 0xF000; + context.windowclear = 0xFF00; context.nearclip = INIT_NEARCLIP; context.farclip = INIT_FARCLIP; context.fov = INIT_FOV; @@ -303,7 +399,7 @@ int main() { // int argc, char **argv) { ecs_eid playerid = tecs_newentity(&ecs, 0); ECS_SETCOMPONENT(&ecs, playerid, ecs_placement){ - .pos = {.x = 0, .y = 10, .z = 0}, .up = DEFAULTUP}; + .pos = {.x = 0, .y = 1, .z = 0}, .up = DEFAULTUP}; ECS_SETCOMPONENT(&ecs, playerid, ecs_movement){.pos = INIT_PLAYERSPEED}; ECS_SETCOMPONENT(&ecs, playerid, ecs_rotation){.yaw = 0, .pitch = CAM_INITPITCH}; @@ -324,7 +420,8 @@ int main() { // int argc, char **argv) { ECS_RUNSYSTEM1(&ecs, sys_playerinput, ecs_input); ECS_RUNSYSTEM2(&ecs, sys_rotation, ecs_placement, ecs_rotation); ECS_RUNSYSTEM3(&ecs, sys_movement, ecs_placement, ecs_movement, tecs); - ECS_RUNSYSTEM2(&ecs, sys_rendercontext, ecs_rendercontext, ecs_placement); + ECS_RUNSYSTEM3(&ecs, sys_rendercontext, ecs_rendercontext, ecs_placement, + tecs); // ECS_RUNSYSTEM3(&ecs, sys_chunk, ecs_chunk, ecs_object, tecs); ECS_RUNSYSTEM3(&ecs, sys_playergarbage, ecs_playergarbage, ecs_object, tecs); diff --git a/terrain_ecstypes.h b/terrain_ecstypes.h index b282430..4fb625b 100644 --- a/terrain_ecstypes.h +++ b/terrain_ecstypes.h @@ -1,6 +1,8 @@ #ifndef __TERRAIN_ECSTYPES #define __TERRAIN_ECSTYPES +#define ECS_FNTYPE static + #include "ecs2.h" #include "haloo3d/haloo3d.h" #include "haloo3d/haloo3dex_easy.h" @@ -114,29 +116,30 @@ struct vec3 globallighting; // the global lighting to apply to terrain object_lighting chunklight; // lighting to assign to a chunk // Everyone needs to be able to get and set obj/texture haloo3d_easystore storage; -ECS_COMPONENT(ecs_rendercontext); -ECS_COMPONENT(ecs_object); -ECS_COMPONENT(ecs_placement); -ECS_COMPONENT(ecs_rotation); -ECS_COMPONENT(ecs_movement); -ECS_COMPONENT(ecs_input); -ECS_COMPONENT(ecs_playergarbage); -ECS_COMPONENT(ecs_chunk); +ECS_COMPONENT(ecs_rendercontext) +ECS_COMPONENT(ecs_object) +ECS_COMPONENT(ecs_placement) +ECS_COMPONENT(ecs_rotation) +ECS_COMPONENT(ecs_movement) +ECS_COMPONENT(ecs_input) +ECS_COMPONENT(ecs_playergarbage) +ECS_COMPONENT(ecs_chunk) ECS_END(tecs) ECS_FN_INIT(tecs) ECS_FN_NEWENTITY(tecs) ECS_FN_DELETEENTITY(tecs) ECS_FN_EID(tecs) +ECS_FN_QUERY(tecs) // And then a copy of the components here... that sucksssss -ECS_CID(ecs_rendercontext, 0); -ECS_CID(ecs_object, 1); -ECS_CID(ecs_placement, 2); -ECS_CID(ecs_rotation, 3); -ECS_CID(ecs_movement, 4); -ECS_CID(ecs_input, 5); -ECS_CID(ecs_playergarbage, 6); -ECS_CID(ecs_chunk, 7); +ECS_CID(ecs_rendercontext, 0) +ECS_CID(ecs_object, 1) +ECS_CID(ecs_placement, 2) +ECS_CID(ecs_rotation, 3) +ECS_CID(ecs_movement, 4) +ECS_CID(ecs_input, 5) +ECS_CID(ecs_playergarbage, 6) +ECS_CID(ecs_chunk, 7) #endif