#include "haloo3d/haloo3d.h" #include "haloo3d/haloo3dex_easy.h" #include "haloo3d/haloo3dex_gen.h" #include "haloo3d/haloo3dex_obj.h" #include "haloo3d/haloo3dex_print.h" #include "haloo3d/lib/mathc.h" #include "unigi/main.h" // #include "keys.h" #include "terrain_ecstypes.h" #include #include #define WIDTH 480 #define HEIGHT 300 #define SCREENSCALE 2 #define SWIDTH (WIDTH * SCREENSCALE) #define SHEIGHT (HEIGHT * SCREENSCALE) #define AVGWEIGHT 0.85 #define DEFAULTUP \ { .x = 0, .y = 1, .z = 0 } // Lookvec for objects which all face forward along with the player #define DEFAULTLOOK \ { .x = 0, .y = 0, .z = -1 } // { .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 10 #define PLBOXEDGE (VIEWDISTANCE * 2 + 1) #define MAXCHUNKPERFRAME 100 // These are initial values but there may be ways to change it #define CAM_INITPITCH MPI_2 #define INIT_NEARCLIP 0.01 #define INIT_FARCLIP 100.0 #define INIT_FOV 90.0 #define INIT_DITHERSTART 10000 #define INIT_DITHEREND 10000 #define INIT_LIGHTPITCH -MPI / 4.0 #define INIT_LIGHTYAW 0 #define INIT_MINLIGHT 0.2 #define INIT_PLAYERSPEED \ { .x = 0, .y = 0, .z = -40.5 } // Some globals you can mess around with potentially int fps = 60; #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 vec3i pos, haloo3d_obj *model) { eprintf("Generating terrain at %d,%d\n", pos.x, pos.z); // 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. // TODO: You need to change this to be a large plane that follows the player // so you can use lighting on the islands but not on the water // 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 // REMEMBER: NEGATIVE Z IS FORWARD int tl = haloo3d_obj_addvertex(model, (struct vec4){.x = 0, .y = 0, .z = 0, .w = 1}); int tr = haloo3d_obj_addvertex(model, (struct vec4){.x = CHUNKSIZE, .y = 0, .z = 0, .w = 1}); int bl = haloo3d_obj_addvertex(model, (struct vec4){.x = 0, .y = 0, .z = CHUNKSIZE, .w = 1}); int br = 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); haloo3d_obj_addface(model, face); 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 vec3i 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 = pos.y, .z = pos.z}}; // 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 = &ecs->chunklight, .model = model, .cullbackface = 1, .context = {ctx}, .contextcount = 1}; } void player_chunkload(ecs_placement *ppos, render_context *ctx, tecs *ecs) { struct vec3i pchunk = {.x = floor(ppos->pos.x), .y = floor(ppos->pos.y), .z = 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.z) - pchunk.z) > 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 surz = chunk->pos.z - pchunk.z + VIEWDISTANCE; if (surx >= 0 && surz >= 0 && surx < PLBOXEDGE && surz < PLBOXEDGE) { // eprintf("SUR: %d %d\n", surx, sury); surround[surz][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 z = pchunk.z - VIEWDISTANCE; z <= pchunk.z + VIEWDISTANCE; z++) { for (int x = pchunk.x - VIEWDISTANCE; x <= pchunk.x + VIEWDISTANCE; x++) { int surx = x - pchunk.x + VIEWDISTANCE; int surz = z - pchunk.z + VIEWDISTANCE; if (!surround[surz][surx]) { gen_chunk(ctx, ecs, (struct vec3i){.x = x, .y = 0, .z = z}); //.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, tecs **ecs) { render_context *rc = *erc; struct vec3 lookat; mfloat_t cammatrix[MAT4_SIZE]; mfloat_t perspective[MAT4_SIZE]; // Some initial clearing haloo3d_fb_cleardepth(&rc->window); if (rc->windowclear & 0xF000) { haloo3d_fb_clear(&rc->window, rc->windowclear); } // Precalc stuff for later object rendering rc->precalc_halfwidth = rc->window.width * H3DVF(0.5); rc->precalc_halfheight = rc->window.height * H3DVF(0.5); haloo3d_perspective(perspective, rc->fov, (mfloat_t)rc->window.width / rc->window.height, rc->nearclip, rc->farclip); vec3_add(lookat.v, p->pos.v, p->lookvec.v); 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 void sys_rotation(ecs_placement *p, ecs_rotation *r) { YAWP2VEC(r->yaw, r->pitch, p->lookvec.v); } void sys_movement(ecs_placement *p, ecs_movement *m, tecs **ecs) { mfloat_t temp[VEC3_SIZE]; vec3_multiply_f(temp, m->lookvec.v, (*ecs)->delta_s); vec3_add(p->lookvec.v, p->lookvec.v, temp); vec3_multiply_f(temp, m->up.v, (*ecs)->delta_s); vec3_add(p->up.v, p->up.v, temp); vec3_multiply_f(temp, m->pos.v, (*ecs)->delta_s); vec3_add(p->pos.v, p->pos.v, temp); } // Perform the entire rendering of an object void sys_renderobject(ecs_placement *p, ecs_object *o, tecs **ecs) { // --**-- First, precalc all the vertices in the object --**-- mfloat_t tmp[VEC4_SIZE]; mfloat_t modelm[MAT4_SIZE]; mfloat_t finalmatrix[MAT4_SIZE]; struct vec4 precalc_verts[H3D_OBJ_MAXVERTICES]; vec3_add(tmp, p->pos.v, p->lookvec.v); haloo3d_my_lookat(modelm, p->pos.v, tmp, p->up.v); // Apply scale such that it looks like it was applied first (this prevents // scaling applying skew to a rotated object) haloo3d_mat4_prescalev(modelm, o->scale.v); // We might be rendering into multiple contexts in a multiplayer game (or // just different angles or something) for (int ctx = 0; ctx < o->contextcount; ctx++) { mat4_multiply(finalmatrix, o->context[ctx]->precalc_screen, modelm); haloo3d_precalc_verts(o->model, finalmatrix, precalc_verts); // --**-- Next, setup some rendering invariants --**-- struct vec3 lighting; // The compiler is complaining about lighting being used unitialized, even // though it's not. Just shut it up vec3(lighting.v, 0, 0, 0); o->context[ctx]->rendersettings.texture = o->texture; if (o->lighting) { if (o->lighting->autolightfix) { // Lighting doesn't rotate with the model unless you do it yourself. // In the easy system, you can request the renderer to do it for you struct vec4 ltmp, lout; // Lighting is centered at 0 vec4(ltmp.v, 0, 0, 0, 1); // Calc the same lookat just without translation. THis should be the // same rotation matrix used on the model haloo3d_my_lookat(modelm, ltmp.v, p->lookvec.v, p->up.v); // We actually want the inverse. Apparently to speed things up, the // transpose works for rotation matrices(?) but I don't trust that this // lookat does that // mat4_inverse(modelm, modelm); mat4_transpose(modelm, modelm); // We HAVE to have a vec4 (oof) vec4(ltmp.v, o->lighting->dir.x, o->lighting->dir.y, o->lighting->dir.z, 1); haloo3d_vec4_multmat_into(<mp, modelm, &lout); // No need to fix W, should all be good (no perspective divide). But we // DO need to pull out that result vec3(lighting.v, lout.x, lout.y, lout.z); vec3_normalize(lighting.v, lighting.v); } else { vec3_assign(lighting.v, o->lighting->dir.v); } } // --**-- Finally, actually render faces --**-- haloo3d_facef face, baseface; haloo3d_facef outfaces[H3D_FACEF_MAXCLIP]; for (int facei = 0; facei < o->model->numfaces; facei++) { // Copy face values out of precalc array and clip them haloo3d_make_facef(o->model->faces[facei], precalc_verts, o->model->vtexture, face); int tris = haloo3d_facef_clip(face, outfaces); if (tris > 0) { uint8_t oflags = o->context[ctx]->rendersettings.flags; if (o->lighting) { haloo3d_obj_facef(o->model, o->model->faces[facei], baseface); o->context[ctx]->rendersettings.intensity = haloo3d_calc_light(lighting.v, o->lighting->minlight, baseface); } else { o->context[ctx]->rendersettings.intensity = H3DVF(1.0); } // if ((r->_objstate[object - r->objects] & H3D_EASYOBJSTATE_NOTRANS)) { // r->rendersettings.flags &= ~H3DR_TRANSPARENCY; // } for (int ti = 0; ti < tris; ti++) { int backface = !haloo3d_facef_finalize(outfaces[ti]); if (o->cullbackface && backface) { continue; } (*ecs)->totaldrawn++; // We still have to convert the points into the view haloo3d_facef_viewport_into_fast(outfaces[ti], o->context[ctx]->precalc_halfwidth, o->context[ctx]->precalc_halfheight); haloo3d_triangle(&o->context[ctx]->window, &o->context[ctx]->rendersettings, outfaces[ti]); } o->context[ctx]->rendersettings.flags = oflags; } } } } void sys_playerinput(ecs_input *in) { in->numevents = 0; unigi_type_event event; 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; } in->numevents++; } while (event.type != unigi_enum_event_none); } void sys_playergarbage(ecs_playergarbage *pg, ecs_object *ob, tecs **ecs) { ecs_eid id = tecs_eid(ecs); if (ob->contextcount <= 0) { eprintf("Deleting object %d\n", id); if (pg->dynmodel[0]) { haloo3d_easystore_deleteobj(&(*ecs)->storage, pg->dynmodel, haloo3d_obj_free); } tecs_deleteentity(*ecs, id); } } // 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) { // } // } // --------------------------------------------------- // MAIN FUNCTION // --------------------------------------------------- int main() { // int argc, char **argv) { srand(clock()); // Init unigi system. Can use anything here that can render to screen unigi_type_resolution res; res.width = SWIDTH; res.height = SHEIGHT; res.depth = 0; unigi_graphics_init(); unigi_window_create(res, "terrain.exe"); // render.printbuf); eprintf("Initialized unigi system\n"); haloo3d_fb screen; haloo3d_fb_init(&screen, SWIDTH, SHEIGHT); haloo3d_print_tracker pt; char printbuf[8192]; haloo3d_print_initdefault(&pt, printbuf, sizeof(printbuf)); pt.fb = &screen; pt.scale = 1; haloo3d_easytimer frametimer, drawtimer, sdltimer; haloo3d_easytimer_init(&frametimer, AVGWEIGHT); haloo3d_easytimer_init(&drawtimer, AVGWEIGHT); haloo3d_easytimer_init(&sdltimer, AVGWEIGHT); render_context context; context.windowclear = 0xF333; context.nearclip = INIT_NEARCLIP; context.farclip = INIT_FARCLIP; context.fov = INIT_FOV; haloo3d_fb_init(&context.window, WIDTH, HEIGHT); eprintf("Initialized screen buffers, context, and timers\n"); tecs ecs; ecs.delta_s = 0; tecs_init(&ecs); int tecs_size = sizeof(tecs); YAWP2VEC(INIT_LIGHTYAW, INIT_LIGHTPITCH, ecs.globallighting.v); // An issue? Not a pointer? Eeehhh.... ecs.chunklight.dir = ecs.globallighting; ecs.chunklight.minlight = INIT_MINLIGHT; ecs.chunklight.autolightfix = 0; haloo3d_easystore_init(&ecs.storage); haloo3d_fb *palettetex = haloo3d_easystore_addtex(&ecs.storage, PALETTEKEY); haloo3d_gen_palettetex(palettetex); eprintf("Setup ECS system + obj/tex storage (%d bytes)\n", tecs_size); ecs_eid playerid = tecs_newentity(&ecs, 0); ECS_SETCOMPONENT(&ecs, playerid, ecs_placement){ .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}; ECS_SETCOMPONENT(&ecs, playerid, ecs_rendercontext) & context; ECS_SETCOMPONENT(&ecs, playerid, ecs_input){}; ecs_placement *playerpos = &ECS_GETCOMPONENT(&ecs, playerid, ecs_placement); eprintf("Setup player\n"); // MAIN LOOP while (1) { haloo3d_easytimer_start(&frametimer); haloo3d_print_refresh(&pt); ecs.totaldrawn = 0; // ECS logic (which includes rendering) if (ecs.delta_s) { 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_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); haloo3d_easytimer_start(&drawtimer); ECS_RUNSYSTEM3(&ecs, sys_renderobject, ecs_placement, ecs_object, tecs); haloo3d_easytimer_end(&drawtimer); } // Scale 3D into final buffer haloo3d_fb_fill(&screen, &context.window); // clang-format off haloo3d_print(&pt, "Pframe: %05.2f (%05.2f) DT: %0.3f\n" "PSDLFl: %05.2f (%05.2f)\n" "Render: %05.2f (%05.2f)\n" "PlPos: %05.2f (%05.2f)\n" "Tris: %d\n", frametimer.last * 1000, frametimer.sum * 1000, ecs.delta_s, sdltimer.last * 1000, sdltimer.sum * 1000, drawtimer.last * 1000, drawtimer.sum * 1000, playerpos->pos.x, playerpos->pos.z, ecs.totaldrawn); // clang-format on // Finally, actually put buffer onto screen 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); // Wait for next frame based on fps float waittime = (1.0 / fps) - frametimer.last; if (waittime > 0) { unigi_time_sleep(waittime * unigi_time_clocks_per_s); } ecs.delta_s = frametimer.last + MAX(waittime, 0); } haloo3d_fb_free(&screen); haloo3d_fb_free(&context.window); haloo3d_easystore_deleteallobj(&ecs.storage, haloo3d_obj_free); haloo3d_easystore_deletealltex(&ecs.storage, haloo3d_fb_free); }