2013-06-23 10:25:42 -06:00
|
|
|
#ifndef RENDERER_LIGHT_INCLUDED
|
|
|
|
#define RENDERER_LIGHT_INCLUDED
|
|
|
|
#include "renderer_opengl.hpp"
|
|
|
|
#include "Types.h"
|
2013-06-25 16:18:26 -06:00
|
|
|
#include <tuple>
|
2013-06-29 11:55:07 -06:00
|
|
|
#include <stack>
|
|
|
|
#include <memory>
|
2013-07-02 13:06:39 -06:00
|
|
|
#include <unordered_map>
|
|
|
|
// we are not using boost so let's cheat:
|
|
|
|
template <class T>
|
|
|
|
inline void hash_combine(std::size_t & seed, const T & v)
|
|
|
|
{
|
|
|
|
std::hash<T> hasher;
|
|
|
|
seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace std
|
|
|
|
{
|
|
|
|
template<typename S, typename T> struct hash<pair<S, T>>
|
|
|
|
{
|
|
|
|
inline size_t operator()(const pair<S, T> & v) const
|
|
|
|
{
|
|
|
|
size_t seed = 0;
|
|
|
|
::hash_combine(seed, v.first);
|
|
|
|
::hash_combine(seed, v.second);
|
|
|
|
return seed;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
template<typename S, typename T,typename V> struct hash<tuple<S, T, V>>
|
|
|
|
{
|
|
|
|
inline size_t operator()(const tuple<S, T, V> & v) const
|
|
|
|
{
|
|
|
|
size_t seed = 0;
|
|
|
|
::hash_combine(seed,get<0>(v));
|
|
|
|
::hash_combine(seed,get<1>(v));
|
|
|
|
::hash_combine(seed,get<2>(v));
|
|
|
|
return seed;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
// now we can hash pairs and tuples
|
|
|
|
|
2013-06-24 15:59:32 -06:00
|
|
|
#include "modules/MapCache.h"
|
2013-06-29 11:55:07 -06:00
|
|
|
bool isInRect(const df::coord2d& pos,const DFHack::rect2d& rect);
|
2013-06-23 10:25:42 -06:00
|
|
|
struct renderer_light : public renderer_wrap {
|
|
|
|
private:
|
2014-01-25 18:26:41 -07:00
|
|
|
float light_adaptation;
|
|
|
|
rgbf adapt_to_light(const rgbf& light)
|
|
|
|
{
|
2014-02-14 02:50:12 -07:00
|
|
|
const float influence=0.0001f;
|
2014-01-25 18:26:41 -07:00
|
|
|
const float max_adapt=1;
|
|
|
|
const float min_adapt=0;
|
|
|
|
float intensity=(light.r+light.g+light.b)/3.0;
|
|
|
|
light_adaptation=intensity*influence+light_adaptation*(1-influence);
|
|
|
|
float delta=light_adaptation-intensity;
|
2015-02-14 20:53:06 -07:00
|
|
|
|
2014-01-25 18:26:41 -07:00
|
|
|
rgbf ret;
|
|
|
|
ret.r=light.r-delta;
|
|
|
|
ret.g=light.g-delta;
|
|
|
|
ret.b=light.b-delta;
|
|
|
|
return ret;
|
|
|
|
//if light_adaptation/intensity~1 then draw 1,1,1 (i.e. totally adapted)
|
|
|
|
/*
|
|
|
|
1. adapted -> 1,1,1 (full bright everything okay) delta=0 multiplier=?
|
|
|
|
2. light adapted, real=dark -> darker delta>0 multiplier<1
|
|
|
|
3. dark adapted, real=light -> lighter delta<0 multiplier>1
|
|
|
|
*/
|
2015-02-14 20:53:06 -07:00
|
|
|
//if light_adaptation/intensity!=0 then draw
|
2014-01-25 18:26:41 -07:00
|
|
|
|
|
|
|
}
|
2013-06-23 10:25:42 -06:00
|
|
|
void colorizeTile(int x,int y)
|
|
|
|
{
|
|
|
|
const int tile = x*(df::global::gps->dimy) + y;
|
|
|
|
old_opengl* p=reinterpret_cast<old_opengl*>(parent);
|
|
|
|
float *fg = p->fg + tile * 4 * 6;
|
|
|
|
float *bg = p->bg + tile * 4 * 6;
|
|
|
|
float *tex = p->tex + tile * 2 * 6;
|
2014-01-25 18:26:41 -07:00
|
|
|
rgbf light=lightGrid[tile];//for light adaptation: rgbf light=adapt_to_light(lightGrid[tile]);
|
|
|
|
|
|
|
|
for (int i = 0; i < 6; i++) { //oh how sse would do wonders here, or shaders...
|
2013-06-23 10:25:42 -06:00
|
|
|
*(fg++) *= light.r;
|
|
|
|
*(fg++) *= light.g;
|
|
|
|
*(fg++) *= light.b;
|
|
|
|
*(fg++) = 1;
|
|
|
|
|
|
|
|
*(bg++) *= light.r;
|
|
|
|
*(bg++) *= light.g;
|
|
|
|
*(bg++) *= light.b;
|
|
|
|
*(bg++) = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
void reinitLightGrid(int w,int h)
|
|
|
|
{
|
|
|
|
tthread::lock_guard<tthread::fast_mutex> guard(dataMutex);
|
2013-07-03 01:44:23 -06:00
|
|
|
lightGrid.resize(w*h,rgbf(1,1,1));
|
2013-06-23 10:25:42 -06:00
|
|
|
}
|
|
|
|
void reinitLightGrid()
|
|
|
|
{
|
|
|
|
reinitLightGrid(df::global::gps->dimy,df::global::gps->dimx);
|
|
|
|
}
|
2015-02-14 20:53:06 -07:00
|
|
|
|
2013-06-23 10:25:42 -06:00
|
|
|
public:
|
|
|
|
tthread::fast_mutex dataMutex;
|
2013-06-30 06:05:59 -06:00
|
|
|
std::vector<rgbf> lightGrid;
|
2014-01-25 18:26:41 -07:00
|
|
|
renderer_light(renderer* parent):renderer_wrap(parent),light_adaptation(1)
|
2013-06-23 10:25:42 -06:00
|
|
|
{
|
|
|
|
reinitLightGrid();
|
|
|
|
}
|
2015-02-14 20:53:06 -07:00
|
|
|
virtual void update_tile(int32_t x, int32_t y) {
|
2013-06-23 10:25:42 -06:00
|
|
|
renderer_wrap::update_tile(x,y);
|
|
|
|
tthread::lock_guard<tthread::fast_mutex> guard(dataMutex);
|
|
|
|
colorizeTile(x,y);
|
|
|
|
};
|
2015-02-14 20:53:06 -07:00
|
|
|
virtual void update_all() {
|
2013-06-23 10:25:42 -06:00
|
|
|
renderer_wrap::update_all();
|
|
|
|
tthread::lock_guard<tthread::fast_mutex> guard(dataMutex);
|
|
|
|
for (int x = 0; x < df::global::gps->dimx; x++)
|
|
|
|
for (int y = 0; y < df::global::gps->dimy; y++)
|
|
|
|
colorizeTile(x,y);
|
|
|
|
};
|
2015-02-14 20:53:06 -07:00
|
|
|
virtual void grid_resize(int32_t w, int32_t h) {
|
2013-06-23 10:25:42 -06:00
|
|
|
renderer_wrap::grid_resize(w,h);
|
|
|
|
reinitLightGrid(w,h);
|
|
|
|
};
|
|
|
|
virtual void resize(int32_t w, int32_t h) {
|
|
|
|
renderer_wrap::resize(w,h);
|
2013-06-23 12:29:03 -06:00
|
|
|
reinitLightGrid();
|
2013-06-23 10:25:42 -06:00
|
|
|
}
|
2014-05-11 05:19:02 -06:00
|
|
|
virtual void set_fullscreen()
|
|
|
|
{
|
|
|
|
renderer_wrap::set_fullscreen();
|
|
|
|
reinitLightGrid();
|
|
|
|
}
|
|
|
|
virtual void zoom(df::zoom_commands z)
|
|
|
|
{
|
|
|
|
renderer_wrap::zoom(z);
|
|
|
|
reinitLightGrid();
|
|
|
|
}
|
2013-06-23 10:25:42 -06:00
|
|
|
};
|
|
|
|
class lightingEngine
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
lightingEngine(renderer_light* target):myRenderer(target){}
|
2013-06-30 13:03:07 -06:00
|
|
|
virtual ~lightingEngine(){}
|
2013-06-23 10:25:42 -06:00
|
|
|
virtual void reinit()=0;
|
|
|
|
virtual void calculate()=0;
|
|
|
|
|
|
|
|
virtual void updateWindow()=0;
|
2014-02-14 02:50:12 -07:00
|
|
|
virtual void preRender()=0;
|
2013-06-23 10:25:42 -06:00
|
|
|
|
2013-06-24 09:18:57 -06:00
|
|
|
virtual void loadSettings()=0;
|
2013-06-25 11:36:53 -06:00
|
|
|
virtual void clear()=0;
|
2015-02-14 20:53:06 -07:00
|
|
|
|
2013-06-26 13:05:22 -06:00
|
|
|
virtual void setHour(float h)=0;
|
2013-06-28 14:34:47 -06:00
|
|
|
virtual void debug(bool enable)=0;
|
2013-06-23 10:25:42 -06:00
|
|
|
protected:
|
|
|
|
renderer_light* myRenderer;
|
|
|
|
};
|
|
|
|
struct lightSource
|
|
|
|
{
|
2013-06-30 06:05:59 -06:00
|
|
|
rgbf power;
|
2013-06-23 10:25:42 -06:00
|
|
|
int radius;
|
2013-06-23 11:45:05 -06:00
|
|
|
bool flicker;
|
|
|
|
lightSource():power(0,0,0),radius(0),flicker(false)
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
2013-06-30 06:05:59 -06:00
|
|
|
lightSource(rgbf power,int radius);
|
2013-06-23 11:45:05 -06:00
|
|
|
float powerSquared()const
|
|
|
|
{
|
|
|
|
return power.r*power.r+power.g*power.g+power.b*power.b;
|
|
|
|
}
|
|
|
|
void combine(const lightSource& other);
|
|
|
|
|
2013-06-23 10:25:42 -06:00
|
|
|
};
|
2013-06-24 09:18:57 -06:00
|
|
|
struct matLightDef
|
|
|
|
{
|
|
|
|
bool isTransparent;
|
2013-06-30 06:05:59 -06:00
|
|
|
rgbf transparency;
|
2013-06-24 09:18:57 -06:00
|
|
|
bool isEmiting;
|
|
|
|
bool flicker;
|
2013-06-30 06:05:59 -06:00
|
|
|
rgbf emitColor;
|
2013-06-24 09:18:57 -06:00
|
|
|
int radius;
|
2018-04-06 13:17:34 -06:00
|
|
|
matLightDef():isTransparent(false),transparency(0,0,0),isEmiting(false),emitColor(0,0,0),radius(0){}
|
|
|
|
matLightDef(rgbf transparency,rgbf emit,int rad):isTransparent(true),transparency(transparency),
|
|
|
|
isEmiting(true),emitColor(emit),radius(rad){}
|
|
|
|
matLightDef(rgbf emit,int rad):isTransparent(false),transparency(0,0,0),isEmiting(true),emitColor(emit),radius(rad){}
|
|
|
|
matLightDef(rgbf transparency):isTransparent(true),transparency(transparency),isEmiting(false){}
|
2013-06-24 09:18:57 -06:00
|
|
|
lightSource makeSource(float size=1) const
|
|
|
|
{
|
2013-06-26 11:19:30 -06:00
|
|
|
if(size>0.999 && size<1.001)
|
|
|
|
return lightSource(emitColor,radius);
|
|
|
|
else
|
|
|
|
return lightSource(emitColor*size,radius*size);//todo check if this is sane
|
2013-06-24 09:18:57 -06:00
|
|
|
}
|
|
|
|
};
|
2013-06-25 16:18:26 -06:00
|
|
|
struct buildingLightDef
|
|
|
|
{
|
|
|
|
matLightDef light;
|
|
|
|
bool poweredOnly;
|
|
|
|
bool useMaterial;
|
2013-06-26 11:19:30 -06:00
|
|
|
float thickness;
|
|
|
|
float size;
|
2013-06-28 12:17:54 -06:00
|
|
|
buildingLightDef():poweredOnly(false),useMaterial(true),thickness(1.0f),size(1.0f){}
|
2013-06-25 16:18:26 -06:00
|
|
|
};
|
2013-09-22 05:50:03 -06:00
|
|
|
struct itemLightDef
|
|
|
|
{
|
2015-02-14 20:53:06 -07:00
|
|
|
matLightDef light;
|
2013-09-22 05:50:03 -06:00
|
|
|
bool haul;
|
|
|
|
bool equiped;
|
|
|
|
bool onGround;
|
|
|
|
bool inBuilding;
|
|
|
|
bool inContainer;
|
|
|
|
bool useMaterial;
|
|
|
|
itemLightDef():haul(true),equiped(true),onGround(true),inBuilding(false),inContainer(false),useMaterial(true){}
|
|
|
|
};
|
|
|
|
struct creatureLightDef
|
|
|
|
{
|
|
|
|
matLightDef light;
|
|
|
|
|
|
|
|
};
|
2013-06-29 11:55:07 -06:00
|
|
|
class lightThread;
|
|
|
|
class lightingEngineViewscreen;
|
|
|
|
class lightThreadDispatch
|
|
|
|
{
|
|
|
|
lightingEngineViewscreen *parent;
|
|
|
|
public:
|
|
|
|
DFHack::rect2d viewPort;
|
|
|
|
|
|
|
|
std::vector<std::unique_ptr<lightThread> > threadPool;
|
|
|
|
std::vector<lightSource>& lights;
|
|
|
|
|
|
|
|
tthread::mutex occlusionMutex;
|
|
|
|
tthread::condition_variable occlusionDone; //all threads wait for occlusion to finish
|
2013-06-30 13:03:07 -06:00
|
|
|
bool occlusionReady;
|
2013-06-29 11:55:07 -06:00
|
|
|
tthread::mutex unprocessedMutex;
|
|
|
|
std::stack<DFHack::rect2d> unprocessed; //stack of parts of map where lighting is not finished
|
2013-06-30 06:05:59 -06:00
|
|
|
std::vector<rgbf>& occlusion;
|
2014-02-14 02:50:12 -07:00
|
|
|
int& num_diffusion;
|
|
|
|
|
2013-06-29 11:55:07 -06:00
|
|
|
tthread::mutex writeLock; //mutex for lightMap
|
2013-06-30 06:05:59 -06:00
|
|
|
std::vector<rgbf>& lightMap;
|
2013-06-29 11:55:07 -06:00
|
|
|
|
|
|
|
tthread::condition_variable writesDone;
|
|
|
|
int writeCount;
|
|
|
|
|
|
|
|
lightThreadDispatch(lightingEngineViewscreen* p);
|
2013-06-30 13:03:07 -06:00
|
|
|
~lightThreadDispatch();
|
2013-06-29 11:55:07 -06:00
|
|
|
void signalDoneOcclusion();
|
|
|
|
void shutdown();
|
|
|
|
void waitForWrites();
|
|
|
|
|
|
|
|
int getW();
|
|
|
|
int getH();
|
|
|
|
void start(int count);
|
|
|
|
};
|
|
|
|
class lightThread
|
|
|
|
{
|
2013-06-30 06:05:59 -06:00
|
|
|
std::vector<rgbf> canvas;
|
2013-06-29 11:55:07 -06:00
|
|
|
lightThreadDispatch& dispatch;
|
|
|
|
DFHack::rect2d myRect;
|
|
|
|
void work(); //main light calculation function
|
|
|
|
void combine(); //combine existing canvas into global lightmap
|
|
|
|
public:
|
|
|
|
tthread::thread *myThread;
|
|
|
|
bool isDone; //no mutex, because bool is atomic
|
|
|
|
lightThread(lightThreadDispatch& dispatch);
|
|
|
|
~lightThread();
|
|
|
|
void run();
|
|
|
|
private:
|
|
|
|
void doLight(int x,int y);
|
2014-02-14 02:50:12 -07:00
|
|
|
void doRay(const rgbf& power,int cx,int cy,int tx,int ty,int num_diffuse);
|
2013-06-30 06:05:59 -06:00
|
|
|
rgbf lightUpCell(rgbf power,int dx,int dy,int tx,int ty);
|
2013-06-29 11:55:07 -06:00
|
|
|
};
|
2013-06-23 10:25:42 -06:00
|
|
|
class lightingEngineViewscreen:public lightingEngine
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
lightingEngineViewscreen(renderer_light* target);
|
2015-02-14 20:53:06 -07:00
|
|
|
~lightingEngineViewscreen();
|
2013-06-23 10:25:42 -06:00
|
|
|
void reinit();
|
|
|
|
void calculate();
|
|
|
|
|
|
|
|
void updateWindow();
|
2014-02-14 02:50:12 -07:00
|
|
|
void preRender();
|
2013-06-24 09:18:57 -06:00
|
|
|
void loadSettings();
|
2013-06-25 11:36:53 -06:00
|
|
|
void clear();
|
2013-06-28 14:34:47 -06:00
|
|
|
|
|
|
|
void debug(bool enable){doDebug=enable;};
|
2013-06-23 10:25:42 -06:00
|
|
|
private:
|
2014-02-14 02:50:12 -07:00
|
|
|
void fixAdvMode(int mode);
|
2013-06-24 15:59:32 -06:00
|
|
|
df::coord2d worldToViewportCoord(const df::coord2d& in,const DFHack::rect2d& r,const df::coord2d& window2d) ;
|
2015-02-14 20:53:06 -07:00
|
|
|
|
2013-06-24 09:18:57 -06:00
|
|
|
|
2013-06-24 15:59:32 -06:00
|
|
|
void doSun(const lightSource& sky,MapExtras::MapCache& map);
|
|
|
|
void doOcupancyAndLights();
|
2013-06-30 06:05:59 -06:00
|
|
|
rgbf propogateSun(MapExtras::Block* b, int x,int y,const rgbf& in,bool lastLevel);
|
|
|
|
void doRay(std::vector<rgbf> & target, rgbf power,int cx,int cy,int tx,int ty);
|
2013-06-23 10:25:42 -06:00
|
|
|
void doFovs();
|
2015-02-14 20:53:06 -07:00
|
|
|
void doLight(std::vector<rgbf> & target, int index);
|
2013-06-30 06:05:59 -06:00
|
|
|
rgbf lightUpCell(std::vector<rgbf> & target, rgbf power,int dx,int dy,int tx,int ty);
|
2013-06-23 11:45:05 -06:00
|
|
|
bool addLight(int tileId,const lightSource& light);
|
2013-06-30 06:05:59 -06:00
|
|
|
void addOclusion(int tileId,const rgbf& c,float thickness);
|
2013-06-24 15:59:32 -06:00
|
|
|
|
2013-09-22 05:50:03 -06:00
|
|
|
matLightDef* getMaterialDef(int matType,int matIndex);
|
|
|
|
buildingLightDef* getBuildingDef(df::building* bld);
|
|
|
|
creatureLightDef* getCreatureDef(df::unit* u);
|
|
|
|
itemLightDef* getItemDef(df::item* it);
|
|
|
|
|
2013-06-24 09:18:57 -06:00
|
|
|
//apply material to cell
|
2013-06-25 05:03:01 -06:00
|
|
|
void applyMaterial(int tileId,const matLightDef& mat,float size=1, float thickness = 1);
|
2013-06-24 09:18:57 -06:00
|
|
|
//try to find and apply material, if failed return false, and if def!=null then apply def.
|
2013-06-26 11:19:30 -06:00
|
|
|
bool applyMaterial(int tileId,int matType,int matIndex,float size=1,float thickness = 1,const matLightDef* def=NULL);
|
2015-02-14 20:53:06 -07:00
|
|
|
|
2013-06-23 10:25:42 -06:00
|
|
|
size_t inline getIndex(int x,int y)
|
|
|
|
{
|
|
|
|
return x*h+y;
|
|
|
|
}
|
2013-06-26 07:42:14 -06:00
|
|
|
df::coord2d inline getCoords(int index)
|
|
|
|
{
|
|
|
|
return df::coord2d(index/h, index%h);
|
|
|
|
}
|
2013-06-24 09:18:57 -06:00
|
|
|
//maps
|
2013-06-30 06:05:59 -06:00
|
|
|
std::vector<rgbf> lightMap;
|
|
|
|
std::vector<rgbf> ocupancy;
|
2013-06-23 10:25:42 -06:00
|
|
|
std::vector<lightSource> lights;
|
2013-06-26 07:42:14 -06:00
|
|
|
|
|
|
|
//Threading stuff
|
2014-02-14 02:50:12 -07:00
|
|
|
int num_diffuse; //under same lock as ocupancy
|
2013-06-29 11:55:07 -06:00
|
|
|
lightThreadDispatch threading;
|
2013-06-26 13:05:22 -06:00
|
|
|
//misc
|
|
|
|
void setHour(float h){dayHour=h;};
|
2013-06-29 11:55:07 -06:00
|
|
|
|
|
|
|
int getW()const {return w;}
|
|
|
|
int getH()const {return h;}
|
2013-06-26 07:42:14 -06:00
|
|
|
public:
|
2015-02-14 20:53:06 -07:00
|
|
|
void lightWorkerThread(void * arg);
|
2013-06-26 07:42:14 -06:00
|
|
|
private:
|
2013-06-30 06:05:59 -06:00
|
|
|
rgbf getSkyColor(float v);
|
2013-06-28 14:34:47 -06:00
|
|
|
bool doDebug;
|
|
|
|
|
2013-06-24 09:18:57 -06:00
|
|
|
//settings
|
2013-06-26 15:14:03 -06:00
|
|
|
float daySpeed;
|
2013-06-26 13:05:22 -06:00
|
|
|
float dayHour; //<0 to cycle
|
2013-06-30 06:05:59 -06:00
|
|
|
std::vector<rgbf> dayColors; // a gradient of colors, first to 0, last to 24
|
2013-06-24 09:18:57 -06:00
|
|
|
///set up sane settings if setting file does not exist.
|
2015-02-14 20:53:06 -07:00
|
|
|
void defaultSettings();
|
2013-06-24 09:18:57 -06:00
|
|
|
|
|
|
|
static int parseMaterials(lua_State* L);
|
|
|
|
static int parseSpecial(lua_State* L);
|
2013-06-25 16:18:26 -06:00
|
|
|
static int parseBuildings(lua_State* L);
|
2013-09-22 05:50:03 -06:00
|
|
|
static int parseItems(lua_State* L);
|
|
|
|
static int parseCreatures(lua_State* L);
|
2013-06-24 09:18:57 -06:00
|
|
|
//special stuff
|
|
|
|
matLightDef matLava;
|
|
|
|
matLightDef matIce;
|
|
|
|
matLightDef matAmbience;
|
|
|
|
matLightDef matCursor;
|
2013-06-24 15:59:32 -06:00
|
|
|
matLightDef matWall;
|
|
|
|
matLightDef matWater;
|
2013-06-25 10:34:38 -06:00
|
|
|
matLightDef matCitizen;
|
|
|
|
float levelDim;
|
2014-02-14 02:50:12 -07:00
|
|
|
int adv_mode;
|
2013-06-24 09:18:57 -06:00
|
|
|
//materials
|
2013-07-02 13:06:39 -06:00
|
|
|
std::unordered_map<std::pair<int,int>,matLightDef> matDefs;
|
2013-06-25 16:18:26 -06:00
|
|
|
//buildings
|
2013-07-02 13:06:39 -06:00
|
|
|
std::unordered_map<std::tuple<int,int,int>,buildingLightDef> buildingDefs;
|
2013-09-22 05:50:03 -06:00
|
|
|
//creatures
|
|
|
|
std::unordered_map<std::pair<int,int>,creatureLightDef> creatureDefs;
|
|
|
|
//items
|
|
|
|
std::unordered_map<std::pair<int,int>,itemLightDef> itemDefs;
|
2013-06-23 10:25:42 -06:00
|
|
|
int w,h;
|
|
|
|
DFHack::rect2d mapPort;
|
2014-05-05 22:17:02 -06:00
|
|
|
friend class lightThreadDispatch;
|
2013-06-23 10:25:42 -06:00
|
|
|
};
|
2013-09-22 05:50:03 -06:00
|
|
|
rgbf blend(const rgbf& a,const rgbf& b);
|
|
|
|
rgbf blendMax(const rgbf& a,const rgbf& b);
|
2013-06-24 02:50:22 -06:00
|
|
|
#endif
|