Sprite sheets, atlases, game UI, time of day, weather presets, headless captures
- BillboardInstance: per-instance uv sub-rect, feet anchor, turn, dithered alpha, flash; BillboardBatch::sharedTexture; Texture createEmpty/update/cellUV/cellAnchor/nearest - SpriteAtlas: fixed-cell runtime atlas with gutters - hd2d::ui: Octopath-style panels, fonts (Cinzel, EB Garamond), dialogue box with typewriter + portraits + choices, menus, nameplates, toasts, ImGui theme - DayCycle (dawn/day/dusk/night keyframes, sun + moon arcs), Weather overcast/mist/ lightning, night-aware presets, stars and moon in the sky - Hidden-window offscreen captures (Window visible=false, Renderer output FBO, OffscreenTarget); --screenshot never opens a window - Camera::worldToScreen; CMake usable via add_subdirectory (HD2D_BUILD_DEMO, HD2D_ENGINE_ASSET_DIR)
This commit is contained in:
@@ -141,9 +141,111 @@ bool DemoScene::load(const std::string& assetDir) {
|
||||
|
||||
player_.instances.resize(1);
|
||||
placePlayer(kStart);
|
||||
return !spriteTest || buildSpriteTest(assetDir);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Lowest row (from the bottom) with alpha >= 128, or 0 if none.
|
||||
int lowestOpaqueRow(const Image& img) {
|
||||
for (int y = 0; y < img.height; ++y)
|
||||
for (int x = 0; x < img.width; ++x)
|
||||
if (img.rgba[(size_t(y) * img.width + x) * 4 + 3] >= 128) return y;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Copies `src` into `dst` with its bottom-left pixel at (x, y).
|
||||
void blit(Image& dst, const Image& src, int x, int y) {
|
||||
for (int r = 0; r < src.height; ++r)
|
||||
std::copy_n(&src.rgba[size_t(r) * src.width * 4], size_t(src.width) * 4,
|
||||
&dst.rgba[(size_t(y + r) * dst.width + x) * 4]);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool DemoScene::buildSpriteTest(const std::string& assetDir) {
|
||||
Image big, flowers, tuft;
|
||||
if (!loadImage(assetDir + "/sprites/test_sprite.png", big) || !loadImage(assetDir + "/textures/flowers.png", flowers) ||
|
||||
!loadImage(assetDir + "/textures/grass_tuft.png", tuft))
|
||||
return false;
|
||||
// The test sprite is 4x upscaled 64 px art: take one pixel per 4x4 block.
|
||||
Image hero;
|
||||
hero.width = big.width / 4;
|
||||
hero.height = big.height / 4;
|
||||
hero.rgba.resize(size_t(hero.width) * hero.height * 4);
|
||||
for (int y = 0; y < hero.height; ++y)
|
||||
for (int x = 0; x < hero.width; ++x)
|
||||
std::copy_n(&big.rgba[((size_t(y) * 4 + 2) * big.width + x * 4 + 2) * 4], 4,
|
||||
&hero.rgba[(size_t(y) * hero.width + x) * 4]);
|
||||
|
||||
// Runtime atlas: add, then exercise remove/re-add so slots get reused.
|
||||
if (!atlas_.init({64, 64}, 4, 2)) return false;
|
||||
atlasSlots_[0] = atlas_.add(hero);
|
||||
int scratch = atlas_.add(big); // cropped to the cell
|
||||
atlas_.remove(scratch);
|
||||
atlasSlots_[1] = atlas_.add(flowers); // reuses the freed slot
|
||||
atlasSlots_[2] = atlas_.add(tuft);
|
||||
HD2D_LOG("sprite test atlas: %d/%d slots used", atlas_.used(), atlas_.capacity());
|
||||
atlasSprites_.sharedTexture = &atlas_.texture();
|
||||
atlasSprites_.instances.resize(6);
|
||||
|
||||
// 2x1 sprite sheet (authored top-down, one row): hero | flowers.
|
||||
Image sheet;
|
||||
sheet.width = 128;
|
||||
sheet.height = 64;
|
||||
sheet.rgba.assign(size_t(sheet.width) * sheet.height * 4, 0);
|
||||
blit(sheet, hero, 0, 0);
|
||||
blit(sheet, flowers, 64 + (64 - flowers.width) / 2, 0);
|
||||
bleedTransparentColors(sheet);
|
||||
if (!sheetSprites_.texture.create(sheet)) return false;
|
||||
sheetFeetRows_[0] = 63 - lowestOpaqueRow(hero);
|
||||
sheetFeetRows_[1] = 63 - lowestOpaqueRow(flowers);
|
||||
sheetSprites_.instances.resize(1);
|
||||
|
||||
atlasSprites_.emissive = sheetSprites_.emissive = player_.emissive;
|
||||
scene.billboards.push_back(&atlasSprites_);
|
||||
scene.billboards.push_back(&sheetSprites_);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DemoScene::updateSpriteTest(float time, const Camera& camera) {
|
||||
glm::vec3 right = camera.right();
|
||||
glm::vec3 fwd(right.z, 0.0f, -right.x); // horizontal camera forward
|
||||
const glm::vec2 size = BillboardBatch::cellSize(atlas_.cellPixels(), kPixelsPerUnit);
|
||||
labels.clear();
|
||||
auto place = [&](BillboardInstance& bi, int index, const char* label) {
|
||||
glm::vec3 p = playerPos - fwd * 1.8f + right * (float(index) - 3.0f); // row in front of the player
|
||||
bi = BillboardInstance{};
|
||||
bi.pos = glm::vec3(p.x, groundHeight({p.x, p.z}), p.z);
|
||||
bi.size = size;
|
||||
labels.push_back({bi.pos + glm::vec3(0, size.y + 0.15f, 0), label});
|
||||
};
|
||||
auto useSlot = [&](BillboardInstance& bi, int slot) {
|
||||
bi.uv = atlas_.uv(slot);
|
||||
bi.anchor = atlas_.anchor(slot);
|
||||
};
|
||||
auto& a = atlasSprites_.instances;
|
||||
const char* names[] = {"atlas", "flip", "turn", "alpha", "flash", "frames"};
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
place(a[i], i, names[i]);
|
||||
useSlot(a[i], atlasSlots_[0]);
|
||||
}
|
||||
a[1].flip = 1.0f;
|
||||
a[2].turn = 0.8f + 0.4f * std::sin(time * 1.5f);
|
||||
a[3].alpha = 0.5f + 0.3f * std::sin(time * 2.0f);
|
||||
a[4].flash = 0.5f + 0.5f * std::sin(time * 5.0f);
|
||||
useSlot(a[5], atlasSlots_[int(time / 0.4f) % 3]);
|
||||
atlasSprites_.upload();
|
||||
|
||||
BillboardInstance& s = sheetSprites_.instances[0];
|
||||
place(s, 6, "sheet");
|
||||
int frame = int(time / 0.5f) % 2;
|
||||
glm::ivec2 texSize = sheetSprites_.texture.size();
|
||||
s.uv = Texture::cellUV(texSize, {64, 64}, frame, 0);
|
||||
s.anchor = Texture::cellAnchor(texSize, {64, 64}, 0, sheetFeetRows_[frame]);
|
||||
sheetSprites_.upload();
|
||||
}
|
||||
|
||||
void DemoScene::buildTerrain() {
|
||||
Terrain& t = terrain_;
|
||||
t.init(kW, kD, 0.5f);
|
||||
@@ -398,4 +500,5 @@ void DemoScene::update(float dt, float time, glm::vec2 move, const Camera& camer
|
||||
scene.interactor = playerPos;
|
||||
scene.blobShadowPos = glm::vec3(playerPos.x, ground, playerPos.z);
|
||||
scene.focusPoint = playerPos + glm::vec3(0, 0.6f, 0);
|
||||
if (spriteTest) updateSpriteTest(time, camera);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user