#pragma once #include "hd2d/GL.h" #include "hd2d/Texture.h" #include #include #include #include #include #include namespace hd2d { // Material of a glTF primitive. Materials whose name starts with "foliage" // get alpha testing, two-sided wrap lighting and sun translucency. struct ModelMaterial { std::string name; const Texture* texture = nullptr; // base color (sRGB); null = white glm::vec4 baseColor{1.0f}; bool alphaMask = false; float alphaCutoff = 0.5f; bool doubleSided = false; bool foliage = false; glm::vec3 emissive{0.0f}; // linear HDR (factor x strength) const Texture* emissiveTexture = nullptr; // multiplies `emissive`; null = white }; struct ModelPrimitive { GLuint vbo = 0, ibo = 0; GLsizei indexCount = 0; const ModelMaterial* material = nullptr; // owned by the ModelLibrary }; // Static mesh made of primitives (one per material). Vertex layout: position, // normal, uv, color (rgb = tint/AO, a = wind weight). Built from one root // node of a glTF file with the node's world transform baked in. struct Model { std::string name; std::vector primitives; glm::vec3 boundsMin{0.0f}, boundsMax{0.0f}; // Named attachment points from child empties of the glTF node (model space), // e.g. "fire_0" on the bridge braziers. std::unordered_map sockets; }; // Loads a .glb/.gltf once (textures shared by all its models) and exposes each // root node with a mesh as a named Model. class ModelLibrary { public: ModelLibrary() = default; ModelLibrary(const ModelLibrary&) = delete; ModelLibrary& operator=(const ModelLibrary&) = delete; ~ModelLibrary(); bool load(const std::string& path); const Model* get(const std::string& name) const; private: std::vector> textures_; std::vector> materials_; std::unordered_map> models_; }; // Per-instance placement: rotation about +Y, non-uniform scale, brightness tint. struct ModelInstance { glm::vec3 pos{0.0f}; float yaw = 0.0f; glm::vec3 scale{1.0f}; float tint = 1.0f; glm::vec3 toWorld(const glm::vec3& local) const { // matches mesh.vert glm::vec3 p = local * scale; float c = std::cos(yaw), s = std::sin(yaw); return pos + glm::vec3(c * p.x + s * p.z, p.y, -s * p.x + c * p.z); } }; // Instanced draw of one model. class ModelBatch { public: explicit ModelBatch(const Model* model = nullptr) : model(model) {} ModelBatch(const ModelBatch&) = delete; ModelBatch& operator=(const ModelBatch&) = delete; ~ModelBatch(); void upload(); // call after editing `instances` // Draws primitive `i` (the renderer binds its material first). void drawPrimitive(size_t i) const; const Model* model = nullptr; std::vector instances; bool castShadows = true; bool inReflections = true; float windScale = 1.0f; // multiplies the per-vertex wind weight // Strength of the contact shadow baked into the ground under each instance // (see GroundOcclusion); 0 disables. Its size follows the model's footprint. float groundOcclusion = 0.0f; // Dither this model away where it hides the player (tall scenery like // trees). Turn off for low props the camera sees over (bridges, rocks). bool occluderFade = true; private: std::vector vaos_; GLuint instanceVbo_ = 0; size_t capacity_ = 0; GLsizei count_ = 0; }; } // namespace hd2d