110 lines
3.6 KiB
C++
110 lines
3.6 KiB
C++
#pragma once
|
|
#include "hd2d/GL.h"
|
|
#include "hd2d/Texture.h"
|
|
|
|
#include <glm/glm.hpp>
|
|
#include <cmath>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
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<ModelPrimitive> 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<std::string, glm::vec3> 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<std::unique_ptr<Texture>> textures_;
|
|
std::vector<std::unique_ptr<ModelMaterial>> materials_;
|
|
std::unordered_map<std::string, std::unique_ptr<Model>> 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<ModelInstance> 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<GLuint> vaos_;
|
|
GLuint instanceVbo_ = 0;
|
|
size_t capacity_ = 0;
|
|
GLsizei count_ = 0;
|
|
};
|
|
|
|
} // namespace hd2d
|