Render culling on sceneoverworld.h
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "itemgive.h"
|
#include "itemgive.h"
|
||||||
#include "rpg/item/backpack.h"
|
#include "rpg/item/backpack.h"
|
||||||
|
#include "rpg/item/item.h"
|
||||||
#include "ui/rpg/textbox/uitextboxmain.h"
|
#include "ui/rpg/textbox/uitextboxmain.h"
|
||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
#include "error/error.h"
|
#include "error/error.h"
|
||||||
|
|||||||
@@ -31,15 +31,15 @@ errorret_t sceneOverworldInit(scenedata_t *sceneData) {
|
|||||||
|
|
||||||
errorret_t sceneOverworldUpdate(scenedata_t *sceneData) {
|
errorret_t sceneOverworldUpdate(scenedata_t *sceneData) {
|
||||||
assertNotNull(sceneData, "Scene data cannot be null");
|
assertNotNull(sceneData, "Scene data cannot be null");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
errorret_t sceneOverworldRender(scenedata_t *sceneData) {
|
errorret_t sceneOverworldRender(scenedata_t *sceneData) {
|
||||||
assertNotNull(sceneData, "Scene data cannot be null");
|
assertNotNull(sceneData, "Scene data cannot be null");
|
||||||
|
|
||||||
|
sceneoverworld_t *overworld = &sceneData->overworld;
|
||||||
|
sceneOverworldCullUpdate(overworld);
|
||||||
|
|
||||||
errorChain(displaySetState((displaystate_t){
|
errorChain(displaySetState((displaystate_t){
|
||||||
.flags = DISPLAY_STATE_FLAG_DEPTH_TEST | DISPLAY_STATE_FLAG_CULL
|
.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.
|
// Base terrain meshes, drawn with normal depth testing.
|
||||||
errorChain(sceneOverworldDrawChunksBase());
|
errorChain(sceneOverworldDrawChunksBase(overworld));
|
||||||
|
|
||||||
// Entities are drawn with depth testing fully disabled so sloped tiles
|
// Entities are drawn with depth testing fully disabled so sloped tiles
|
||||||
// (ramps) never clip them; entity-vs-entity overlap falls back to
|
// (ramps) never clip them; entity-vs-entity overlap falls back to
|
||||||
@@ -75,34 +75,10 @@ errorret_t sceneOverworldRender(scenedata_t *sceneData) {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// Entities
|
// Entities
|
||||||
{
|
for(uint8_t i = 0; i < ENTITY_COUNT; i++) {
|
||||||
for(uint8_t i = 0; i < ENTITY_COUNT; i++) {
|
entity_t *ent = &ENTITIES[i];
|
||||||
entity_t *ent = &ENTITIES[i];
|
if(ent->type == ENTITY_TYPE_NULL) continue;
|
||||||
if(ent->type == ENTITY_TYPE_NULL) continue;
|
errorChain(sceneOverworldDrawEntity(overworld, ent));
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Other chunk meshes (trees, buildings, etc), drawn last with normal
|
// Other chunk meshes (trees, buildings, etc), drawn last with normal
|
||||||
@@ -111,15 +87,91 @@ errorret_t sceneOverworldRender(scenedata_t *sceneData) {
|
|||||||
errorChain(displaySetState((displaystate_t){
|
errorChain(displaySetState((displaystate_t){
|
||||||
.flags = DISPLAY_STATE_FLAG_DEPTH_TEST | DISPLAY_STATE_FLAG_CULL
|
.flags = DISPLAY_STATE_FLAG_DEPTH_TEST | DISPLAY_STATE_FLAG_CULL
|
||||||
}));
|
}));
|
||||||
errorChain(sceneOverworldDrawChunksProps());
|
errorChain(sceneOverworldDrawChunksProps(overworld));
|
||||||
|
|
||||||
errorOk();
|
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++) {
|
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
|
||||||
chunk_t *chunk = MAP.chunkOrder[i];
|
chunk_t *chunk = MAP.chunkOrder[i];
|
||||||
if(chunk == NULL) continue;
|
if(chunk == NULL) continue;
|
||||||
|
if(!sceneOverworldChunkShouldRender(overworld, chunk)) continue;
|
||||||
if(chunk->meshCount == 0) continue;
|
if(chunk->meshCount == 0) continue;
|
||||||
if(chunk->modelEntries[0] == NULL) continue;
|
if(chunk->modelEntries[0] == NULL) continue;
|
||||||
if(chunk->modelEntries[0]->state != ASSET_ENTRY_STATE_LOADED) continue;
|
if(chunk->modelEntries[0]->state != ASSET_ENTRY_STATE_LOADED) continue;
|
||||||
@@ -152,10 +204,11 @@ errorret_t sceneOverworldDrawChunksBase() {
|
|||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
errorret_t sceneOverworldDrawChunksProps() {
|
errorret_t sceneOverworldDrawChunksProps(const sceneoverworld_t *overworld) {
|
||||||
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
|
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
|
||||||
chunk_t *chunk = MAP.chunkOrder[i];
|
chunk_t *chunk = MAP.chunkOrder[i];
|
||||||
if(chunk == NULL) continue;
|
if(chunk == NULL) continue;
|
||||||
|
if(!sceneOverworldChunkShouldRender(overworld, chunk)) continue;
|
||||||
|
|
||||||
for(uint8_t m = 1; m < chunk->meshCount; m++) {
|
for(uint8_t m = 1; m < chunk->meshCount; m++) {
|
||||||
if(chunk->modelEntries[m] == NULL) continue;
|
if(chunk->modelEntries[m] == NULL) continue;
|
||||||
|
|||||||
@@ -7,11 +7,35 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "scene/scenebase.h"
|
#include "scene/scenebase.h"
|
||||||
|
#include "rpg/overworld/chunk.h"
|
||||||
|
#include "rpg/entity/entity.h"
|
||||||
|
|
||||||
typedef struct {
|
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;
|
} 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.
|
* Initialises the overworld scene.
|
||||||
*
|
*
|
||||||
@@ -28,22 +52,85 @@ errorret_t sceneOverworldInit(scenedata_t *sceneData);
|
|||||||
*/
|
*/
|
||||||
errorret_t sceneOverworldUpdate(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
|
* Draws the base (tile) mesh of every loaded chunk, with normal depth
|
||||||
* testing. Must be called before entities are rendered.
|
* 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.
|
* @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),
|
* Draws every loaded chunk's additional meshes (trees, buildings, etc),
|
||||||
* with normal depth testing so they correctly occlude entities standing
|
* with normal depth testing so they correctly occlude entities standing
|
||||||
* beneath them. Must be called after entities are rendered.
|
* 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.
|
* @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.
|
* Renders the overworld scene.
|
||||||
|
|||||||
Reference in New Issue
Block a user