//interface
class Compose : public IModule
{
public:
Compose(void);
~Compose(void);
double get(double,double,double,double);
double get(double,double,double);
double get(double,double);
Compose* setInput(IModule*);
Compose* setWrapper(std::function<double (double) >);
private:
std::function<double (double) > f_;
std::shared_ptr<IModule> basis_;
};
//implementation
Compose* Compose::setWrapper(std::function<double (double) > function)
{
f_ = function;
return this;
}
Compose* Compose::setInput(IModule* module)
{
basis_ = std::shared_ptr<IModule>(module);
return this;
}
double Compose::get(double x,double y,double z,double w)
{
return f_(basis_->get(x,y,z,w));
}
double Compose::get(double x,double y,double z)
{
return f_(basis_->get(x,y,z));
}
double Compose::get(double x,double y)
{
return f_(basis_->get(x,y));
}
If its not immediately obvious, its a module that basically takes another module and a lambda function, and when executed (call to get(x,y,z) ), passes the output of the module into the lambda function. A simple use case looks something like this:
Compose* sample = new Compose;
sample->setWrapper([](double n){ return std::cos(n); })->setInput(new Perlin);
Where get(0,0,0) for example would return 0 from the perlin function (always zero at integral inputs) and therefore 1 from the compose function calling cos().
It's a fairly typical infrastructure for noise libraries, except generalized a bit from the use of Lambdas. It's worked for me fairlly well, but I'd like to be able to extend it to operate on multiple modules at once. For example, a common operation is something like "blend", which takes two modules and lerps between their outputs by the output of a third module (ideally clamped to the range of [0,1]). It would be nice to be able to extend my existing functionality to call something like this:
//NOT REAL CODE
auto blend = [](double a, double b, double selector){ return a + selector*(b-a); };
Compose* sample = new Compose;
sample->setInput(new Perlin)->addInput(new Perlin)->addInput(new Perlin)->setWrapper(blend);
Unless there's some kind of crazy magic-bullet third option, I can see two ways to implement this sort of extension:
- Make the lambda accept a vector<double>. This lets me pass totally arbitrary functions (which is kinda nice), but makes the syntax for declaring the lambdas all kinds of weird and causes some minor bounds checking issues that can probably be worked out
- create overloads up to n (the number of arguments I care about. For example:
//interface snippetCompose* setWrapper(std::function<double (double) >);Compose* setWrapper(std::function<double (double,double) >);Compose* setWrapper(std::function<double (double,double,double) >);Compose* setWrapper(std::function<double (double,double,double,double) >);private:std::function<double (double) > f1_; //versus std::function<double(std::vector<double>)> f_;std::function<double (double,double) > f2_;std::function<double (double,double) > f3_;std::function<double (double,double) > f4_;std::vector<std::shared_ptr<IModule> > basis_; //has to be a vector in either implementation};//implementation snippetdouble Compose::get(double x,double y){if(f4) return f4_(basis[0]_->get(x,y), basis[1]_->get(x,y), basis[2]_->get(x,y), basis[3]_->get(x,y));else if(f3)//...continued}this has the advantage of being a little safer in terms of bounds checking and has cleaner lambda syntax, but isn't as straightforward to use and can't extend very far.
EDIT: Looks like the code box axed my whitespace. I think it's pretty easy code to read overall anyway, but that's unfair to say since it's my code. Sorry if that causes any issues.
Edited by SeraphLance, 23 October 2012 - 08:10 PM.






