191 lines
7.1 KiB
Text
191 lines
7.1 KiB
Text
|
|
//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<std::uint32_t> 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<float, 3, 0> translation = Vector<float, 3, 0>(0, 0, 0);
|
||
|
|
Vector<float, 4, 0> rotation = Vector<float, 4, 0>(0, 0, 0, 1);
|
||
|
|
Vector<float, 3, 0> scale = Vector<float, 3, 0>(1, 1, 1);
|
||
|
|
bool hasMatrix = false;
|
||
|
|
MatrixRowMajor<float, 4, 4, 1> matrix = MatrixRowMajor<float, 4, 4, 1>::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<float, 3> 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<float> / 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<Vector<float, 3, 3>> positions;
|
||
|
|
std::vector<Vector<float, 3, 3>> 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<Vector<float, 4, 4>> tangents;
|
||
|
|
std::vector<Vector<float, 2, 2>> uv0;
|
||
|
|
std::vector<std::array<std::uint16_t, 4>> joints;
|
||
|
|
// Normalized to sum to 1, which the file is meant to do and often
|
||
|
|
// only nearly does.
|
||
|
|
std::vector<std::array<float, 4>> weights;
|
||
|
|
// 0..n-1 when the primitive isn't indexed.
|
||
|
|
std::vector<std::uint32_t> indices;
|
||
|
|
std::int32_t material = -1;
|
||
|
|
};
|
||
|
|
|
||
|
|
struct GlbMesh {
|
||
|
|
std::string name;
|
||
|
|
std::vector<GlbPrimitive> primitives;
|
||
|
|
};
|
||
|
|
|
||
|
|
struct GlbSkin {
|
||
|
|
std::string name;
|
||
|
|
std::vector<std::uint32_t> joints;
|
||
|
|
// One per joint, in the 4x4 row-vector form like GlbNode::matrix;
|
||
|
|
// identity when the file leaves them out.
|
||
|
|
std::vector<MatrixRowMajor<float, 4, 4, 1>> 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<float, 4> baseColorFactor = {1, 1, 1, 1};
|
||
|
|
float metallicFactor = 1.0f;
|
||
|
|
float roughnessFactor = 1.0f;
|
||
|
|
std::array<float, 3> 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<GlbNode> nodes;
|
||
|
|
// The root nodes of the default scene.
|
||
|
|
std::vector<std::uint32_t> sceneRoots;
|
||
|
|
std::vector<GlbMesh> meshes;
|
||
|
|
std::vector<GlbSkin> skins;
|
||
|
|
std::vector<GlbMaterial> materials;
|
||
|
|
std::vector<GlbTexture> textures;
|
||
|
|
std::vector<GlbSampler> samplers;
|
||
|
|
std::vector<TextureAsset<GlbRGBA8>> images;
|
||
|
|
std::vector<GlbCamera> cameras;
|
||
|
|
std::vector<GlbLight> lights;
|
||
|
|
// The default scene's extras as JSON text, or empty.
|
||
|
|
std::string extras;
|
||
|
|
// The JSON chunk, verbatim.
|
||
|
|
std::string json;
|
||
|
|
|
||
|
|
static std::expected<GlbAsset, std::string> Load(const fs::path& path);
|
||
|
|
static std::expected<GlbAsset, std::string> Load(std::span<const std::byte> glb);
|
||
|
|
};
|
||
|
|
}
|