- Fix JsonDataNode::getChildReadOnly() to handle JSON array access by numeric index - Fix test_ui_showcase to use JSON array for children (matching test_single_button pattern) - Add visual test files: test_single_button, test_ui_showcase, test_sprite_debug - Clean up debug logging from SpritePass, SceneCollector, UIButton, BgfxDevice The root cause was that UITree couldn't access array children in JSON layouts. UIButton hover/click now works correctly in both test files. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
661 lines
22 KiB
C++
661 lines
22 KiB
C++
/**
|
|
* BgfxRenderer Complete Showcase
|
|
*
|
|
* Demonstrates ALL rendering features of BgfxRendererModule:
|
|
* - Sprites (static, animated, colored, layered, rotated, scaled)
|
|
* - Text (different sizes, colors, multi-line)
|
|
* - Particles (with life, velocity, size, additive blending)
|
|
* - Tilemap (simple grid with tile indices)
|
|
* - Debug primitives (lines, rectangles wireframe and filled)
|
|
* - Camera (orthographic projection, panning, zooming)
|
|
* - Clear color
|
|
*
|
|
* Controls:
|
|
* - Arrow keys: Move camera
|
|
* - +/-: Zoom in/out
|
|
* - SPACE: Spawn explosion particles
|
|
* - C: Cycle clear color
|
|
* - ESC: Exit
|
|
*/
|
|
|
|
#include <SDL.h>
|
|
#include <SDL_syswm.h>
|
|
#include <iostream>
|
|
#include <cmath>
|
|
#include <memory>
|
|
#include <vector>
|
|
#include <random>
|
|
|
|
#include "BgfxRendererModule.h"
|
|
#include <grove/JsonDataNode.h>
|
|
#include <grove/IntraIOManager.h>
|
|
#include <grove/IntraIO.h>
|
|
#include <spdlog/spdlog.h>
|
|
#include <spdlog/sinks/stdout_color_sinks.h>
|
|
|
|
using namespace grove;
|
|
|
|
// Particle structure for CPU-side simulation
|
|
struct Particle {
|
|
float x, y;
|
|
float vx, vy;
|
|
float size;
|
|
float life;
|
|
float maxLife;
|
|
uint32_t color;
|
|
bool alive;
|
|
};
|
|
|
|
// Simple tilemap data (10x8 grid)
|
|
static const int TILEMAP_WIDTH = 10;
|
|
static const int TILEMAP_HEIGHT = 8;
|
|
static const int TILE_SIZE = 32;
|
|
static uint16_t tilemapData[TILEMAP_WIDTH * TILEMAP_HEIGHT] = {
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
|
1, 0, 2, 2, 0, 0, 3, 3, 0, 1,
|
|
1, 0, 2, 2, 0, 0, 3, 3, 0, 1,
|
|
1, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
|
1, 0, 4, 4, 4, 4, 4, 4, 0, 1,
|
|
1, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
};
|
|
|
|
// Clear colors to cycle through
|
|
static const uint32_t clearColors[] = {
|
|
0x1a1a2eFF, // Dark blue-gray
|
|
0x16213eFF, // Navy blue
|
|
0x0f3460FF, // Deep blue
|
|
0x1e5128FF, // Forest green
|
|
0x2c3333FF, // Dark teal
|
|
0x3d0000FF, // Dark red
|
|
};
|
|
static const int numClearColors = sizeof(clearColors) / sizeof(clearColors[0]);
|
|
|
|
class RendererShowcase {
|
|
public:
|
|
RendererShowcase()
|
|
: m_rng(std::random_device{}())
|
|
{
|
|
m_particles.reserve(500);
|
|
}
|
|
|
|
bool init(SDL_Window* window) {
|
|
// Get native window handle
|
|
SDL_SysWMinfo wmi;
|
|
SDL_VERSION(&wmi.version);
|
|
SDL_GetWindowWMInfo(window, &wmi);
|
|
|
|
m_logger = spdlog::stdout_color_mt("Showcase");
|
|
spdlog::set_level(spdlog::level::info);
|
|
|
|
m_logger->info("=== BgfxRenderer Complete Showcase ===");
|
|
|
|
// Create IIO instances - IMPORTANT: separate for publisher and subscriber
|
|
// Keep shared_ptr alive, use IIO* abstract interface
|
|
m_rendererIOPtr = IntraIOManager::getInstance().createInstance("renderer");
|
|
m_gameIOPtr = IntraIOManager::getInstance().createInstance("game");
|
|
m_rendererIO = m_rendererIOPtr.get();
|
|
m_gameIO = m_gameIOPtr.get();
|
|
|
|
// Create and configure renderer
|
|
m_renderer = std::make_unique<BgfxRendererModule>();
|
|
|
|
JsonDataNode config("config");
|
|
config.setDouble("nativeWindowHandle", static_cast<double>(reinterpret_cast<uintptr_t>(wmi.info.win.window)));
|
|
config.setInt("windowWidth", 1024);
|
|
config.setInt("windowHeight", 768);
|
|
config.setString("backend", "d3d11");
|
|
config.setBool("vsync", true);
|
|
|
|
// Load textures - try multiple paths for flexibility
|
|
// Works from build/ or build/tests/
|
|
config.setString("texture1", "../assets/textures/1f440.png"); // Eye emoji
|
|
config.setString("texture2", "../assets/textures/5oxaxt1vo2f91.jpg"); // Image
|
|
|
|
m_renderer->setConfiguration(config, m_rendererIO, nullptr);
|
|
|
|
m_logger->info("Renderer initialized");
|
|
m_logger->info("Controls:");
|
|
m_logger->info(" Arrows: Move camera");
|
|
m_logger->info(" +/-: Zoom");
|
|
m_logger->info(" SPACE: Spawn particles");
|
|
m_logger->info(" C: Cycle clear color");
|
|
m_logger->info(" ESC: Exit");
|
|
|
|
return true;
|
|
}
|
|
|
|
void handleInput(SDL_Event& e) {
|
|
if (e.type == SDL_KEYDOWN) {
|
|
switch (e.key.keysym.sym) {
|
|
case SDLK_LEFT: m_cameraVX = -200.0f; break;
|
|
case SDLK_RIGHT: m_cameraVX = 200.0f; break;
|
|
case SDLK_UP: m_cameraVY = -200.0f; break;
|
|
case SDLK_DOWN: m_cameraVY = 200.0f; break;
|
|
case SDLK_PLUS:
|
|
case SDLK_EQUALS:
|
|
m_cameraZoom = std::min(4.0f, m_cameraZoom * 1.1f);
|
|
break;
|
|
case SDLK_MINUS:
|
|
m_cameraZoom = std::max(0.25f, m_cameraZoom / 1.1f);
|
|
break;
|
|
case SDLK_SPACE:
|
|
spawnExplosion(512.0f + m_cameraX, 400.0f + m_cameraY);
|
|
break;
|
|
case SDLK_c:
|
|
m_clearColorIndex = (m_clearColorIndex + 1) % numClearColors;
|
|
break;
|
|
}
|
|
}
|
|
else if (e.type == SDL_KEYUP) {
|
|
switch (e.key.keysym.sym) {
|
|
case SDLK_LEFT:
|
|
case SDLK_RIGHT:
|
|
m_cameraVX = 0.0f;
|
|
break;
|
|
case SDLK_UP:
|
|
case SDLK_DOWN:
|
|
m_cameraVY = 0.0f;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void update(float dt) {
|
|
m_time += dt;
|
|
m_frameCount++;
|
|
|
|
// Update camera position
|
|
m_cameraX += m_cameraVX * dt;
|
|
m_cameraY += m_cameraVY * dt;
|
|
|
|
// Update particles
|
|
updateParticles(dt);
|
|
}
|
|
|
|
void render() {
|
|
// 1. Set clear color
|
|
sendClearColor();
|
|
|
|
// 2. Set camera
|
|
sendCamera();
|
|
|
|
// 3. Render tilemap (background layer)
|
|
sendTilemap();
|
|
|
|
// 4. Render sprites (multiple layers)
|
|
sendSprites();
|
|
|
|
// 5. Render particles
|
|
sendParticles();
|
|
|
|
// 6. Render text
|
|
sendText();
|
|
|
|
// 7. Render debug primitives
|
|
sendDebugPrimitives();
|
|
|
|
// Process frame
|
|
JsonDataNode input("input");
|
|
input.setDouble("deltaTime", 0.016);
|
|
input.setInt("frameCount", m_frameCount);
|
|
m_renderer->process(input);
|
|
}
|
|
|
|
void shutdown() {
|
|
m_renderer->shutdown();
|
|
IntraIOManager::getInstance().removeInstance("renderer");
|
|
IntraIOManager::getInstance().removeInstance("game");
|
|
m_logger->info("Showcase shutdown complete");
|
|
}
|
|
|
|
int getFrameCount() const { return m_frameCount; }
|
|
|
|
private:
|
|
void sendClearColor() {
|
|
auto clear = std::make_unique<JsonDataNode>("clear");
|
|
clear->setInt("color", clearColors[m_clearColorIndex]);
|
|
m_gameIO->publish("render:clear", std::move(clear));
|
|
}
|
|
|
|
void sendCamera() {
|
|
auto cam = std::make_unique<JsonDataNode>("camera");
|
|
cam->setDouble("x", m_cameraX);
|
|
cam->setDouble("y", m_cameraY);
|
|
cam->setDouble("zoom", m_cameraZoom);
|
|
cam->setInt("viewportX", 0);
|
|
cam->setInt("viewportY", 0);
|
|
cam->setInt("viewportW", 1024);
|
|
cam->setInt("viewportH", 768);
|
|
m_gameIO->publish("render:camera", std::move(cam));
|
|
}
|
|
|
|
void sendTilemap() {
|
|
auto tilemap = std::make_unique<JsonDataNode>("tilemap");
|
|
tilemap->setDouble("x", 50.0);
|
|
tilemap->setDouble("y", 400.0);
|
|
tilemap->setInt("width", TILEMAP_WIDTH);
|
|
tilemap->setInt("height", TILEMAP_HEIGHT);
|
|
tilemap->setInt("tileW", TILE_SIZE);
|
|
tilemap->setInt("tileH", TILE_SIZE);
|
|
tilemap->setInt("textureId", 0);
|
|
|
|
// Convert tilemap to comma-separated string
|
|
std::string tileData;
|
|
for (int i = 0; i < TILEMAP_WIDTH * TILEMAP_HEIGHT; ++i) {
|
|
if (i > 0) tileData += ",";
|
|
tileData += std::to_string(tilemapData[i]);
|
|
}
|
|
tilemap->setString("tileData", tileData);
|
|
|
|
m_gameIO->publish("render:tilemap", std::move(tilemap));
|
|
}
|
|
|
|
void sendSprites() {
|
|
// Layer 0: Background sprites with TEXTURE 2 (image)
|
|
for (int i = 0; i < 5; ++i) {
|
|
auto sprite = std::make_unique<JsonDataNode>("sprite");
|
|
sprite->setDouble("x", 100 + i * 150);
|
|
sprite->setDouble("y", 50);
|
|
sprite->setDouble("scaleX", 120.0);
|
|
sprite->setDouble("scaleY", 90.0);
|
|
sprite->setDouble("rotation", 0.0);
|
|
sprite->setInt("color", 0xFFFFFFFF); // White (no tint)
|
|
sprite->setInt("layer", 0);
|
|
sprite->setInt("textureId", 2); // Image texture
|
|
m_gameIO->publish("render:sprite", std::move(sprite));
|
|
}
|
|
|
|
// Layer 5: Bouncing EYE EMOJIS with texture 1
|
|
for (int i = 0; i < 5; ++i) {
|
|
float offset = std::sin(m_time * 2.0f + i * 1.2f) * 40.0f;
|
|
auto sprite = std::make_unique<JsonDataNode>("sprite");
|
|
sprite->setDouble("x", 100 + i * 180);
|
|
sprite->setDouble("y", 200 + offset);
|
|
sprite->setDouble("scaleX", 64.0);
|
|
sprite->setDouble("scaleY", 64.0);
|
|
sprite->setDouble("rotation", 0.0);
|
|
sprite->setInt("color", 0xFFFFFFFF); // No tint
|
|
sprite->setInt("layer", 5);
|
|
sprite->setInt("textureId", 1); // Eye emoji
|
|
m_gameIO->publish("render:sprite", std::move(sprite));
|
|
}
|
|
|
|
// Layer 10: Rotating eye emoji
|
|
{
|
|
auto sprite = std::make_unique<JsonDataNode>("sprite");
|
|
sprite->setDouble("x", 700);
|
|
sprite->setDouble("y", 200);
|
|
sprite->setDouble("scaleX", 100.0);
|
|
sprite->setDouble("scaleY", 100.0);
|
|
sprite->setDouble("rotation", m_time); // Radians
|
|
sprite->setInt("color", 0xFFFFFFFF);
|
|
sprite->setInt("layer", 10);
|
|
sprite->setInt("textureId", 1); // Eye emoji
|
|
m_gameIO->publish("render:sprite", std::move(sprite));
|
|
}
|
|
|
|
// Layer 15: Scaling/pulsing image
|
|
{
|
|
float scale = 80.0f + std::sin(m_time * 3.0f) * 30.0f;
|
|
auto sprite = std::make_unique<JsonDataNode>("sprite");
|
|
sprite->setDouble("x", 850);
|
|
sprite->setDouble("y", 200);
|
|
sprite->setDouble("scaleX", scale);
|
|
sprite->setDouble("scaleY", scale * 0.75f);
|
|
sprite->setDouble("rotation", 0.0);
|
|
sprite->setInt("color", 0xFFFFFFFF);
|
|
sprite->setInt("layer", 15);
|
|
sprite->setInt("textureId", 2); // Image
|
|
m_gameIO->publish("render:sprite", std::move(sprite));
|
|
}
|
|
|
|
// Layer 20: Tinted sprites (color overlay on texture)
|
|
{
|
|
uint32_t colors[] = { 0xFF8888FF, 0x88FF88FF, 0x8888FFFF, 0xFFFF88FF };
|
|
for (int i = 0; i < 4; ++i) {
|
|
auto sprite = std::make_unique<JsonDataNode>("sprite");
|
|
sprite->setDouble("x", 100 + i * 100);
|
|
sprite->setDouble("y", 320);
|
|
sprite->setDouble("scaleX", 80.0);
|
|
sprite->setDouble("scaleY", 80.0);
|
|
sprite->setDouble("rotation", 0.0);
|
|
sprite->setInt("color", colors[i]); // Tinted
|
|
sprite->setInt("layer", 20);
|
|
sprite->setInt("textureId", 1); // Eye emoji with color tint
|
|
m_gameIO->publish("render:sprite", std::move(sprite));
|
|
}
|
|
}
|
|
|
|
// Layer 25: Grid of small images
|
|
for (int row = 0; row < 2; ++row) {
|
|
for (int col = 0; col < 4; ++col) {
|
|
auto sprite = std::make_unique<JsonDataNode>("sprite");
|
|
sprite->setDouble("x", 550 + col * 70);
|
|
sprite->setDouble("y", 320 + row * 55);
|
|
sprite->setDouble("scaleX", 60.0);
|
|
sprite->setDouble("scaleY", 45.0);
|
|
sprite->setDouble("rotation", 0.0);
|
|
sprite->setInt("color", 0xFFFFFFFF);
|
|
sprite->setInt("layer", 25);
|
|
sprite->setInt("textureId", 2); // Image
|
|
m_gameIO->publish("render:sprite", std::move(sprite));
|
|
}
|
|
}
|
|
}
|
|
|
|
void sendParticles() {
|
|
for (const auto& p : m_particles) {
|
|
if (!p.alive) continue;
|
|
|
|
auto particle = std::make_unique<JsonDataNode>("particle");
|
|
particle->setDouble("x", p.x);
|
|
particle->setDouble("y", p.y);
|
|
particle->setDouble("vx", p.vx);
|
|
particle->setDouble("vy", p.vy);
|
|
particle->setDouble("size", p.size);
|
|
particle->setDouble("life", p.life / p.maxLife); // Normalized 0-1
|
|
particle->setInt("color", p.color);
|
|
particle->setInt("textureId", 0);
|
|
m_gameIO->publish("render:particle", std::move(particle));
|
|
}
|
|
}
|
|
|
|
void sendText() {
|
|
// Title (large)
|
|
{
|
|
auto text = std::make_unique<JsonDataNode>("text");
|
|
text->setDouble("x", 10);
|
|
text->setDouble("y", 10);
|
|
text->setString("text", "BgfxRenderer Showcase");
|
|
text->setInt("fontSize", 32);
|
|
text->setInt("color", 0xFFFFFFFF);
|
|
text->setInt("layer", 100);
|
|
m_gameIO->publish("render:text", std::move(text));
|
|
}
|
|
|
|
// Info text
|
|
{
|
|
auto text = std::make_unique<JsonDataNode>("text");
|
|
text->setDouble("x", 10);
|
|
text->setDouble("y", 50);
|
|
char buf[128];
|
|
snprintf(buf, sizeof(buf), "Frame: %d Camera: (%.0f, %.0f) Zoom: %.2fx",
|
|
m_frameCount, m_cameraX, m_cameraY, m_cameraZoom);
|
|
text->setString("text", buf);
|
|
text->setInt("fontSize", 16);
|
|
text->setInt("color", 0xAAAAAAFF);
|
|
text->setInt("layer", 100);
|
|
m_gameIO->publish("render:text", std::move(text));
|
|
}
|
|
|
|
// Particles count
|
|
{
|
|
auto text = std::make_unique<JsonDataNode>("text");
|
|
text->setDouble("x", 10);
|
|
text->setDouble("y", 70);
|
|
char buf[64];
|
|
int aliveCount = 0;
|
|
for (const auto& p : m_particles) if (p.alive) aliveCount++;
|
|
snprintf(buf, sizeof(buf), "Particles: %d", aliveCount);
|
|
text->setString("text", buf);
|
|
text->setInt("fontSize", 16);
|
|
text->setInt("color", 0xFFCC00FF);
|
|
text->setInt("layer", 100);
|
|
m_gameIO->publish("render:text", std::move(text));
|
|
}
|
|
|
|
// Controls help
|
|
{
|
|
auto text = std::make_unique<JsonDataNode>("text");
|
|
text->setDouble("x", 750);
|
|
text->setDouble("y", 730);
|
|
text->setString("text", "SPACE: Particles | C: Color | Arrows: Pan | +/-: Zoom");
|
|
text->setInt("fontSize", 14);
|
|
text->setInt("color", 0x888888FF);
|
|
text->setInt("layer", 100);
|
|
m_gameIO->publish("render:text", std::move(text));
|
|
}
|
|
|
|
// Multi-line text demo
|
|
{
|
|
auto text = std::make_unique<JsonDataNode>("text");
|
|
text->setDouble("x", 700);
|
|
text->setDouble("y", 300);
|
|
text->setString("text", "Multi-line\nText Demo\nWith Colors!");
|
|
text->setInt("fontSize", 20);
|
|
text->setInt("color", 0x00FF88FF);
|
|
text->setInt("layer", 100);
|
|
m_gameIO->publish("render:text", std::move(text));
|
|
}
|
|
}
|
|
|
|
void sendDebugPrimitives() {
|
|
// Grid lines
|
|
for (int i = 0; i <= 10; ++i) {
|
|
// Vertical lines
|
|
auto vline = std::make_unique<JsonDataNode>("line");
|
|
vline->setDouble("x1", 50 + i * 32);
|
|
vline->setDouble("y1", 400);
|
|
vline->setDouble("x2", 50 + i * 32);
|
|
vline->setDouble("y2", 400 + TILEMAP_HEIGHT * 32);
|
|
vline->setInt("color", 0x444444FF);
|
|
m_gameIO->publish("render:debug:line", std::move(vline));
|
|
}
|
|
for (int i = 0; i <= 8; ++i) {
|
|
// Horizontal lines
|
|
auto hline = std::make_unique<JsonDataNode>("line");
|
|
hline->setDouble("x1", 50);
|
|
hline->setDouble("y1", 400 + i * 32);
|
|
hline->setDouble("x2", 50 + TILEMAP_WIDTH * 32);
|
|
hline->setDouble("y2", 400 + i * 32);
|
|
hline->setInt("color", 0x444444FF);
|
|
m_gameIO->publish("render:debug:line", std::move(hline));
|
|
}
|
|
|
|
// Diagonal line (animated)
|
|
{
|
|
auto line = std::make_unique<JsonDataNode>("line");
|
|
float offset = std::sin(m_time) * 50.0f;
|
|
line->setDouble("x1", 500);
|
|
line->setDouble("y1", 450 + offset);
|
|
line->setDouble("x2", 700);
|
|
line->setDouble("y2", 550 - offset);
|
|
line->setInt("color", 0xFFFF00FF); // Yellow
|
|
m_gameIO->publish("render:debug:line", std::move(line));
|
|
}
|
|
|
|
// Wireframe rectangle
|
|
{
|
|
auto rect = std::make_unique<JsonDataNode>("rect");
|
|
rect->setDouble("x", 750);
|
|
rect->setDouble("y", 400);
|
|
rect->setDouble("w", 100);
|
|
rect->setDouble("h", 80);
|
|
rect->setInt("color", 0x00FFFFFF); // Cyan
|
|
rect->setBool("filled", false);
|
|
m_gameIO->publish("render:debug:rect", std::move(rect));
|
|
}
|
|
|
|
// Filled rectangle (pulsing)
|
|
{
|
|
float pulse = (std::sin(m_time * 2.0f) + 1.0f) * 0.5f;
|
|
uint8_t alpha = static_cast<uint8_t>(128 + pulse * 127);
|
|
uint32_t color = 0xFF4444FF | (alpha);
|
|
|
|
auto rect = std::make_unique<JsonDataNode>("rect");
|
|
rect->setDouble("x", 870);
|
|
rect->setDouble("y", 400);
|
|
rect->setDouble("w", 80);
|
|
rect->setDouble("h", 80);
|
|
rect->setInt("color", color);
|
|
rect->setBool("filled", true);
|
|
m_gameIO->publish("render:debug:rect", std::move(rect));
|
|
}
|
|
|
|
// Crosshair at center
|
|
{
|
|
float cx = 512.0f, cy = 384.0f;
|
|
auto hline = std::make_unique<JsonDataNode>("line");
|
|
hline->setDouble("x1", cx - 20);
|
|
hline->setDouble("y1", cy);
|
|
hline->setDouble("x2", cx + 20);
|
|
hline->setDouble("y2", cy);
|
|
hline->setInt("color", 0xFF0000FF);
|
|
m_gameIO->publish("render:debug:line", std::move(hline));
|
|
|
|
auto vline = std::make_unique<JsonDataNode>("line");
|
|
vline->setDouble("x1", cx);
|
|
vline->setDouble("y1", cy - 20);
|
|
vline->setDouble("x2", cx);
|
|
vline->setDouble("y2", cy + 20);
|
|
vline->setInt("color", 0xFF0000FF);
|
|
m_gameIO->publish("render:debug:line", std::move(vline));
|
|
}
|
|
}
|
|
|
|
void spawnExplosion(float x, float y) {
|
|
std::uniform_real_distribution<float> angleDist(0.0f, 2.0f * 3.14159f);
|
|
std::uniform_real_distribution<float> speedDist(50.0f, 200.0f);
|
|
std::uniform_real_distribution<float> sizeDist(5.0f, 20.0f);
|
|
std::uniform_real_distribution<float> lifeDist(0.5f, 2.0f);
|
|
|
|
uint32_t colors[] = {
|
|
0xFF4444FF, 0xFF8844FF, 0xFFCC44FF, 0xFFFF44FF,
|
|
0xFF6644FF, 0xFFAA00FF
|
|
};
|
|
std::uniform_int_distribution<int> colorDist(0, 5);
|
|
|
|
for (int i = 0; i < 50; ++i) {
|
|
Particle p;
|
|
float angle = angleDist(m_rng);
|
|
float speed = speedDist(m_rng);
|
|
p.x = x;
|
|
p.y = y;
|
|
p.vx = std::cos(angle) * speed;
|
|
p.vy = std::sin(angle) * speed;
|
|
p.size = sizeDist(m_rng);
|
|
p.life = lifeDist(m_rng);
|
|
p.maxLife = p.life;
|
|
p.color = colors[colorDist(m_rng)];
|
|
p.alive = true;
|
|
|
|
// Find dead particle slot or add new
|
|
bool found = false;
|
|
for (auto& existing : m_particles) {
|
|
if (!existing.alive) {
|
|
existing = p;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
m_particles.push_back(p);
|
|
}
|
|
}
|
|
|
|
m_logger->info("Spawned explosion at ({:.0f}, {:.0f})", x, y);
|
|
}
|
|
|
|
void updateParticles(float dt) {
|
|
for (auto& p : m_particles) {
|
|
if (!p.alive) continue;
|
|
|
|
p.x += p.vx * dt;
|
|
p.y += p.vy * dt;
|
|
p.vy += 100.0f * dt; // Gravity
|
|
p.life -= dt;
|
|
|
|
if (p.life <= 0.0f) {
|
|
p.alive = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<spdlog::logger> m_logger;
|
|
std::unique_ptr<BgfxRendererModule> m_renderer;
|
|
std::shared_ptr<IntraIO> m_rendererIOPtr; // Keep shared_ptr alive
|
|
std::shared_ptr<IntraIO> m_gameIOPtr; // Keep shared_ptr alive
|
|
IIO* m_rendererIO = nullptr; // Abstract interface
|
|
IIO* m_gameIO = nullptr; // Abstract interface
|
|
|
|
float m_time = 0.0f;
|
|
int m_frameCount = 0;
|
|
|
|
// Camera
|
|
float m_cameraX = 0.0f;
|
|
float m_cameraY = 0.0f;
|
|
float m_cameraVX = 0.0f;
|
|
float m_cameraVY = 0.0f;
|
|
float m_cameraZoom = 1.0f;
|
|
|
|
// Clear color
|
|
int m_clearColorIndex = 0;
|
|
|
|
// Particles
|
|
std::vector<Particle> m_particles;
|
|
std::mt19937 m_rng;
|
|
};
|
|
|
|
int main(int argc, char* argv[]) {
|
|
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
|
|
std::cerr << "SDL_Init failed: " << SDL_GetError() << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
SDL_Window* window = SDL_CreateWindow(
|
|
"BgfxRenderer Showcase",
|
|
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
|
1024, 768,
|
|
SDL_WINDOW_SHOWN
|
|
);
|
|
|
|
if (!window) {
|
|
std::cerr << "SDL_CreateWindow failed: " << SDL_GetError() << std::endl;
|
|
SDL_Quit();
|
|
return 1;
|
|
}
|
|
|
|
RendererShowcase showcase;
|
|
if (!showcase.init(window)) {
|
|
SDL_DestroyWindow(window);
|
|
SDL_Quit();
|
|
return 1;
|
|
}
|
|
|
|
bool running = true;
|
|
Uint64 lastTime = SDL_GetPerformanceCounter();
|
|
|
|
while (running) {
|
|
SDL_Event e;
|
|
while (SDL_PollEvent(&e)) {
|
|
if (e.type == SDL_QUIT ||
|
|
(e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE)) {
|
|
running = false;
|
|
}
|
|
showcase.handleInput(e);
|
|
}
|
|
|
|
Uint64 now = SDL_GetPerformanceCounter();
|
|
float dt = static_cast<float>(now - lastTime) / SDL_GetPerformanceFrequency();
|
|
lastTime = now;
|
|
|
|
showcase.update(dt);
|
|
showcase.render();
|
|
|
|
// Cap to ~60 FPS
|
|
SDL_Delay(1);
|
|
}
|
|
|
|
Uint64 endTime = SDL_GetPerformanceCounter();
|
|
int frames = showcase.getFrameCount();
|
|
|
|
showcase.shutdown();
|
|
SDL_DestroyWindow(window);
|
|
SDL_Quit();
|
|
|
|
std::cout << "Rendered " << frames << " frames" << std::endl;
|
|
return 0;
|
|
}
|