Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion shaders/terrain.frag
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
layout(location = 0) in vec3 fragNormal;
layout(location = 1) in vec2 fragTexCoord;
layout(location = 2) in vec3 fragWorldPos;
layout(location = 3) in vec2 fragAtlasCoord;

layout(location = 0) out vec4 outColor;

Expand All @@ -20,7 +21,7 @@ void main() {

vec3 baseColor;
if (material.useTexture == 1u) {
baseColor = texture(texSampler, fragTexCoord).rgb;
baseColor = texture(texSampler, fragAtlasCoord).rgb;
} else {
float height = fragWorldPos.y;
float t = clamp(height / 100.0, 0.0, 1.0);
Expand Down
3 changes: 3 additions & 0 deletions shaders/terrain.vert
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ layout(set = 0, binding = 0) uniform UniformBufferObject {
layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inNormal;
layout(location = 2) in vec2 inTexCoord;
layout(location = 3) in vec2 inAtlasCoord;

layout(location = 0) out vec3 fragNormal;
layout(location = 1) out vec2 fragTexCoord;
layout(location = 2) out vec3 fragWorldPos;
layout(location = 3) out vec2 fragAtlasCoord;

void main() {
vec4 worldPos = ubo.model * vec4(inPosition, 1.0);
Expand All @@ -21,4 +23,5 @@ void main() {
fragNormal = mat3(transpose(inverse(ubo.model))) * inNormal;
fragTexCoord = inTexCoord;
fragWorldPos = worldPos.xyz;
fragAtlasCoord = inAtlasCoord;
}
5 changes: 3 additions & 2 deletions src/lib/gfx/pipeline.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -159,11 +159,12 @@ struct PipelineCreateInfo {
info.fragShaderPath = "shaders/terrain.frag.spv";

info.vertexInput.binding =
vk::VertexInputBindingDescription{0, 32, vk::VertexInputRate::eVertex};
vk::VertexInputBindingDescription{0, 40, vk::VertexInputRate::eVertex};
info.vertexInput.attributes = {
vk::VertexInputAttributeDescription{0, 0, vk::Format::eR32G32B32Sfloat, 0 },
vk::VertexInputAttributeDescription{1, 0, vk::Format::eR32G32B32Sfloat, 12},
vk::VertexInputAttributeDescription{2, 0, vk::Format::eR32G32Sfloat, 24}
vk::VertexInputAttributeDescription{2, 0, vk::Format::eR32G32Sfloat, 24},
vk::VertexInputAttributeDescription{3, 0, vk::Format::eR32G32Sfloat, 32}
};

info.descriptorBindings = {
Expand Down
148 changes: 148 additions & 0 deletions src/render/terrain/terrain_atlas.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
#include "render/terrain/terrain_atlas.hpp"

#include <algorithm>
#include <cmath>

namespace w3d::terrain {

int32_t decodeTileIndex(int16_t tileNdx) {
return static_cast<int32_t>((static_cast<uint16_t>(tileNdx) >> 2) & 0x3FFF);
}

int32_t decodeQuadrant(int16_t tileNdx) {
return static_cast<int32_t>(static_cast<uint16_t>(tileNdx) & 0x3);
}

TileUV computeQuadrantUV(const TileUV &tileUV, int32_t quadrant) {
TileUV result = tileUV;
result.uSize = tileUV.uSize * 0.5f;
result.vSize = tileUV.vSize * 0.5f;

if (quadrant == 1 || quadrant == 3) {
result.u = tileUV.u + result.uSize;
}
if (quadrant == 2 || quadrant == 3) {
result.v = tileUV.v + result.vSize;
}
return result;
}

TileUV decodeTileNdxUV(int16_t tileNdx, const std::vector<TileUV> &tileUVs) {
int32_t tileIndex = decodeTileIndex(tileNdx);
int32_t quadrant = decodeQuadrant(tileNdx);

if (tileIndex < 0 || static_cast<size_t>(tileIndex) >= tileUVs.size()) {
return TileUV{};
}

return computeQuadrantUV(tileUVs[static_cast<size_t>(tileIndex)], quadrant);
}

std::vector<TileUV> computeTileUVTable(const std::vector<map::TextureClass> &textureClasses,
int32_t atlasWidth, int32_t tilePixelSize) {
if (textureClasses.empty() || atlasWidth <= 0 || tilePixelSize <= 0) {
return {};
}

int32_t tilesPerRow = atlasWidth / tilePixelSize;
if (tilesPerRow <= 0) {
return {};
}

int32_t totalTiles = 0;
for (const auto &tc : textureClasses) {
totalTiles += tc.numTiles;
}

if (totalTiles <= 0) {
return {};
}

int32_t totalRows = (totalTiles + tilesPerRow - 1) / tilesPerRow;
int32_t atlasHeight = totalRows * tilePixelSize;

float uStep = static_cast<float>(tilePixelSize) / static_cast<float>(atlasWidth);
float vStep = static_cast<float>(tilePixelSize) / static_cast<float>(atlasHeight);

std::vector<TileUV> result;
result.reserve(static_cast<size_t>(totalTiles));

int32_t tileIdx = 0;
for (const auto &tc : textureClasses) {
for (int32_t i = 0; i < tc.numTiles; ++i) {
int32_t col = tileIdx % tilesPerRow;
int32_t row = tileIdx / tilesPerRow;

TileUV uv;
uv.u = static_cast<float>(col) * uStep;
uv.v = static_cast<float>(row) * vStep;
uv.uSize = uStep;
uv.vSize = vStep;
result.push_back(uv);

++tileIdx;
}
}

return result;
}

TerrainAtlasData buildProceduralAtlas(int32_t numTiles, int32_t atlasWidth, int32_t tilePixelSize) {
if (numTiles <= 0 || atlasWidth <= 0 || tilePixelSize <= 0) {
return {};
}

int32_t tilesPerRow = atlasWidth / tilePixelSize;
if (tilesPerRow <= 0) {
return {};
}

int32_t totalRows = (numTiles + tilesPerRow - 1) / tilesPerRow;
int32_t atlasHeight = totalRows * tilePixelSize;

TerrainAtlasData data;
data.atlasWidth = atlasWidth;
data.atlasHeight = atlasHeight;
data.tilePixelSize = tilePixelSize;
data.tilesPerRow = tilesPerRow;

data.pixels.resize(static_cast<size_t>(atlasWidth * atlasHeight * 4), 0);

float uStep = static_cast<float>(tilePixelSize) / static_cast<float>(atlasWidth);
float vStep = static_cast<float>(tilePixelSize) / static_cast<float>(atlasHeight);

data.tileUVs.reserve(static_cast<size_t>(numTiles));

for (int32_t t = 0; t < numTiles; ++t) {
int32_t col = t % tilesPerRow;
int32_t row = t / tilesPerRow;

uint8_t r = static_cast<uint8_t>((t * 37 + 50) & 0xFF);
uint8_t g = static_cast<uint8_t>((t * 73 + 100) & 0xFF);
uint8_t b = static_cast<uint8_t>((t * 113 + 150) & 0xFF);

int32_t startPx = col * tilePixelSize;
int32_t startPy = row * tilePixelSize;

for (int32_t py = startPy; py < startPy + tilePixelSize && py < atlasHeight; ++py) {
for (int32_t px = startPx; px < startPx + tilePixelSize && px < atlasWidth; ++px) {
size_t idx = static_cast<size_t>((py * atlasWidth + px) * 4);
data.pixels[idx + 0] = r;
data.pixels[idx + 1] = g;
data.pixels[idx + 2] = b;
data.pixels[idx + 3] = 255;
}
}

TileUV uv;
uv.u = static_cast<float>(col) * uStep;
uv.v = static_cast<float>(row) * vStep;
uv.uSize = uStep;
uv.vSize = vStep;
data.tileUVs.push_back(uv);
}

return data;
}

} // namespace w3d::terrain
43 changes: 43 additions & 0 deletions src/render/terrain/terrain_atlas.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#pragma once

#include <cstdint>
#include <vector>

#include "lib/formats/map/types.hpp"

namespace w3d::terrain {

struct TileUV {
float u = 0.0f;
float v = 0.0f;
float uSize = 0.0f;
float vSize = 0.0f;
};

struct TerrainAtlasData {
int32_t atlasWidth = 0;
int32_t atlasHeight = 0;
int32_t tilePixelSize = 64;
int32_t tilesPerRow = 0;
std::vector<uint8_t> pixels;
std::vector<TileUV> tileUVs;

bool isValid() const { return atlasWidth > 0 && atlasHeight > 0 && !pixels.empty(); }
};

[[nodiscard]] int32_t decodeTileIndex(int16_t tileNdx);

[[nodiscard]] int32_t decodeQuadrant(int16_t tileNdx);

[[nodiscard]] TileUV computeQuadrantUV(const TileUV &tileUV, int32_t quadrant);

[[nodiscard]] TileUV decodeTileNdxUV(int16_t tileNdx, const std::vector<TileUV> &tileUVs);

[[nodiscard]] std::vector<TileUV>
computeTileUVTable(const std::vector<map::TextureClass> &textureClasses, int32_t atlasWidth = 2048,
int32_t tilePixelSize = 64);

[[nodiscard]] TerrainAtlasData buildProceduralAtlas(int32_t numTiles, int32_t atlasWidth = 2048,
int32_t tilePixelSize = 64);

} // namespace w3d::terrain
113 changes: 113 additions & 0 deletions src/render/terrain/terrain_blend.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#include "render/terrain/terrain_blend.hpp"

#include <algorithm>
#include <cmath>

namespace w3d::terrain {

BlendPattern generateBlendPattern(BlendDirection direction) {
BlendPattern pattern;
pattern.size = BLEND_PATTERN_SIZE;
pattern.alpha.resize(static_cast<size_t>(BLEND_PATTERN_SIZE * BLEND_PATTERN_SIZE), 0);

for (int32_t y = 0; y < BLEND_PATTERN_SIZE; ++y) {
for (int32_t x = 0; x < BLEND_PATTERN_SIZE; ++x) {
float nx = static_cast<float>(x) / static_cast<float>(BLEND_PATTERN_SIZE - 1);
float ny = static_cast<float>(y) / static_cast<float>(BLEND_PATTERN_SIZE - 1);
float value = 0.0f;

switch (direction) {
case BlendDirection::Horizontal:
value = nx;
break;
case BlendDirection::HorizontalInv:
value = 1.0f - nx;
break;
case BlendDirection::Vertical:
value = ny;
break;
case BlendDirection::VerticalInv:
value = 1.0f - ny;
break;
case BlendDirection::DiagonalRight:
value = std::clamp((nx + ny) * 0.5f, 0.0f, 1.0f);
break;
case BlendDirection::DiagonalRightInv:
value = 1.0f - std::clamp((nx + ny) * 0.5f, 0.0f, 1.0f);
break;
case BlendDirection::DiagonalLeft:
value = std::clamp(((1.0f - nx) + ny) * 0.5f, 0.0f, 1.0f);
break;
case BlendDirection::DiagonalLeftInv:
value = 1.0f - std::clamp(((1.0f - nx) + ny) * 0.5f, 0.0f, 1.0f);
break;
case BlendDirection::LongDiagonal:
value = std::clamp((2.0f * nx + ny) / 3.0f, 0.0f, 1.0f);
break;
case BlendDirection::LongDiagonalInv:
value = 1.0f - std::clamp((2.0f * nx + ny) / 3.0f, 0.0f, 1.0f);
break;
case BlendDirection::LongDiagonalAlt:
value = std::clamp((nx + 2.0f * ny) / 3.0f, 0.0f, 1.0f);
break;
case BlendDirection::LongDiagonalAltInv:
value = 1.0f - std::clamp((nx + 2.0f * ny) / 3.0f, 0.0f, 1.0f);
break;
}

size_t idx = static_cast<size_t>(y * BLEND_PATTERN_SIZE + x);
pattern.alpha[idx] = static_cast<uint8_t>(std::clamp(value, 0.0f, 1.0f) * 255.0f);
}
}

return pattern;
}

std::vector<BlendPattern> generateAllBlendPatterns() {
std::vector<BlendPattern> patterns;
patterns.reserve(NUM_BLEND_PATTERNS);

for (int32_t i = 0; i < NUM_BLEND_PATTERNS; ++i) {
patterns.push_back(generateBlendPattern(static_cast<BlendDirection>(i)));
}

return patterns;
}

BlendDirection blendDirectionFromInfo(const map::BlendTileInfo &info) {
if (info.horiz != 0) {
return (info.inverted & map::INVERTED_MASK) ? BlendDirection::HorizontalInv
: BlendDirection::Horizontal;
}
if (info.vert != 0) {
return (info.inverted & map::INVERTED_MASK) ? BlendDirection::VerticalInv
: BlendDirection::Vertical;
}
if (info.rightDiagonal != 0) {
return (info.inverted & map::INVERTED_MASK) ? BlendDirection::DiagonalRightInv
: BlendDirection::DiagonalRight;
}
if (info.leftDiagonal != 0) {
return (info.inverted & map::INVERTED_MASK) ? BlendDirection::DiagonalLeftInv
: BlendDirection::DiagonalLeft;
}
if (info.longDiagonal != 0) {
bool alt = (info.inverted & map::FLIPPED_MASK) != 0;
bool inv = (info.inverted & map::INVERTED_MASK) != 0;
if (alt && inv)
return BlendDirection::LongDiagonalAltInv;
if (alt)
return BlendDirection::LongDiagonalAlt;
if (inv)
return BlendDirection::LongDiagonalInv;
return BlendDirection::LongDiagonal;
}
return BlendDirection::Horizontal;
}

bool cellHasBlend(const map::BlendTileInfo &info) {
return info.horiz != 0 || info.vert != 0 || info.rightDiagonal != 0 || info.leftDiagonal != 0 ||
info.longDiagonal != 0;
}

} // namespace w3d::terrain
41 changes: 41 additions & 0 deletions src/render/terrain/terrain_blend.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#pragma once

#include <cstdint>
#include <vector>

#include "lib/formats/map/types.hpp"

namespace w3d::terrain {

constexpr int32_t BLEND_PATTERN_SIZE = 64;
constexpr int32_t NUM_BLEND_PATTERNS = 12;

enum class BlendDirection : int32_t {
Horizontal = 0,
HorizontalInv = 1,
Vertical = 2,
VerticalInv = 3,
DiagonalRight = 4,
DiagonalRightInv = 5,
DiagonalLeft = 6,
DiagonalLeftInv = 7,
LongDiagonal = 8,
LongDiagonalInv = 9,
LongDiagonalAlt = 10,
LongDiagonalAltInv = 11,
};

struct BlendPattern {
int32_t size = BLEND_PATTERN_SIZE;
std::vector<uint8_t> alpha;
};

[[nodiscard]] BlendPattern generateBlendPattern(BlendDirection direction);

[[nodiscard]] std::vector<BlendPattern> generateAllBlendPatterns();

[[nodiscard]] BlendDirection blendDirectionFromInfo(const map::BlendTileInfo &info);

[[nodiscard]] bool cellHasBlend(const map::BlendTileInfo &info);

} // namespace w3d::terrain
Loading