118 lines
3.7 KiB
C++
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);
|
|
}
|