Каков хороший дизайн для этой ситуации? - PullRequest
2 голосов
/ 17 февраля 2012

Я делаю базовый движок рендеринга.

Чтобы позволить движку рендеринга работать со всеми видами геометрии, я создал этот класс:

class Geometry
{
protected:
ID3D10Buffer* m_pVertexBuffer;
ID3D10Buffer* m_pIndexBuffer;

public:
[...]
};

Теперь я бы хотелпользователь сможет создавать свою собственную геометрию, наследуя от этого класса.Итак, давайте предположим, что пользователь сделал class Cube : public Geometry Пользователь должен будет создать вершинный буфер и индексный буфер при инициализации.

Это проблема, так как он будет воссоздавать вершинный буфер и индексный буфер каждый раз, когда создается новый объект Cube.,На производный класс должен быть только один экземпляр vertexbuffer и indexbuffer.Либо так, либо совершенно другой дизайн.

Решением может быть создание отдельного static ID3D10Buffer* для наследующего класса и установка указателей унаследованного класса, равных указателям в конструкторе.

Но для этого потребуется статический метод, такой как static void CreateBuffers(), который пользователь должен будет явно вызывать один раз в своем приложении для каждого типа, который он решает сделать, который наследуется от Geometry.Это не похоже на хороший дизайн.

Что является хорошим решением этой проблемы?

Ответы [ 3 ]

4 голосов
/ 17 февраля 2012

Вы должны отделить концепцию экземпляра от концепции сетки.Это означает, что вы создаете одну версию Geometry для куба, которая представляет вершину и индексный буфер для куба.

Затем вы вводите новый класс с именем GeometryInstance, который содержит матрицу преобразования.Этот класс также должен иметь указатель / ссылку на геометрию.Теперь вы можете создавать новые экземпляры вашей геометрии, создавая GeometryInstances, которые все ссылаются на один и тот же объект Geometry, не дублируя память или работая при создании нового блока.Класс Mesh, как в вашем комментарии, ваш класс Mesh должен выглядеть примерно так:

class Mesh {
  private:
  Matrix4x4 transformation;
  Geometry* geometry;
  public:
  Mesh(const Matrix4x4 _t, Geometry* _g) : transformation(_t), geometry(_g) {}
}

Теперь при создании вашей сцены вы хотите делать такие вещи

