Sto implementando un semplice sistema di rendering per un motore di gioco. Nel mio motore ho delle entità renderizzabili che hanno un componente Model (sto usando l'ereditarietà al posto di un ECS per il mio motore per ora, ma incapsulare un comportamento comune nei componenti renderà le cose più facili quando / se voglio transistionare in un componente basato architettura). Questa classe Model contiene una mesh (buffer di vertici e buffer di indice), trame e materiali.
Alcuni modelli di entità sono caricati da file (modelli 3D generati da applicazioni di modellazione), quindi passo semplicemente un modello nei loro costruttori, mentre altri hanno attributi di mesh e di vertici che sono "cablati" nella loro classe. Ad esempio una classe SkyBox dovrebbe essere codificata in questo modo:
// SkyBox.hpp
class SkyBox
{
public:
SkyBox(const wchar_t *cubeMapFilePath);
~SkyBox();
XMFLOAT4X4 const &GetWorldMatrix() const;
Model const &GetModel() const;
void SetRotation(XMFLOAT3 const &rotation);
static Model CreateSkyBox(const wchar_t *cubeMapFilePath); // static member function called from constructor
private:
Model mModel; // the member with no def-ctor
XMFLOAT3 mRotation;
mutable XMFLOAT4X4 mWorldMatrix;
mutable bool mDirtyFlag = true;
};
// SkyBox.cpp
#include "SkyBox.h"
SkyBox::SkyBox(const wchar_t *cubeMapFilePath) : mModel(CreateSkyBox(cubeMapFilePath)), mRotation(0.0f, 0.0f, 0.0f)
{
}
SkyBox::~SkyBox()
{
}
Model SkyBox::CreateSkyBox(const wchar_t *cubeMapFilePath)
{
Mesh mesh;
std::vector<XMFLOAT3> positions;
positions.push_back(XMFLOAT3(-0.5f, 0.5f, -0.5f));
positions.push_back(XMFLOAT3(0.5f, 0.5f, -0.5f));
positions.push_back(XMFLOAT3(0.5f, -0.5f, -0.5f));
positions.push_back(XMFLOAT3(-0.5f, -0.5f, -0.5f));
positions.push_back(XMFLOAT3(-0.5f, 0.5f, 0.5f));
positions.push_back(XMFLOAT3(0.5f, 0.5f, 0.5f));
positions.push_back(XMFLOAT3(0.5f, -0.5f, 0.5f));
positions.push_back(XMFLOAT3(-0.5f, -0.5f, 0.5f));
std::vector<unsigned int> indices{ 0, 1, 3, 3, 1, 2, 5, 4, 6, 6, 4, 7, 4, 0, 7, 7, 0, 3, 1, 5, 2, 2, 5, 6, 4, 5, 0, 0, 5, 1, 3, 2, 7, 7, 2, 6 };
mesh.LoadAttribute("POSITION", &positions[0], positions.size());
mesh.LoadIndexBuffer(indices);
std::vector<Texture> cubeMap;
Texture texture(Texture::CUBE_MAP);
texture.LoadCubeMap(cubeMapFilePath);
cubeMap.push_back(texture);
Material material = {};
return Model(mesh, cubeMap, material);
}
// .... other members
La mia classe Model non ha un costruttore predefinito, quindi quando definisco una classe che ha un componente model devo fornire un modo per costruirne uno nel costruttore della classe dell'entità, ma per costruire un modello ho bisogno prima di eseguire alcuni calcoli (posizioni dei vertici calcolati, normali ecc.).
Mi è venuta in mente questa soluzione, chiamando una funzione membro statico (privata) che esegue i calcoli necessari e restituisce un modello, con cui inizializzare il membro del modello nella classe. Un'alternativa sarebbe fornire alla classe Model un costruttore predefinito ed eseguire i calcoli nel costruttore dell'entità e quindi assegnarlo al membro del modello, ma non ha molto senso avere un modello vuoto.
Quindi, questa soluzione è un hack o è buona, o esiste un modello per questo tipo di problemi, o dovrei andare per il costruttore predefinito?
In realtà, dovevo assegnare alla classe Model un costruttore predefinito in quanto dovevo accedere ai membri dei dati durante la costruzione di modelli per altre classi (una classe Terrain, ad esempio, memorizza le altezze della griglia in una matrice membro dati per il rilevamento delle collisioni). Non so se è il modo giusto per farlo, ma è veloce e sporco e funziona.