Render culling on sceneoverworld.h
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "itemgive.h"
|
||||
#include "rpg/item/backpack.h"
|
||||
#include "rpg/item/item.h"
|
||||
#include "ui/rpg/textbox/uitextboxmain.h"
|
||||
#include "util/string.h"
|
||||
#include "error/error.h"
|
||||
|
||||
@@ -31,15 +31,15 @@ errorret_t sceneOverworldInit(scenedata_t *sceneData) {
|
||||
|
||||
errorret_t sceneOverworldUpdate(scenedata_t *sceneData) {
|
||||
assertNotNull(sceneData, "Scene data cannot be null");
|
||||
|
||||
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t sceneOverworldRender(scenedata_t *sceneData) {
|
||||
assertNotNull(sceneData, "Scene data cannot be null");
|
||||
|
||||
sceneoverworld_t *overworld = &sceneData->overworld;
|
||||
sceneOverworldCullUpdate(overworld);
|
||||
|
||||
errorChain(displaySetState((displaystate_t){
|
||||
.flags = DISPLAY_STATE_FLAG_DEPTH_TEST | DISPLAY_STATE_FLAG_CULL
|
||||
}));
|
||||
@@ -65,7 +65,7 @@ errorret_t sceneOverworldRender(scenedata_t *sceneData) {
|
||||
));
|
||||
|
||||
// Base terrain meshes, drawn with normal depth testing.
|
||||
errorChain(sceneOverworldDrawChunksBase());
|
||||
errorChain(sceneOverworldDrawChunksBase(overworld));
|
||||
|
||||
// Entities are drawn with depth testing fully disabled so sloped tiles
|
||||
// (ramps) never clip them; entity-vs-entity overlap falls back to
|
||||
@@ -75,34 +75,10 @@ errorret_t sceneOverworldRender(scenedata_t *sceneData) {
|
||||
}));
|
||||
|
||||
// Entities
|
||||
{
|
||||
for(uint8_t i = 0; i < ENTITY_COUNT; i++) {
|
||||
entity_t *ent = &ENTITIES[i];
|
||||
if(ent->type == ENTITY_TYPE_NULL) continue;
|
||||
|
||||
spritebatchsprite_t sprite;
|
||||
glm_vec3_copy(ent->renderPosition, sprite.min);
|
||||
glm_vec3_add(sprite.min, (vec3){ 0, -0.05f, 0.05f }, sprite.min);// Stop Fight
|
||||
glm_vec3_copy(sprite.min, sprite.max);
|
||||
glm_vec3_add(sprite.max, (vec3){ 1, 1, 0 }, sprite.max);
|
||||
glm_vec2_copy((vec2){ 0, 0 }, sprite.uvMin);
|
||||
glm_vec2_copy((vec2){ 1, 1 }, sprite.uvMax);
|
||||
|
||||
color_t color;
|
||||
switch(ent->direction) {
|
||||
case ENTITY_DIR_NORTH: color = COLOR_YELLOW; break;
|
||||
case ENTITY_DIR_EAST: color = COLOR_RED; break;
|
||||
case ENTITY_DIR_SOUTH: color = COLOR_GREEN; break;
|
||||
case ENTITY_DIR_WEST: color = COLOR_BLUE; break;
|
||||
default: color = COLOR_CYAN; break;
|
||||
}
|
||||
|
||||
shadermaterial_t material = {
|
||||
.unlit = { .color = color, .texture = NULL }
|
||||
};
|
||||
spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, material);
|
||||
spriteBatchFlush();
|
||||
}
|
||||
for(uint8_t i = 0; i < ENTITY_COUNT; i++) {
|
||||
entity_t *ent = &ENTITIES[i];
|
||||
if(ent->type == ENTITY_TYPE_NULL) continue;
|
||||
errorChain(sceneOverworldDrawEntity(overworld, ent));
|
||||
}
|
||||
|
||||
// Other chunk meshes (trees, buildings, etc), drawn last with normal
|
||||
@@ -111,15 +87,91 @@ errorret_t sceneOverworldRender(scenedata_t *sceneData) {
|
||||
errorChain(displaySetState((displaystate_t){
|
||||
.flags = DISPLAY_STATE_FLAG_DEPTH_TEST | DISPLAY_STATE_FLAG_CULL
|
||||
}));
|
||||
errorChain(sceneOverworldDrawChunksProps());
|
||||
errorChain(sceneOverworldDrawChunksProps(overworld));
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t sceneOverworldDrawChunksBase() {
|
||||
void sceneOverworldCullUpdate(sceneoverworld_t *overworld) {
|
||||
rpgCameraGetPosition(overworld->cullTarget);
|
||||
|
||||
const float_t worldH =
|
||||
(float_t)(SCREEN.height / SCREEN.scale3d) / TILE_SIZE_PIXELS;
|
||||
const float_t worldW = worldH * SCREEN.aspect;
|
||||
|
||||
overworld->cullHalfRangeX =
|
||||
(worldW * 0.5f) + SCENE_OVERWORLD_CHUNK_CULL_SKIN_X;
|
||||
overworld->cullHalfRangeY =
|
||||
(worldH * 0.5f) + SCENE_OVERWORLD_CHUNK_CULL_SKIN_Y;
|
||||
}
|
||||
|
||||
bool_t sceneOverworldChunkShouldRender(
|
||||
const sceneoverworld_t *overworld,
|
||||
const chunk_t *chunk
|
||||
) {
|
||||
worldpos_t worldPos;
|
||||
chunkPosToWorldPos(&chunk->position, &worldPos);
|
||||
|
||||
const float_t chunkCenterX = (float_t)worldPos.x + (CHUNK_WIDTH * 0.5f);
|
||||
const float_t chunkCenterY = (float_t)worldPos.y + (CHUNK_HEIGHT * 0.5f);
|
||||
|
||||
if(fabsf(overworld->cullTarget[0] - chunkCenterX) >
|
||||
overworld->cullHalfRangeX) return false;
|
||||
if(fabsf(overworld->cullTarget[1] - chunkCenterY) >
|
||||
overworld->cullHalfRangeY) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool_t sceneOverworldEntityShouldRender(
|
||||
const sceneoverworld_t *overworld,
|
||||
const entity_t *ent
|
||||
) {
|
||||
if(fabsf(overworld->cullTarget[0] - ent->renderPosition[0]) >
|
||||
overworld->cullHalfRangeX) return false;
|
||||
if(fabsf(overworld->cullTarget[1] - ent->renderPosition[1]) >
|
||||
overworld->cullHalfRangeY) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
errorret_t sceneOverworldDrawEntity(
|
||||
const sceneoverworld_t *overworld,
|
||||
entity_t *ent
|
||||
) {
|
||||
if(!sceneOverworldEntityShouldRender(overworld, ent)) errorOk();
|
||||
|
||||
spritebatchsprite_t sprite;
|
||||
glm_vec3_copy(ent->renderPosition, sprite.min);
|
||||
glm_vec3_add(sprite.min, (vec3){ 0, -0.05f, 0.05f }, sprite.min);// Stop Fight
|
||||
glm_vec3_copy(sprite.min, sprite.max);
|
||||
glm_vec3_add(sprite.max, (vec3){ 1, 1, 0 }, sprite.max);
|
||||
glm_vec2_copy((vec2){ 0, 0 }, sprite.uvMin);
|
||||
glm_vec2_copy((vec2){ 1, 1 }, sprite.uvMax);
|
||||
|
||||
color_t color;
|
||||
switch(ent->direction) {
|
||||
case ENTITY_DIR_NORTH: color = COLOR_YELLOW; break;
|
||||
case ENTITY_DIR_EAST: color = COLOR_RED; break;
|
||||
case ENTITY_DIR_SOUTH: color = COLOR_GREEN; break;
|
||||
case ENTITY_DIR_WEST: color = COLOR_BLUE; break;
|
||||
default: color = COLOR_CYAN; break;
|
||||
}
|
||||
|
||||
shadermaterial_t material = {
|
||||
.unlit = { .color = color, .texture = NULL }
|
||||
};
|
||||
errorChain(spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, material));
|
||||
errorChain(spriteBatchFlush());
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t sceneOverworldDrawChunksBase(const sceneoverworld_t *overworld) {
|
||||
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
|
||||
chunk_t *chunk = MAP.chunkOrder[i];
|
||||
if(chunk == NULL) continue;
|
||||
if(!sceneOverworldChunkShouldRender(overworld, chunk)) continue;
|
||||
if(chunk->meshCount == 0) continue;
|
||||
if(chunk->modelEntries[0] == NULL) continue;
|
||||
if(chunk->modelEntries[0]->state != ASSET_ENTRY_STATE_LOADED) continue;
|
||||
@@ -152,10 +204,11 @@ errorret_t sceneOverworldDrawChunksBase() {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t sceneOverworldDrawChunksProps() {
|
||||
errorret_t sceneOverworldDrawChunksProps(const sceneoverworld_t *overworld) {
|
||||
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
|
||||
chunk_t *chunk = MAP.chunkOrder[i];
|
||||
if(chunk == NULL) continue;
|
||||
if(!sceneOverworldChunkShouldRender(overworld, chunk)) continue;
|
||||
|
||||
for(uint8_t m = 1; m < chunk->meshCount; m++) {
|
||||
if(chunk->modelEntries[m] == NULL) continue;
|
||||
|
||||
@@ -7,11 +7,35 @@
|
||||
|
||||
#pragma once
|
||||
#include "scene/scenebase.h"
|
||||
#include "rpg/overworld/chunk.h"
|
||||
#include "rpg/entity/entity.h"
|
||||
|
||||
typedef struct {
|
||||
|
||||
// Cached per-frame chunk culling bounds, refreshed by
|
||||
// sceneOverworldCullUpdate() so sceneOverworldChunkShouldRender()
|
||||
// does not need to recompute them for every chunk.
|
||||
vec3 cullTarget;
|
||||
float_t cullHalfRangeX;
|
||||
float_t cullHalfRangeY;
|
||||
} sceneoverworld_t;
|
||||
|
||||
// Extra world-space margin, in tiles, added on top of the rough
|
||||
// screen-size skin below - cheap insurance against the approximation
|
||||
// culling a chunk that is actually still just in view. Y gets its own
|
||||
// value since the camera's tilt makes the vertical check less exact.
|
||||
#define SCENE_OVERWORLD_CHUNK_CULL_PADDING_X 8.0f
|
||||
#define SCENE_OVERWORLD_CHUNK_CULL_PADDING_Y 4.0f
|
||||
|
||||
// The camera is tilted between the ground-depth (Y) and height (Z)
|
||||
// axes, so a chunk's on-screen vertical extent also depends on its Z
|
||||
// layers. Rather than projecting every corner, fold that into a
|
||||
// generous skin margin on the depth check.
|
||||
#define SCENE_OVERWORLD_CHUNK_CULL_SKIN_X \
|
||||
((CHUNK_WIDTH * 0.5f) + SCENE_OVERWORLD_CHUNK_CULL_PADDING_X)
|
||||
#define SCENE_OVERWORLD_CHUNK_CULL_SKIN_Y \
|
||||
((CHUNK_HEIGHT * 0.5f) + (CHUNK_DEPTH * WORLD_LAYER_HEIGHT) + \
|
||||
SCENE_OVERWORLD_CHUNK_CULL_PADDING_Y)
|
||||
|
||||
/**
|
||||
* Initialises the overworld scene.
|
||||
*
|
||||
@@ -28,22 +52,85 @@ errorret_t sceneOverworldInit(scenedata_t *sceneData);
|
||||
*/
|
||||
errorret_t sceneOverworldUpdate(scenedata_t *sceneData);
|
||||
|
||||
/**
|
||||
* Refreshes the chunk culling bounds cached on the overworld scene data,
|
||||
* from the current camera position and screen dimensions. Must be
|
||||
* called once per frame before sceneOverworldChunkShouldRender().
|
||||
*
|
||||
* @param overworld The overworld scene data to update.
|
||||
*/
|
||||
void sceneOverworldCullUpdate(sceneoverworld_t *overworld);
|
||||
|
||||
/**
|
||||
* Roughly checks whether a chunk falls within the visible screen area,
|
||||
* based on chunk size and screen dimensions. Cheap approximation, not
|
||||
* an exact frustum check - includes a generous skin margin to account
|
||||
* for the camera's angle tilting height/depth onto the screen's
|
||||
* vertical axis, so it may pass some chunks that are actually just out
|
||||
* of view but will never wrongly cull one that is visible.
|
||||
*
|
||||
* @param overworld The overworld scene data holding the cached culling
|
||||
* bounds, as refreshed by sceneOverworldCullUpdate().
|
||||
* @param chunk The chunk to check.
|
||||
* @return true if the chunk should be rendered, false otherwise.
|
||||
*/
|
||||
bool_t sceneOverworldChunkShouldRender(
|
||||
const sceneoverworld_t *overworld,
|
||||
const chunk_t *chunk
|
||||
);
|
||||
|
||||
/**
|
||||
* Draws the base (tile) mesh of every loaded chunk, with normal depth
|
||||
* testing. Must be called before entities are rendered.
|
||||
*
|
||||
* @param overworld The overworld scene data holding the cached culling
|
||||
* bounds, as refreshed by sceneOverworldCullUpdate().
|
||||
* @return An error if drawing failed, or errorOk() on success.
|
||||
*/
|
||||
errorret_t sceneOverworldDrawChunksBase();
|
||||
errorret_t sceneOverworldDrawChunksBase(const sceneoverworld_t *overworld);
|
||||
|
||||
/**
|
||||
* Draws every loaded chunk's additional meshes (trees, buildings, etc),
|
||||
* with normal depth testing so they correctly occlude entities standing
|
||||
* beneath them. Must be called after entities are rendered.
|
||||
*
|
||||
* @param overworld The overworld scene data holding the cached culling
|
||||
* bounds, as refreshed by sceneOverworldCullUpdate().
|
||||
* @return An error if drawing failed, or errorOk() on success.
|
||||
*/
|
||||
errorret_t sceneOverworldDrawChunksProps();
|
||||
errorret_t sceneOverworldDrawChunksProps(const sceneoverworld_t *overworld);
|
||||
|
||||
/**
|
||||
* Roughly checks whether an entity falls within the visible screen
|
||||
* area. Reuses the same cached chunk culling bounds, since an entity
|
||||
* is just a point within that same space - deliberately cheap, a
|
||||
* couple of subtractions and comparisons, since it runs per entity
|
||||
* every frame and it is not worth spending more math than that to
|
||||
* avoid drawing one that is slightly off-screen.
|
||||
*
|
||||
* @param overworld The overworld scene data holding the cached culling
|
||||
* bounds, as refreshed by sceneOverworldCullUpdate().
|
||||
* @param ent The entity to check.
|
||||
* @return true if the entity should be rendered, false otherwise.
|
||||
*/
|
||||
bool_t sceneOverworldEntityShouldRender(
|
||||
const sceneoverworld_t *overworld,
|
||||
const entity_t *ent
|
||||
);
|
||||
|
||||
/**
|
||||
* Draws a single entity as a sprite, colored by its facing direction.
|
||||
* Skips drawing (without error) if the entity should not render.
|
||||
*
|
||||
* @param overworld The overworld scene data holding the cached culling
|
||||
* bounds, as refreshed by sceneOverworldCullUpdate().
|
||||
* @param ent The entity to draw.
|
||||
* @return An error if drawing failed, or errorOk() on success.
|
||||
*/
|
||||
errorret_t sceneOverworldDrawEntity(
|
||||
const sceneoverworld_t *overworld,
|
||||
entity_t *ent
|
||||
);
|
||||
|
||||
/**
|
||||
* Renders the overworld scene.
|
||||
|
||||
Reference in New Issue
Block a user