...
std::vector<Mesh> myMeshes;
// OrdinaryGeometry is a class inheriting Geometry 
OrdinaryGeometry* geom = new OrdinaryGeometry(...);
for(int i = 0; i < ordinaryGeomCount; ++i) {
  // generateTransform is a function that generates some 
  // transformation Matrix given an index, just as an example
  myMeshes.push_back(Mesh(generateTransform(i), geom);
}
// SpecialGeometry is a class inheriting Geometry with a different
// set of vertices and indices
SuperSpecialGeometry* specialGeom = new SuperSpecialGeometry(...);
for(int i = 0; i < specialGeomCount; ++i) {
  myMeshes.push_back(Mesh(generateTransform(i), specialGeom);
}

// Now render all instances
for(int i = 0; i < myMeshes.size(); ++i) {
  render(myMeshes[i]);
}

Обратите внимание, что у насдва объекта Geometry, которые совместно используются несколькими сетками.В идеале они должны быть пересчитаны с использованием std :: shared_ptr или чего-то подобного, но это выходит за рамки вопроса.

2 голосов
/ 21 февраля 2012

Какой смысл подразделять геометрию в вашем примере с кубом? Куб - это просто экземпляр Geometry, который имеет определенный набор треугольников и индексов. Не будет никакой разницы между классом Cube и классом Sphere, за исключением того, что они заполняют свои буферы треугольника / индекса разными данными. Таким образом, здесь важны сами данные. Вам нужен способ, позволяющий пользователю предоставлять вашему движку различные данные формы, а затем каким-то образом ссылаться на эти данные после его создания.

Для предоставления данных формы у вас есть два варианта. Вы можете решить, сохранять ли детали Geometry частными, и предоставить некоторый интерфейс, который принимает необработанные данные, такие как строки из файла, или массив с плавающей точкой, заполненный некоторой пользовательской функцией, создает экземпляр Geometry для этих данных и затем дает Пользователь использует некоторый дескриптор этого экземпляра (или позволяет пользователю указать дескриптор). Или вы можете создать некоторый класс, такой как GeometryInfo, который имеет методы addTriangle, addVertex и т. Д., Которые пользователь сам заполняет, а затем иметь некоторую функцию, которая принимает GeometryInfo, создает экземпляр Geometry для этих данных и затем снова дает пользователю некоторый дескриптор.

В обеих ситуациях вам необходимо предоставить некоторый интерфейс, который позволит пользователю сказать «вот некоторые данные, сделать из них что-то и дать им некоторую обработку. Как минимум, это будет иметь функцию, как я описал. Вам нужно будет поддерживать сопоставить где-нибудь созданные экземпляры Geometry в вашем движке, чтобы вы применяли один экземпляр к правилу формы и могли связать то, что хочет пользователь ("Ball", "Cube") с тем, что нужно вашему движку (Geometry с заполненными буферами ).

Теперь о ручке. Я бы либо позволил пользователю связать данные с именем, например «Ball», либо вернул некоторое целое число, которое пользователь затем связал бы с определенным экземпляром «Ball». Таким образом, когда вы создаете свой класс Rocket, пользователь может затем запросить экземпляр «Ball» из вашего движка, различные другие объекты могут использовать «Ball», и все в порядке, потому что они просто хранят ручки, а не сам мяч. Я бы не советовал хранить указатель на фактический экземпляр Geometry. Сетка не владеет геометрией, потому что она может делиться ею с другими сетками. Ему не нужен доступ к элементам геометрии, потому что средство визуализации обрабатывает грубую работу. Так что это ненужная зависимость. Единственная причина - скорость, но использование хеширования для ваших ручек будет работать так же хорошо.

Теперь несколько примеров:

Предоставление данных формы:

//option one
engine->CreateGeometryFromFile("ball.txt", "Ball");

//option two
GeometryInfo ball;

ball.addTriangle(0, 1, 0, 1);
ball.addTriangle(...);
...

engine->CreateGeometryFromInfo(ball, "Ball");

Ссылаясь на эти данные с помощью дескриптора:

class Drawable
{
    std::string shape;
    Matrix transform;
};

class Rocket : public Drawable
{
    Rocket() { shape = "Ball";}
    //other stuff here for physics maybe
};

class BallShapedEnemy : public Drawable
{

    BallShapedEnemy() { shape = "Ball";}
    ...
}

...

...in user's render loop...

for each (drawable in myDrawables)
{
    engine->Render(drawable.GetShape(), drawable.GetTransform());
}

Теперь наличие отдельного класса для каждого отдельного игрового объекта, такого как Rocket, является дискуссионным и является предметом другого вопроса, я просто сделал его похожим на ваш пример из комментария.

1 голос
/ 17 февраля 2012

Это может быть неаккуратный способ сделать это, но не могли бы вы просто сделать синглтон?

#pragma once
#include <iostream>
#define GEOM Geometry::getInstance()

class Geometry
{
protected:
    static Geometry* ptrInstance;
    static Geometry* getInstance();

    float* m_pVertexBuffer;
    float* m_pIndexBuffer;
public:
    Geometry(void);
    ~Geometry(void);

    void callGeom();
};

#include "Geometry.h"

Geometry* Geometry::ptrInstance = 0;

Geometry::Geometry(void)
{
}


Geometry::~Geometry(void)
{
}

Geometry* Geometry::getInstance()
{
    if(ptrInstance == 0)
    {
        ptrInstance = new Geometry();
    }
    return ptrInstance;
}

void Geometry::callGeom()
{
    std::cout << "Call successful!" << std::endl;
}

Единственная проблема этого метода в том, что у вас будет только один объект Geometry, и я предполагаю, что вам может понадобиться более одного? Если нет, то это может быть полезно, но я думаю, что метод Лассераллана, вероятно, является гораздо лучшей реализацией того, что вы ищете.

...