#include #include #include #include #include CPUMesh PrimitiveMeshes::MakeBox(const glm::vec3& size) { struct Face { glm::vec3 normal; glm::vec3 tangent; }; constexpr std::array 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 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(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 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(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); }