feat: Implement chunk-based Grid rendering for large grids (closes #123)
Adds a sub-grid system where grids larger than 64x64 cells are automatically divided into 64x64 chunks, each with its own RenderTexture for incremental rendering. This significantly improves performance for large grids by: - Only re-rendering dirty chunks when cells are modified - Caching rendered chunk textures between frames - Viewport culling at the chunk level (skip invisible chunks entirely) Implementation details: - GridChunk class manages individual 64x64 cell regions with dirty tracking - ChunkManager organizes chunks and routes cell access appropriately - UIGrid::at() method transparently routes through chunks for large grids - UIGrid::render() uses chunk-based blitting for large grids - Compile-time CHUNK_SIZE (64) and CHUNK_THRESHOLD (64) constants - Small grids (<= 64x64) continue to use flat storage (no regression) Benchmark results show ~2x improvement in base layer render time for 100x100 grids (0.45ms -> 0.22ms) due to chunk caching. Note: Dynamic layers (#147) still use full-grid textures; extending chunk system to layers is tracked separately as #150. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
abb3316ac1
commit
9469c04b01
6 changed files with 1059 additions and 49 deletions
253
src/GridChunk.cpp
Normal file
253
src/GridChunk.cpp
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
#include "GridChunk.h"
|
||||
#include "UIGrid.h"
|
||||
#include "PyTexture.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
// =============================================================================
|
||||
// GridChunk implementation
|
||||
// =============================================================================
|
||||
|
||||
GridChunk::GridChunk(int chunk_x, int chunk_y, int width, int height,
|
||||
int world_x, int world_y, UIGrid* parent)
|
||||
: chunk_x(chunk_x), chunk_y(chunk_y),
|
||||
width(width), height(height),
|
||||
world_x(world_x), world_y(world_y),
|
||||
cells(width * height),
|
||||
dirty(true), texture_initialized(false),
|
||||
parent_grid(parent)
|
||||
{}
|
||||
|
||||
UIGridPoint& GridChunk::at(int local_x, int local_y) {
|
||||
return cells[local_y * width + local_x];
|
||||
}
|
||||
|
||||
const UIGridPoint& GridChunk::at(int local_x, int local_y) const {
|
||||
return cells[local_y * width + local_x];
|
||||
}
|
||||
|
||||
void GridChunk::markDirty() {
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
void GridChunk::ensureTexture(int cell_width, int cell_height) {
|
||||
unsigned int required_width = width * cell_width;
|
||||
unsigned int required_height = height * cell_height;
|
||||
|
||||
if (texture_initialized &&
|
||||
cached_texture.getSize().x == required_width &&
|
||||
cached_texture.getSize().y == required_height) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cached_texture.create(required_width, required_height)) {
|
||||
texture_initialized = false;
|
||||
return;
|
||||
}
|
||||
|
||||
texture_initialized = true;
|
||||
dirty = true; // Force re-render after resize
|
||||
cached_sprite.setTexture(cached_texture.getTexture());
|
||||
}
|
||||
|
||||
void GridChunk::renderToTexture(int cell_width, int cell_height,
|
||||
std::shared_ptr<PyTexture> texture) {
|
||||
ensureTexture(cell_width, cell_height);
|
||||
if (!texture_initialized) return;
|
||||
|
||||
cached_texture.clear(sf::Color::Transparent);
|
||||
|
||||
sf::RectangleShape rect;
|
||||
rect.setSize(sf::Vector2f(cell_width, cell_height));
|
||||
rect.setOutlineThickness(0);
|
||||
|
||||
// Render all cells in this chunk
|
||||
for (int y = 0; y < height; ++y) {
|
||||
for (int x = 0; x < width; ++x) {
|
||||
const auto& cell = at(x, y);
|
||||
sf::Vector2f pixel_pos(x * cell_width, y * cell_height);
|
||||
|
||||
// Draw background color
|
||||
rect.setPosition(pixel_pos);
|
||||
rect.setFillColor(cell.color);
|
||||
cached_texture.draw(rect);
|
||||
|
||||
// Draw tile sprite if available
|
||||
if (texture && cell.tilesprite != -1) {
|
||||
sf::Sprite sprite = texture->sprite(cell.tilesprite, pixel_pos,
|
||||
sf::Vector2f(1.0f, 1.0f));
|
||||
cached_texture.draw(sprite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cached_texture.display();
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
sf::FloatRect GridChunk::getWorldBounds(int cell_width, int cell_height) const {
|
||||
return sf::FloatRect(
|
||||
sf::Vector2f(world_x * cell_width, world_y * cell_height),
|
||||
sf::Vector2f(width * cell_width, height * cell_height)
|
||||
);
|
||||
}
|
||||
|
||||
bool GridChunk::isVisible(float left_edge, float top_edge,
|
||||
float right_edge, float bottom_edge) const {
|
||||
// Check if chunk's cell range overlaps with viewport's cell range
|
||||
float chunk_right = world_x + width;
|
||||
float chunk_bottom = world_y + height;
|
||||
|
||||
return !(world_x >= right_edge || chunk_right <= left_edge ||
|
||||
world_y >= bottom_edge || chunk_bottom <= top_edge);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ChunkManager implementation
|
||||
// =============================================================================
|
||||
|
||||
ChunkManager::ChunkManager(int grid_x, int grid_y, UIGrid* parent)
|
||||
: grid_x(grid_x), grid_y(grid_y), parent_grid(parent)
|
||||
{
|
||||
// Calculate number of chunks needed
|
||||
chunks_x = (grid_x + GridChunk::CHUNK_SIZE - 1) / GridChunk::CHUNK_SIZE;
|
||||
chunks_y = (grid_y + GridChunk::CHUNK_SIZE - 1) / GridChunk::CHUNK_SIZE;
|
||||
|
||||
chunks.reserve(chunks_x * chunks_y);
|
||||
|
||||
// Create chunks
|
||||
for (int cy = 0; cy < chunks_y; ++cy) {
|
||||
for (int cx = 0; cx < chunks_x; ++cx) {
|
||||
// Calculate world position
|
||||
int world_x = cx * GridChunk::CHUNK_SIZE;
|
||||
int world_y = cy * GridChunk::CHUNK_SIZE;
|
||||
|
||||
// Calculate actual size (may be smaller at edges)
|
||||
int chunk_width = std::min(GridChunk::CHUNK_SIZE, grid_x - world_x);
|
||||
int chunk_height = std::min(GridChunk::CHUNK_SIZE, grid_y - world_y);
|
||||
|
||||
chunks.push_back(std::make_unique<GridChunk>(
|
||||
cx, cy, chunk_width, chunk_height, world_x, world_y, parent
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GridChunk* ChunkManager::getChunkForCell(int x, int y) {
|
||||
if (x < 0 || x >= grid_x || y < 0 || y >= grid_y) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int chunk_x = x / GridChunk::CHUNK_SIZE;
|
||||
int chunk_y = y / GridChunk::CHUNK_SIZE;
|
||||
return getChunk(chunk_x, chunk_y);
|
||||
}
|
||||
|
||||
const GridChunk* ChunkManager::getChunkForCell(int x, int y) const {
|
||||
if (x < 0 || x >= grid_x || y < 0 || y >= grid_y) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int chunk_x = x / GridChunk::CHUNK_SIZE;
|
||||
int chunk_y = y / GridChunk::CHUNK_SIZE;
|
||||
return getChunk(chunk_x, chunk_y);
|
||||
}
|
||||
|
||||
GridChunk* ChunkManager::getChunk(int chunk_x, int chunk_y) {
|
||||
if (chunk_x < 0 || chunk_x >= chunks_x || chunk_y < 0 || chunk_y >= chunks_y) {
|
||||
return nullptr;
|
||||
}
|
||||
return chunks[chunk_y * chunks_x + chunk_x].get();
|
||||
}
|
||||
|
||||
const GridChunk* ChunkManager::getChunk(int chunk_x, int chunk_y) const {
|
||||
if (chunk_x < 0 || chunk_x >= chunks_x || chunk_y < 0 || chunk_y >= chunks_y) {
|
||||
return nullptr;
|
||||
}
|
||||
return chunks[chunk_y * chunks_x + chunk_x].get();
|
||||
}
|
||||
|
||||
UIGridPoint& ChunkManager::at(int x, int y) {
|
||||
GridChunk* chunk = getChunkForCell(x, y);
|
||||
if (!chunk) {
|
||||
// Return a static dummy point for out-of-bounds access
|
||||
// This matches the original behavior of UIGrid::at()
|
||||
static UIGridPoint dummy;
|
||||
return dummy;
|
||||
}
|
||||
|
||||
// Convert to local coordinates within chunk
|
||||
int local_x = x % GridChunk::CHUNK_SIZE;
|
||||
int local_y = y % GridChunk::CHUNK_SIZE;
|
||||
|
||||
// Mark chunk dirty when accessed for modification
|
||||
chunk->markDirty();
|
||||
|
||||
return chunk->at(local_x, local_y);
|
||||
}
|
||||
|
||||
const UIGridPoint& ChunkManager::at(int x, int y) const {
|
||||
const GridChunk* chunk = getChunkForCell(x, y);
|
||||
if (!chunk) {
|
||||
static UIGridPoint dummy;
|
||||
return dummy;
|
||||
}
|
||||
|
||||
int local_x = x % GridChunk::CHUNK_SIZE;
|
||||
int local_y = y % GridChunk::CHUNK_SIZE;
|
||||
|
||||
return chunk->at(local_x, local_y);
|
||||
}
|
||||
|
||||
void ChunkManager::markAllDirty() {
|
||||
for (auto& chunk : chunks) {
|
||||
chunk->markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<GridChunk*> ChunkManager::getVisibleChunks(float left_edge, float top_edge,
|
||||
float right_edge, float bottom_edge) {
|
||||
std::vector<GridChunk*> visible;
|
||||
visible.reserve(chunks.size()); // Pre-allocate for worst case
|
||||
|
||||
for (auto& chunk : chunks) {
|
||||
if (chunk->isVisible(left_edge, top_edge, right_edge, bottom_edge)) {
|
||||
visible.push_back(chunk.get());
|
||||
}
|
||||
}
|
||||
|
||||
return visible;
|
||||
}
|
||||
|
||||
void ChunkManager::resize(int new_grid_x, int new_grid_y) {
|
||||
// For now, simple rebuild - could be optimized to preserve data
|
||||
grid_x = new_grid_x;
|
||||
grid_y = new_grid_y;
|
||||
|
||||
chunks_x = (grid_x + GridChunk::CHUNK_SIZE - 1) / GridChunk::CHUNK_SIZE;
|
||||
chunks_y = (grid_y + GridChunk::CHUNK_SIZE - 1) / GridChunk::CHUNK_SIZE;
|
||||
|
||||
chunks.clear();
|
||||
chunks.reserve(chunks_x * chunks_y);
|
||||
|
||||
for (int cy = 0; cy < chunks_y; ++cy) {
|
||||
for (int cx = 0; cx < chunks_x; ++cx) {
|
||||
int world_x = cx * GridChunk::CHUNK_SIZE;
|
||||
int world_y = cy * GridChunk::CHUNK_SIZE;
|
||||
int chunk_width = std::min(GridChunk::CHUNK_SIZE, grid_x - world_x);
|
||||
int chunk_height = std::min(GridChunk::CHUNK_SIZE, grid_y - world_y);
|
||||
|
||||
chunks.push_back(std::make_unique<GridChunk>(
|
||||
cx, cy, chunk_width, chunk_height, world_x, world_y, parent_grid
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ChunkManager::dirtyChunks() const {
|
||||
int count = 0;
|
||||
for (const auto& chunk : chunks) {
|
||||
if (chunk->dirty) ++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
118
src/GridChunk.h
Normal file
118
src/GridChunk.h
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
#pragma once
|
||||
#include "Common.h"
|
||||
#include <SFML/Graphics.hpp>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include "UIGridPoint.h"
|
||||
|
||||
// Forward declarations
|
||||
class UIGrid;
|
||||
class PyTexture;
|
||||
|
||||
/**
|
||||
* #123 - Grid chunk for sub-grid rendering system
|
||||
*
|
||||
* Each chunk represents a CHUNK_SIZE x CHUNK_SIZE portion of the grid.
|
||||
* Chunks have their own RenderTexture and dirty flag for efficient
|
||||
* incremental rendering - only dirty chunks are re-rendered.
|
||||
*/
|
||||
class GridChunk {
|
||||
public:
|
||||
// Compile-time configurable chunk size (power of 2 recommended)
|
||||
static constexpr int CHUNK_SIZE = 64;
|
||||
|
||||
// Position of this chunk in chunk coordinates
|
||||
int chunk_x, chunk_y;
|
||||
|
||||
// Actual dimensions (may be less than CHUNK_SIZE at grid edges)
|
||||
int width, height;
|
||||
|
||||
// World position (in cell coordinates)
|
||||
int world_x, world_y;
|
||||
|
||||
// Cell data for this chunk
|
||||
std::vector<UIGridPoint> cells;
|
||||
|
||||
// Cached rendering
|
||||
sf::RenderTexture cached_texture;
|
||||
sf::Sprite cached_sprite;
|
||||
bool dirty;
|
||||
bool texture_initialized;
|
||||
|
||||
// Parent grid reference (for texture access)
|
||||
UIGrid* parent_grid;
|
||||
|
||||
// Constructor
|
||||
GridChunk(int chunk_x, int chunk_y, int width, int height,
|
||||
int world_x, int world_y, UIGrid* parent);
|
||||
|
||||
// Access cell at local chunk coordinates
|
||||
UIGridPoint& at(int local_x, int local_y);
|
||||
const UIGridPoint& at(int local_x, int local_y) const;
|
||||
|
||||
// Mark chunk as needing re-render
|
||||
void markDirty();
|
||||
|
||||
// Ensure texture is properly sized
|
||||
void ensureTexture(int cell_width, int cell_height);
|
||||
|
||||
// Render chunk content to cached texture
|
||||
void renderToTexture(int cell_width, int cell_height,
|
||||
std::shared_ptr<PyTexture> texture);
|
||||
|
||||
// Get pixel bounds of this chunk in world coordinates
|
||||
sf::FloatRect getWorldBounds(int cell_width, int cell_height) const;
|
||||
|
||||
// Check if chunk overlaps with viewport
|
||||
bool isVisible(float left_edge, float top_edge,
|
||||
float right_edge, float bottom_edge) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* Manages a 2D array of chunks for a grid
|
||||
*/
|
||||
class ChunkManager {
|
||||
public:
|
||||
// Dimensions in chunks
|
||||
int chunks_x, chunks_y;
|
||||
|
||||
// Grid dimensions in cells
|
||||
int grid_x, grid_y;
|
||||
|
||||
// All chunks (row-major order)
|
||||
std::vector<std::unique_ptr<GridChunk>> chunks;
|
||||
|
||||
// Parent grid
|
||||
UIGrid* parent_grid;
|
||||
|
||||
// Constructor - creates chunks for given grid dimensions
|
||||
ChunkManager(int grid_x, int grid_y, UIGrid* parent);
|
||||
|
||||
// Get chunk containing cell (x, y)
|
||||
GridChunk* getChunkForCell(int x, int y);
|
||||
const GridChunk* getChunkForCell(int x, int y) const;
|
||||
|
||||
// Get chunk at chunk coordinates
|
||||
GridChunk* getChunk(int chunk_x, int chunk_y);
|
||||
const GridChunk* getChunk(int chunk_x, int chunk_y) const;
|
||||
|
||||
// Access cell at grid coordinates (routes through chunk)
|
||||
UIGridPoint& at(int x, int y);
|
||||
const UIGridPoint& at(int x, int y) const;
|
||||
|
||||
// Mark all chunks dirty (for full rebuild)
|
||||
void markAllDirty();
|
||||
|
||||
// Get chunks that overlap with viewport
|
||||
std::vector<GridChunk*> getVisibleChunks(float left_edge, float top_edge,
|
||||
float right_edge, float bottom_edge);
|
||||
|
||||
// Resize grid (rebuilds chunks)
|
||||
void resize(int new_grid_x, int new_grid_y);
|
||||
|
||||
// Get total number of chunks
|
||||
int totalChunks() const { return chunks_x * chunks_y; }
|
||||
|
||||
// Get number of dirty chunks
|
||||
int dirtyChunks() const;
|
||||
};
|
||||
156
src/UIGrid.cpp
156
src/UIGrid.cpp
|
|
@ -8,10 +8,10 @@
|
|||
#include <cmath> // #142 - for std::floor
|
||||
// UIDrawable methods now in UIBase.h
|
||||
|
||||
UIGrid::UIGrid()
|
||||
UIGrid::UIGrid()
|
||||
: grid_x(0), grid_y(0), zoom(1.0f), center_x(0.0f), center_y(0.0f), ptex(nullptr),
|
||||
fill_color(8, 8, 8, 255), tcod_map(nullptr), tcod_dijkstra(nullptr), tcod_path(nullptr),
|
||||
perspective_enabled(false) // Default to omniscient view
|
||||
perspective_enabled(false), use_chunks(false) // Default to omniscient view
|
||||
{
|
||||
// Initialize entities list
|
||||
entities = std::make_shared<std::list<std::shared_ptr<UIEntity>>>();
|
||||
|
|
@ -24,30 +24,31 @@ UIGrid::UIGrid()
|
|||
position = sf::Vector2f(0, 0); // Set base class position
|
||||
box.setPosition(position); // Sync box position
|
||||
box.setFillColor(sf::Color(0, 0, 0, 0));
|
||||
|
||||
|
||||
// Initialize render texture (small default size)
|
||||
renderTexture.create(1, 1);
|
||||
|
||||
|
||||
// Initialize output sprite
|
||||
output.setTextureRect(sf::IntRect(0, 0, 0, 0));
|
||||
output.setPosition(0, 0);
|
||||
output.setTexture(renderTexture.getTexture());
|
||||
|
||||
|
||||
// Points vector starts empty (grid_x * grid_y = 0)
|
||||
// TCOD map will be created when grid is resized
|
||||
}
|
||||
|
||||
UIGrid::UIGrid(int gx, int gy, std::shared_ptr<PyTexture> _ptex, sf::Vector2f _xy, sf::Vector2f _wh)
|
||||
: grid_x(gx), grid_y(gy),
|
||||
zoom(1.0f),
|
||||
ptex(_ptex), points(gx * gy),
|
||||
zoom(1.0f),
|
||||
ptex(_ptex),
|
||||
fill_color(8, 8, 8, 255), tcod_map(nullptr), tcod_dijkstra(nullptr), tcod_path(nullptr),
|
||||
perspective_enabled(false) // Default to omniscient view
|
||||
perspective_enabled(false),
|
||||
use_chunks(gx > CHUNK_THRESHOLD || gy > CHUNK_THRESHOLD) // #123 - Use chunks for large grids
|
||||
{
|
||||
// Use texture dimensions if available, otherwise use defaults
|
||||
int cell_width = _ptex ? _ptex->sprite_width : DEFAULT_CELL_WIDTH;
|
||||
int cell_height = _ptex ? _ptex->sprite_height : DEFAULT_CELL_HEIGHT;
|
||||
|
||||
|
||||
center_x = (gx/2) * cell_width;
|
||||
center_y = (gy/2) * cell_height;
|
||||
entities = std::make_shared<std::list<std::shared_ptr<UIEntity>>>();
|
||||
|
|
@ -57,12 +58,12 @@ UIGrid::UIGrid(int gx, int gy, std::shared_ptr<PyTexture> _ptex, sf::Vector2f _x
|
|||
|
||||
box.setSize(_wh);
|
||||
position = _xy; // Set base class position
|
||||
box.setPosition(position); // Sync box position
|
||||
box.setPosition(position); // Sync box position
|
||||
|
||||
box.setFillColor(sf::Color(0,0,0,0));
|
||||
// create renderTexture with maximum theoretical size; sprite can resize to show whatever amount needs to be rendered
|
||||
renderTexture.create(1920, 1080); // TODO - renderTexture should be window size; above 1080p this will cause rendering errors
|
||||
|
||||
|
||||
// Only initialize sprite if texture is available
|
||||
if (ptex) {
|
||||
sprite = ptex->sprite(0);
|
||||
|
|
@ -77,23 +78,47 @@ UIGrid::UIGrid(int gx, int gy, std::shared_ptr<PyTexture> _ptex, sf::Vector2f _x
|
|||
|
||||
// Create TCOD map
|
||||
tcod_map = new TCODMap(gx, gy);
|
||||
|
||||
|
||||
// Create TCOD dijkstra pathfinder
|
||||
tcod_dijkstra = new TCODDijkstra(tcod_map);
|
||||
|
||||
|
||||
// Create TCOD A* pathfinder
|
||||
tcod_path = new TCODPath(tcod_map);
|
||||
|
||||
// Initialize grid points with parent reference
|
||||
for (int y = 0; y < gy; y++) {
|
||||
for (int x = 0; x < gx; x++) {
|
||||
int idx = y * gx + x;
|
||||
points[idx].grid_x = x;
|
||||
points[idx].grid_y = y;
|
||||
points[idx].parent_grid = this;
|
||||
|
||||
// #123 - Initialize storage based on grid size
|
||||
if (use_chunks) {
|
||||
// Large grid: use chunk-based storage
|
||||
chunk_manager = std::make_unique<ChunkManager>(gx, gy, this);
|
||||
|
||||
// Initialize all cells with parent reference
|
||||
for (int cy = 0; cy < chunk_manager->chunks_y; ++cy) {
|
||||
for (int cx = 0; cx < chunk_manager->chunks_x; ++cx) {
|
||||
GridChunk* chunk = chunk_manager->getChunk(cx, cy);
|
||||
if (!chunk) continue;
|
||||
|
||||
for (int ly = 0; ly < chunk->height; ++ly) {
|
||||
for (int lx = 0; lx < chunk->width; ++lx) {
|
||||
auto& cell = chunk->at(lx, ly);
|
||||
cell.grid_x = chunk->world_x + lx;
|
||||
cell.grid_y = chunk->world_y + ly;
|
||||
cell.parent_grid = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Small grid: use flat storage (original behavior)
|
||||
points.resize(gx * gy);
|
||||
for (int y = 0; y < gy; y++) {
|
||||
for (int x = 0; x < gx; x++) {
|
||||
int idx = y * gx + x;
|
||||
points[idx].grid_x = x;
|
||||
points[idx].grid_y = y;
|
||||
points[idx].parent_grid = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Initial sync of TCOD map
|
||||
syncTCODMap();
|
||||
}
|
||||
|
|
@ -147,36 +172,64 @@ void UIGrid::render(sf::Vector2f offset, sf::RenderTarget& target)
|
|||
|
||||
// base layer - bottom color, tile sprite ("ground")
|
||||
int cellsRendered = 0;
|
||||
for (int x = (left_edge - 1 >= 0 ? left_edge - 1 : 0);
|
||||
x < x_limit; //x < view_width;
|
||||
x+=1)
|
||||
{
|
||||
//for (float y = (top_edge >= 0 ? top_edge : 0);
|
||||
for (int y = (top_edge - 1 >= 0 ? top_edge - 1 : 0);
|
||||
y < y_limit; //y < view_height;
|
||||
y+=1)
|
||||
{
|
||||
auto pixel_pos = sf::Vector2f(
|
||||
(x*cell_width - left_spritepixels) * zoom,
|
||||
(y*cell_height - top_spritepixels) * zoom );
|
||||
|
||||
auto gridpoint = at(std::floor(x), std::floor(y));
|
||||
// #123 - Use chunk-based rendering for large grids
|
||||
if (use_chunks && chunk_manager) {
|
||||
// Get visible chunks based on cell coordinate bounds
|
||||
float right_edge = left_edge + width_sq + 2;
|
||||
float bottom_edge = top_edge + height_sq + 2;
|
||||
auto visible_chunks = chunk_manager->getVisibleChunks(left_edge, top_edge, right_edge, bottom_edge);
|
||||
|
||||
//sprite.setPosition(pixel_pos);
|
||||
|
||||
r.setPosition(pixel_pos);
|
||||
r.setFillColor(gridpoint.color);
|
||||
renderTexture.draw(r);
|
||||
|
||||
// tilesprite - only draw if texture is available
|
||||
// if discovered but not visible, set opacity to 90%
|
||||
// if not discovered... just don't draw it?
|
||||
if (ptex && gridpoint.tilesprite != -1) {
|
||||
sprite = ptex->sprite(gridpoint.tilesprite, pixel_pos, sf::Vector2f(zoom, zoom)); //setSprite(gridpoint.tilesprite);;
|
||||
renderTexture.draw(sprite);
|
||||
for (auto* chunk : visible_chunks) {
|
||||
// Re-render dirty chunks to their cached textures
|
||||
if (chunk->dirty) {
|
||||
chunk->renderToTexture(cell_width, cell_height, ptex);
|
||||
}
|
||||
|
||||
cellsRendered++;
|
||||
// Calculate pixel position for this chunk's sprite
|
||||
float chunk_pixel_x = (chunk->world_x * cell_width - left_spritepixels) * zoom;
|
||||
float chunk_pixel_y = (chunk->world_y * cell_height - top_spritepixels) * zoom;
|
||||
|
||||
// Set up and draw the chunk sprite
|
||||
chunk->cached_sprite.setPosition(chunk_pixel_x, chunk_pixel_y);
|
||||
chunk->cached_sprite.setScale(zoom, zoom);
|
||||
renderTexture.draw(chunk->cached_sprite);
|
||||
|
||||
cellsRendered += chunk->width * chunk->height;
|
||||
}
|
||||
} else {
|
||||
// Original cell-by-cell rendering for small grids
|
||||
for (int x = (left_edge - 1 >= 0 ? left_edge - 1 : 0);
|
||||
x < x_limit; //x < view_width;
|
||||
x+=1)
|
||||
{
|
||||
//for (float y = (top_edge >= 0 ? top_edge : 0);
|
||||
for (int y = (top_edge - 1 >= 0 ? top_edge - 1 : 0);
|
||||
y < y_limit; //y < view_height;
|
||||
y+=1)
|
||||
{
|
||||
auto pixel_pos = sf::Vector2f(
|
||||
(x*cell_width - left_spritepixels) * zoom,
|
||||
(y*cell_height - top_spritepixels) * zoom );
|
||||
|
||||
auto gridpoint = at(std::floor(x), std::floor(y));
|
||||
|
||||
//sprite.setPosition(pixel_pos);
|
||||
|
||||
r.setPosition(pixel_pos);
|
||||
r.setFillColor(gridpoint.color);
|
||||
renderTexture.draw(r);
|
||||
|
||||
// tilesprite - only draw if texture is available
|
||||
// if discovered but not visible, set opacity to 90%
|
||||
// if not discovered... just don't draw it?
|
||||
if (ptex && gridpoint.tilesprite != -1) {
|
||||
sprite = ptex->sprite(gridpoint.tilesprite, pixel_pos, sf::Vector2f(zoom, zoom)); //setSprite(gridpoint.tilesprite);;
|
||||
renderTexture.draw(sprite);
|
||||
}
|
||||
|
||||
cellsRendered++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -368,6 +421,10 @@ void UIGrid::render(sf::Vector2f offset, sf::RenderTarget& target)
|
|||
|
||||
UIGridPoint& UIGrid::at(int x, int y)
|
||||
{
|
||||
// #123 - Route through chunk manager for large grids
|
||||
if (use_chunks && chunk_manager) {
|
||||
return chunk_manager->at(x, y);
|
||||
}
|
||||
return points[y * grid_x + x];
|
||||
}
|
||||
|
||||
|
|
@ -1109,7 +1166,8 @@ PyObject* UIGrid::py_at(PyUIGridObject* self, PyObject* args, PyObject* kwds)
|
|||
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "GridPoint");
|
||||
auto obj = (PyUIGridPointObject*)type->tp_alloc(type, 0);
|
||||
//auto target = std::static_pointer_cast<UIEntity>(target);
|
||||
obj->data = &(self->data->points[x + self->data->grid_x * y]);
|
||||
// #123 - Use at() method to route through chunks for large grids
|
||||
obj->data = &(self->data->at(x, y));
|
||||
obj->grid = self->data;
|
||||
return (PyObject*)obj;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
#include "UIDrawable.h"
|
||||
#include "UIBase.h"
|
||||
#include "GridLayers.h"
|
||||
#include "GridChunk.h"
|
||||
|
||||
class UIGrid: public UIDrawable
|
||||
{
|
||||
|
|
@ -75,7 +76,15 @@ public:
|
|||
std::shared_ptr<PyTexture> getTexture();
|
||||
sf::Sprite sprite, output;
|
||||
sf::RenderTexture renderTexture;
|
||||
|
||||
// #123 - Chunk-based storage for large grid support
|
||||
std::unique_ptr<ChunkManager> chunk_manager;
|
||||
// Legacy flat storage (kept for small grids or compatibility)
|
||||
std::vector<UIGridPoint> points;
|
||||
// Use chunks for grids larger than this threshold
|
||||
static constexpr int CHUNK_THRESHOLD = 64;
|
||||
bool use_chunks;
|
||||
|
||||
std::shared_ptr<std::list<std::shared_ptr<UIEntity>>> entities;
|
||||
|
||||
// UIDrawable children collection (speech bubbles, effects, overlays, etc.)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue