What is the appropriate design model for this situation?

advertisements

I'm trying to implement a model(gfx) class but I can't seem to find a proper design for it.

What I have is this: [pseudocode]

class Material {
  public:
    virtual void SetPerFrameInfo(void* pData)=0;
    //[...]
}

class SpecificMaterial : public Material {
  public:
    void SetPerFrameInfo(void* pData);
    //[...]
}

void  SpecificMaterial::SetPerFrameInfo(void* pData) {
    ThisMaterialPerFrameInfoStruct* pInfo = (ThisMaterialPerFrameInfoStruct*)pData;
    //[...]
}

class Model {
  public:
    Model(Material* pMaterial){m_pMatirial = pMaterial;}
    void Draw();
  private:
    Material* m_pMaterial
    //[...]
}

void Model::Draw() {
    PerFrameInformation info;
    m_pMaterial->SetPerFrameInfo(&info);
}

How to use it:

Material* pMaterial = new SpecificMaterial();
Model someModel(pMaterial);

As you might see, I have two main problems:

1) The type of struct passed to SetPerFrameInfo() depends on the actual material, so how should I fill it in?

2) The Model class might need some other datamembers depending on the actual material, since for some materials I might decide to implement a timer, and for some I might not. It would be a memory waste to have datamembers in the Model class for all materials.

Please help, I can't seem to find any proper design pattern.


1.) I think the problem you are experience is due to trying to put too much in the base class. Since the Model needs to know what particular material type it is working with when it is calling the SetPerFrameInfo function, why not just give each Material-derived class its own Set..() function that takes the proper type instead of void*? Because the Material base class has no way to interact with its per-frame info (it is void*), then why generalize it into the base class?

2.) Just put the material-specific datamembers into the derived material classes. I assume each Material instance corresponds to a unique Model, so the material-specific information for the Model should be in its Material-derived class.

In response to your clarification:

Okay, I misunderstood the relationship between the two classes. Basically you want, in the ideal scenario, a set of Materials classes that don't know the details of a Model, and a set of Models that can work with any Material, without knowing the details of the Material. So you may want to apply a Rough material instance to six different models.

I don't think there's any question that, if each Material instance is shared by several Models and each Model instance knows nothing about the different Materials, a third per-Model object is needed to store the timers or whatever instance data is needed for drawing. So each Model receives its own AppliedMaterial object which points to a shared Material instance as well as the data members needed to apply the material to that model. There would have to be a different AppliedMaterial subclass for each Material type, storing the data values pertinent to each material. Example of using a third context object:

struct GlossMaterialContext
{
    Timer t;
    int someContextValue;
};

class GlossMaterial : Material
{
    void * NewApplicator() { return new GlossMaterialContext; }
    void FreeApplicator(void *applicator) { delete (GlossMaterialContext*)applicator; }
    void ApplyMaterial(void *applicator)
    {
        // Set up shader...
        // Apply texture...
    }
};

class 3DModel
{
    Material *material;
    void *materialContext;

    void SetMaterial(Material *m)
    {
        material = m;
        materialContext = m->NewApplicator();
    }
    void Draw()
    {
        material->ApplyMaterial(materialContext);
    }
};

It is not an incredibly clean technique. I always feel like dealing with void* pointers is a kludge. You could also have the 3rd context object be based on a class, like:

class AppliedGlossMaterial : AppliedMaterial

and:

class GlossMaterial : Material
{
    AppliedMaterial * NewApplicator() { return new AppliedGlossMaterial; }
    ...

If the datamembers depend on both the Model type and the Material type, then your models and materials are way too tightly coupled to make them into separate classes that are supposed to know nothing about each other. You would have to create subclasses of your Model: Glossy3DModel, Rough3DModel, etc.