Files
Destrum/destrum/src/Graphics/Resources/PrimitiveMeshes.cpp
T

118 lines
3.7 KiB
C++

#include <destrum/Graphics/Resources/PrimitiveMeshes.h>
#include <array>
#include <charconv>
#include <cstdint>
#include <format>
CPUMesh PrimitiveMeshes::MakeBox(const glm::vec3& size)
{
struct Face {
glm::vec3 normal;
glm::vec3 tangent;
};
constexpr std::array<Face, 6> faces{{
{{1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, -1.0f}},
{{-1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}},
{{0.0f, 1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}},
{{0.0f, -1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}},
{{0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}},
{{0.0f, 0.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}},
}};
constexpr std::array<glm::vec2, 4> corners{{
{-1.0f, -1.0f}, {1.0f, -1.0f}, {1.0f, 1.0f}, {-1.0f, 1.0f},
}};
CPUMesh mesh{};
// The name becomes the mesh cache key, saved scenes rebuild the box from it
mesh.name = BoxKey(size);
mesh.minPos = -size * 0.5f;
mesh.maxPos = size * 0.5f;
for (const Face& face : faces) {
const glm::vec3 bitangent = glm::cross(face.normal, face.tangent);
const float width = glm::dot(glm::abs(face.tangent), size);
const float height = glm::dot(glm::abs(bitangent), size);
const auto first = static_cast<std::uint32_t>(mesh.vertices.size());
for (const glm::vec2& corner : corners) {
// v runs against the bitangent so images are upright on the side faces (w = -1 to match)
mesh.vertices.push_back(CPUMesh::Vertex{
.position = (face.normal + face.tangent * corner.x + bitangent * corner.y) * size * 0.5f,
.uv_x = (corner.x + 1.0f) * 0.5f * width,
.normal = face.normal,
.uv_y = (1.0f - corner.y) * 0.5f * height,
.tangent = glm::vec4{face.tangent, -1.0f},
});
}
for (const std::uint32_t index : {0u, 1u, 2u, 0u, 2u, 3u}) {
mesh.indices.push_back(first + index);
}
}
return mesh;
}
namespace {
constexpr std::string_view PrimitiveScheme = "prim://";
constexpr std::string_view BoxPrefix = "prim://box?size=";
}
std::string PrimitiveMeshes::BoxKey(const glm::vec3& size)
{
return std::format("{}{},{},{}", BoxPrefix, size.x, size.y, size.z);
}
bool PrimitiveMeshes::IsPrimitiveKey(std::string_view key)
{
return key.starts_with(PrimitiveScheme);
}
std::string_view PrimitiveMeshes::StripDuplicateSuffix(std::string_view key)
{
// MeshCache appends "#N" when the same key is added more than once
const auto separator = key.rfind('#');
if (separator == std::string_view::npos || separator + 1 >= key.size()) {
return key;
}
for (const char c : key.substr(separator + 1)) {
if (c < '0' || c > '9') {
return key;
}
}
return key.substr(0, separator);
}
std::optional<CPUMesh> PrimitiveMeshes::MakeFromKey(std::string_view key)
{
key = StripDuplicateSuffix(key);
if (!key.starts_with(BoxPrefix)) {
return std::nullopt;
}
std::string_view rest = key.substr(BoxPrefix.size());
glm::vec3 size{0.0f};
for (int i = 0; i < 3; ++i) {
const char* end = rest.data() + rest.size();
float value = 0.0f;
const auto [next, error] = std::from_chars(rest.data(), end, value);
if (error != std::errc{} || !(value > 0.0f)) {
return std::nullopt;
}
size[i] = value;
rest.remove_prefix(static_cast<std::size_t>(next - rest.data()));
if (i < 2) {
if (!rest.starts_with(',')) {
return std::nullopt;
}
rest.remove_prefix(1);
}
}
if (!rest.empty()) {
return std::nullopt;
}
return MakeBox(size);
}