/** * Copyright (c) 2025 Dominic Masters * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ #include "scenemap.h" #include "scene/scenedata.h" #include "display/spritebatch.h" #include "assert/assert.h" #include "asset/asset.h" #include "rpg/entity/entity.h" #include "rpg/overworld/map.h" #include "display/screen.h" #include "rpg/rpgcamera.h" #include "util/memory.h" #include "duskdefs.h" errorret_t sceneMapInit(scenedata_t *data) { // Init the camera. cameraInitPerspective(&data->sceneMap.camera); data->sceneMap.camera.projType = CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED; data->sceneMap.camera.viewType = CAMERA_VIEW_TYPE_LOOKAT_PIXEL_PERFECT; glm_vec3_zero(data->sceneMap.camera.lookatPixelPerfect.offset); data->sceneMap.camera.lookatPixelPerfect.offset[1] = RPG_CAMERA_Z_OFFSET; glm_vec3_copy( (vec3){ 0.0f, 0.0f, 0.0f }, data->sceneMap.camera.lookatPixelPerfect.target ); glm_vec3_copy( (vec3){ 0.0f, 1.0f, 0.0f }, data->sceneMap.camera.lookatPixelPerfect.up ); data->sceneMap.camera.lookatPixelPerfect.pixelsPerUnit = ( RPG_CAMERA_PIXELS_PER_UNIT ); data->sceneMap.camera.perspective.fov = glm_rad(RPG_CAMERA_FOV); errorOk(); } void sceneMapUpdate(scenedata_t *data) { } void sceneMapGetWorldPosition(const worldpos_t pos, vec3 outPosition) { assertNotNull(outPosition, "Output position cannot be NULL"); outPosition[0] = pos.x * TILE_WIDTH; outPosition[1] = pos.y * TILE_HEIGHT; outPosition[2] = pos.z * TILE_DEPTH; // Handle stair tiles. tile_t tile = mapGetTile(pos); if(tileIsRamp(tile)) { outPosition[2] += TILE_DEPTH / 2.0f; } } void sceneMapEntityGetPosition(const entity_t *entity, vec3 outPosition) { assertNotNull(entity, "Entity cannot be NULL"); assertNotNull(outPosition, "Output position cannot be NULL"); // Get position sceneMapGetWorldPosition(entity->position, outPosition); // Add a small offset so we render above the tile outPosition[2] += 0.1f; // Add animation offset(s) switch(entity->animation) { case ENTITY_ANIM_WALK: { float_t animPercentage = entity->animTime / ENTITY_ANIM_WALK_DURATION; vec3 lastPosition; sceneMapGetWorldPosition(entity->lastPosition, lastPosition); vec3 offset; glm_vec3_sub(outPosition, lastPosition, offset); glm_vec3_scale(offset, -animPercentage, offset); glm_vec3_add(outPosition, offset, outPosition); break; } default: break; } } void sceneMapRender(scenedata_t *data) { if(!mapIsLoaded()) return; // Look at target. vec3 cameraTarget; switch(RPG_CAMERA.mode) { case RPG_CAMERA_MODE_FREE: sceneMapGetWorldPosition(RPG_CAMERA.free, cameraTarget); break; case RPG_CAMERA_MODE_FOLLOW_ENTITY: { const entity_t *ent = &ENTITIES[RPG_CAMERA.followEntity.followEntityId]; sceneMapEntityGetPosition(ent, cameraTarget); break; } default: glm_vec3_zero(cameraTarget); break; } glm_vec3_copy( cameraTarget, data->sceneMap.camera.lookatPixelPerfect.target ); // Push camera cameraPushMatrix(&data->sceneMap.camera); // Render map probably. sceneMapRenderMap(); // Render ents entity_t *ent = ENTITIES; do { sceneMapRenderEntity(ent); } while(++ent, ent < &ENTITIES[ENTITY_COUNT]); spriteBatchFlush(); // Finished, pop back camera. cameraPopMatrix(); } void sceneMapRenderEntity(entity_t *entity) { assertNotNull(entity, "Entity cannot be NULL"); if(entity->type == ENTITY_TYPE_NULL) return; vec3 posMin, posMax; vec3 size = { TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH }; sceneMapEntityGetPosition(entity, posMin); glm_vec3_add(posMin, size, posMax); // TEST: Change color depending on dir. color_t testColor; switch(entity->direction) { case ENTITY_DIR_NORTH: testColor = COLOR_BLUE; break; case ENTITY_DIR_EAST: testColor = COLOR_GREEN; break; case ENTITY_DIR_SOUTH: testColor = COLOR_CYAN; break; case ENTITY_DIR_WEST: testColor = COLOR_YELLOW; break; default: testColor = COLOR_WHITE; break; } vec2 uv0 = { 0.0f, 0.0f }; vec2 uv1 = { 1.0f, 1.0f }; spriteBatchPush3D(NULL, posMin, posMax, testColor, uv0, uv1); } void sceneMapRenderMap() { assertTrue(mapIsLoaded(), "No map loaded to render"); // For each chunk. for(uint32_t i = 0; i < MAP_CHUNK_COUNT; i++) { chunk_t *chunk = MAP.chunkOrder[i]; for(uint8_t j = 0; j < chunk->meshCount; j++) { mesh_t *mesh = &chunk->meshes[j]; if(mesh->vertexCount == 0) continue; textureBind(NULL); meshDraw(mesh, -1, -1); } // vec3 min, max; // min[0] = chunk->position.x * CHUNK_WIDTH * TILE_WIDTH; // min[1] = chunk->position.y * CHUNK_HEIGHT * TILE_HEIGHT; // min[2] = chunk->position.z * CHUNK_DEPTH * TILE_DEPTH; // max[0] = min[0] + (CHUNK_WIDTH * TILE_WIDTH); // max[1] = min[1] + (CHUNK_HEIGHT * TILE_HEIGHT); // max[2] = min[2]; // color_t color = COLOR_WHITE; // if(chunk->position.x % 2 == 0) { // color = (chunk->position.y % 2 == 0) ? COLOR_BLACK : COLOR_WHITE; // } else { // color = (chunk->position.y % 2 == 0) ? COLOR_WHITE : COLOR_BLACK; // } // spriteBatchPush3D( // NULL, // min, // max, // color, // (vec2){ 0.0f, 0.0f }, // (vec2){ 1.0f, 1.0f } // ); } } void sceneMapDispose(scenedata_t *data) { }