//SPDX-License-Identifier: LGPL-3.0-only //SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® export module Crafter.Asset:GLB; import :Texture; import std; import Crafter.Math; namespace fs = std::filesystem; // glTF 2.0 binary (.glb) loading: the node hierarchy, every mesh's // triangle primitives with their vertex streams, skins, materials, textures // and decoded images, cameras, and the lights of KHR_lights_punctual. The JSON // chunk stays available as text, for the extensions this loader doesn't // interpret (VRMC_springBone, ...), and so do the `extras` of the scene, // nodes, materials and lights (Blender writes custom properties there); // parsing those is the caller's business, which keeps the JSON library out of // this interface and out of everything that imports Crafter.Asset. // // Errors in the file come back as the error of the std::expected rather than // through Compression::Fatal: a GLB is user content, and a bad one shouldn't // take the program down. export namespace Crafter { struct GlbRGBA8 { std::uint8_t r, g, b, a; }; struct GlbNode { std::string name; std::int32_t parent = -1; std::vector children; // The local transform, as TRS unless the file gives a matrix. The // matrix is the 4x4 row-vector form (translation in row 3), which is // glTF's column-major array copied into the rows unchanged. Vector translation = Vector(0, 0, 0); Vector rotation = Vector(0, 0, 0, 1); Vector scale = Vector(1, 1, 1); bool hasMatrix = false; MatrixRowMajor matrix = MatrixRowMajor::Identity(); std::int32_t mesh = -1; std::int32_t skin = -1; std::int32_t camera = -1; // A light of KHR_lights_punctual: GlbAsset::lights. std::int32_t light = -1; // The node's extras as JSON text, or empty. std::string extras; }; // A camera looks down its node's -Z, with +Y up. struct GlbCamera { std::string name; bool orthographic = false; // Perspective: the vertical field of view in radians, and the aspect // ratio, or 0 when the file leaves it to the viewport. float yfov = 0.0f; float aspectRatio = 0.0f; // Orthographic: half the view's width and height. float xmag = 0.0f; float ymag = 0.0f; float znear = 0.0f; // 0 for a perspective camera without a far plane. float zfar = 0.0f; }; enum class GlbLightType : std::uint8_t { Directional, Point, Spot, }; // A KHR_lights_punctual light. It sits at its node's origin; a // directional or spot light shines down the node's -Z. struct GlbLight { std::string name; GlbLightType type = GlbLightType::Point; std::array color = {1, 1, 1}; // Candela for a point or spot light, lux for a directional one. float intensity = 1.0f; // Where its light ends, or 0 when it goes on with distance's falloff. float range = 0.0f; // A spot light's cone, from its axis, in radians. float innerConeAngle = 0.0f; float outerConeAngle = std::numbers::pi_v / 4.0f; // The light's extras as JSON text, or empty. std::string extras; }; // One triangle list. Streams the primitive doesn't have stay empty; // otherwise each has one entry per vertex. struct GlbPrimitive { std::vector> positions; std::vector> normals; // xyz plus the bitangent's handedness in w. Computed from the UVs when // the file has none but has normals and TEXCOORD_0. std::vector> tangents; std::vector> uv0; std::vector> joints; // Normalized to sum to 1, which the file is meant to do and often // only nearly does. std::vector> weights; // 0..n-1 when the primitive isn't indexed. std::vector indices; std::int32_t material = -1; }; struct GlbMesh { std::string name; std::vector primitives; }; struct GlbSkin { std::string name; std::vector joints; // One per joint, in the 4x4 row-vector form like GlbNode::matrix; // identity when the file leaves them out. std::vector> inverseBindMatrices; std::int32_t skeleton = -1; }; enum class GlbAlphaMode : std::uint8_t { Opaque, Mask, Blend, }; struct GlbTextureRef { std::int32_t texture = -1; std::uint32_t texCoord = 0; // normalTexture's scale or occlusionTexture's strength. float scale = 1.0f; }; struct GlbMaterial { std::string name; std::array baseColorFactor = {1, 1, 1, 1}; float metallicFactor = 1.0f; float roughnessFactor = 1.0f; std::array emissiveFactor = {0, 0, 0}; GlbTextureRef baseColorTexture; GlbTextureRef metallicRoughnessTexture; GlbTextureRef normalTexture; GlbTextureRef occlusionTexture; GlbTextureRef emissiveTexture; GlbAlphaMode alphaMode = GlbAlphaMode::Opaque; float alphaCutoff = 0.5f; bool doubleSided = false; // KHR_materials_emissive_strength: emissiveFactor's multiplier. float emissiveStrength = 1.0f; // KHR_materials_transmission: how much light passes through the // surface, times the texture's red channel. float transmissionFactor = 0.0f; GlbTextureRef transmissionTexture; // KHR_materials_ior. float ior = 1.5f; // The material's extras as JSON text, or empty. std::string extras; }; // The OpenGL filter and wrap enums, as in the file; 0 when unspecified. struct GlbSampler { std::uint32_t magFilter = 0; std::uint32_t minFilter = 0; std::uint32_t wrapS = 10497; std::uint32_t wrapT = 10497; }; struct GlbTexture { std::int32_t image = -1; std::int32_t sampler = -1; }; struct GlbAsset { std::vector nodes; // The root nodes of the default scene. std::vector sceneRoots; std::vector meshes; std::vector skins; std::vector materials; std::vector textures; std::vector samplers; std::vector> images; std::vector cameras; std::vector lights; // The default scene's extras as JSON text, or empty. std::string extras; // The JSON chunk, verbatim. std::string json; static std::expected Load(const fs::path& path); static std::expected Load(std::span glb); }; }