Merge branch god_rays

This commit is contained in:
Michaël Lemaire 2015-10-08 09:45:39 +02:00
commit 8d9e3fbc94
35 changed files with 913 additions and 36 deletions

1
TODO
View file

@ -3,6 +3,7 @@ Technlology Preview 2 :
- Streamline the definition system, with undo and events. - Streamline the definition system, with undo and events.
- Implement copy-on-write on definitions (to avoid copying whole heightmaps for instance). - Implement copy-on-write on definitions (to avoid copying whole heightmaps for instance).
- Add clouds to OpenGL with 3d textures. - Add clouds to OpenGL with 3d textures.
- Refactor medium traversal to unify clouds, atmosphere and god rays.
- Fix potential holes in land rendering (OpenGL and software). - Fix potential holes in land rendering (OpenGL and software).
- Fix polygon culling near the camera in low-res renders (automatic tessellation ?). - Fix polygon culling near the camera in low-res renders (automatic tessellation ?).
- Fix sun size not being consistent between opengl and software - Fix sun size not being consistent between opengl and software

View file

@ -11,3 +11,17 @@ double Interpolation::bicubic(double stencil[16], double x, double y)
return Interpolation::cubic(buf_cubic_y, y); return Interpolation::cubic(buf_cubic_y, y);
} }
double Interpolation::bilinear(double p[4], double x, double y)
{
double e1 = linear(p[0], p[1], x);
double e2 = linear(p[3], p[2], x);
return Interpolation::linear(e1, e2, y);
}
double Interpolation::trilinear(double p[8], double x, double y, double z)
{
double f1 = bilinear(p, x, y);
double f2 = bilinear(p + 4, x, y);
return Interpolation::linear(f1, f2, z);
}

View file

@ -9,6 +9,13 @@ namespace basics {
class BASICSSHARED_EXPORT Interpolation class BASICSSHARED_EXPORT Interpolation
{ {
public: public:
static inline double linear(double p1, double p2, double x)
{
return p1 + x * (p2 - p1);
}
static double bilinear(double p[4], double x, double y);
static double trilinear(double p[8], double x, double y, double z);
static inline double cubic(double p[4], double x) static inline double cubic(double p[4], double x)
{ {
return p[1] + 0.5 * x * (p[2] - p[0] + x * (2.0 * p[0] - 5.0 * p[1] + 4.0 * p[2] - p[3] + x * (3.0 * (p[1] - p[2]) + p[3] - p[0]))); return p[1] + 0.5 * x * (p[2] - p[0] + x * (2.0 * p[0] - 5.0 * p[1] + 4.0 * p[2] - p[3] + x * (3.0 * (p[1] - p[2]) + p[3] - p[0])));

View file

@ -20,6 +20,10 @@ public:
inline Vector3 getStart() const {return start;} inline Vector3 getStart() const {return start;}
inline Vector3 getEnd() const {return end;} inline Vector3 getEnd() const {return end;}
inline double getXDiff() const {return end.x - start.x;}
inline double getYDiff() const {return end.y - start.y;}
inline double getZDiff() const {return end.z - start.z;}
/*! /*!
* \brief Keep only the intersection with a slice orthogonal to the Y axis. * \brief Keep only the intersection with a slice orthogonal to the Y axis.
* \return true if a segment remains * \return true if a segment remains

View file

@ -3,10 +3,12 @@
#include "PackStream.h" #include "PackStream.h"
#include "RandomGenerator.h" #include "RandomGenerator.h"
#include "FloatNode.h" #include "FloatNode.h"
#include "GodRaysDefinition.h"
AtmosphereDefinition::AtmosphereDefinition(DefinitionNode* parent): AtmosphereDefinition::AtmosphereDefinition(DefinitionNode* parent):
DefinitionNode(parent, "atmosphere", "atmosphere") DefinitionNode(parent, "atmosphere", "atmosphere")
{ {
godrays = new GodRaysDefinition(this);
daytime = new FloatNode(this, "daytime"); daytime = new FloatNode(this, "daytime");
humidity = new FloatNode(this, "humidity"); humidity = new FloatNode(this, "humidity");
sun_radius = new FloatNode(this, "sun_radius"); sun_radius = new FloatNode(this, "sun_radius");

View file

@ -45,6 +45,7 @@ public:
virtual void copy(DefinitionNode* destination) const override; virtual void copy(DefinitionNode* destination) const override;
inline GodRaysDefinition *childGodRays() const {return godrays;}
inline FloatNode *propDayTime() const {return daytime;} inline FloatNode *propDayTime() const {return daytime;}
inline FloatNode *propHumidity() const {return humidity;} inline FloatNode *propHumidity() const {return humidity;}
inline FloatNode *propSunRadius() const {return sun_radius;} inline FloatNode *propSunRadius() const {return sun_radius;}
@ -78,6 +79,7 @@ public:
std::vector<Star> stars; std::vector<Star> stars;
private: private:
GodRaysDefinition *godrays;
FloatNode *humidity; FloatNode *humidity;
FloatNode *daytime; FloatNode *daytime;
FloatNode *sun_radius; FloatNode *sun_radius;

View file

@ -0,0 +1,12 @@
#include "GodRaysDefinition.h"
#include "FloatNode.h"
GodRaysDefinition::GodRaysDefinition(DefinitionNode *parent):
DefinitionNode(parent, "godrays", "godrays")
{
penetration = new FloatNode(this, "penetration", 0.01);
resistance = new FloatNode(this, "resistance", 0.3);
boost = new FloatNode(this, "boost", 8.0);
}

View file

@ -0,0 +1,29 @@
#ifndef GODRAYSDEFINITION_H
#define GODRAYSDEFINITION_H
#include "definition_global.h"
#include "DefinitionNode.h"
namespace paysages {
namespace definition {
class DEFINITIONSHARED_EXPORT GodRaysDefinition: public DefinitionNode
{
public:
GodRaysDefinition(DefinitionNode *parent);
inline FloatNode *propPenetration() const {return penetration;}
inline FloatNode *propResistance() const {return resistance;}
inline FloatNode *propBoost() const {return boost;}
private:
FloatNode *penetration;
FloatNode *resistance;
FloatNode *boost;
};
}
}
#endif // GODRAYSDEFINITION_H

View file

@ -37,7 +37,8 @@ SOURCES += \
DiffManager.cpp \ DiffManager.cpp \
DefinitionWatcher.cpp \ DefinitionWatcher.cpp \
IntNode.cpp \ IntNode.cpp \
IntDiff.cpp IntDiff.cpp \
GodRaysDefinition.cpp
HEADERS +=\ HEADERS +=\
definition_global.h \ definition_global.h \
@ -64,7 +65,8 @@ HEADERS +=\
DiffManager.h \ DiffManager.h \
DefinitionWatcher.h \ DefinitionWatcher.h \
IntNode.h \ IntNode.h \
IntDiff.h IntDiff.h \
GodRaysDefinition.h
unix:!symbian { unix:!symbian {
maemo5 { maemo5 {

View file

@ -30,6 +30,7 @@ namespace definition {
class CloudsDefinition; class CloudsDefinition;
class CloudLayerDefinition; class CloudLayerDefinition;
class AtmosphereDefinition; class AtmosphereDefinition;
class GodRaysDefinition;
class TexturesDefinition; class TexturesDefinition;
class TextureLayerDefinition; class TextureLayerDefinition;
class TerrainDefinition; class TerrainDefinition;

View file

@ -5,11 +5,17 @@
#include "TerrainDefinition.h" #include "TerrainDefinition.h"
#include "AtmosphereDefinition.h" #include "AtmosphereDefinition.h"
#include "TexturesDefinition.h" #include "TexturesDefinition.h"
#include "GodRaysDefinition.h"
#include "TextureLayerDefinition.h" #include "TextureLayerDefinition.h"
#include "WaterDefinition.h" #include "WaterDefinition.h"
#include "SurfaceMaterial.h" #include "SurfaceMaterial.h"
#include "FloatNode.h" #include "FloatNode.h"
#include "SkyRasterizer.h" #include "SkyRasterizer.h"
#include "CloudsDefinition.h"
#include "LightComponent.h"
#include "LightingManager.h"
#include "LightFilter.h"
#include "GodRaysSampler.h"
#include <sstream> #include <sstream>
@ -103,10 +109,94 @@ static void testCloudQuality()
} }
} }
static void testGodRays()
{
class TestLightFilter: public LightFilter
{
virtual bool applyLightFilter(LightComponent &light, const Vector3 &at) override
{
if (Vector3(0.0, 100.0, 0.0).sub(at).normalize().y > 0.97)
{
light.color = COLOR_BLACK;
return false;
}
else
{
return true;
}
}
};
class TestRenderer: public SoftwareCanvasRenderer
{
public:
TestRenderer(Scenery *scenery): SoftwareCanvasRenderer(scenery) {}
private:
virtual void prepare() override
{
SoftwareRenderer::prepare();
getLightingManager()->clearSources();
getLightingManager()->addStaticLight(LightComponent(COLOR_WHITE, VECTOR_DOWN));
getGodRaysSampler()->setAltitudes(0.0, 30.0);
getGodRaysSampler()->reset();
}
};
Scenery scenery;
scenery.autoPreset(63);
scenery.getAtmosphere()->setDayTime(12);
scenery.getCamera()->setLocation(Vector3(0.0, 1.0, -50.0));
scenery.getCamera()->setTarget(Vector3(0.0, 15.0, 0.0));
scenery.getTerrain()->height = 0.0;
scenery.getTerrain()->validate();
scenery.getClouds()->clear();
TestRenderer renderer(&scenery);
renderer.setSize(500, 300);
SkyRasterizer *rasterizer = new SkyRasterizer(&renderer, renderer.getProgressHelper(), 0);
renderer.setSoloRasterizer(rasterizer);
TestLightFilter filter;
renderer.getLightingManager()->clearFilters();
renderer.getLightingManager()->registerFilter(&filter);
// quality
for (int i = 0; i < 6; i++)
{
renderer.setQuality((double)i / 5.0);
rasterizer->setQuality(0.2);
startTestRender(&renderer, "god_rays_quality", i);
}
renderer.setQuality(0.5);
// penetration
for (int i = 0; i < 3; i++)
{
scenery.getAtmosphere()->childGodRays()->propPenetration()->setValue(0.01 + 0.02 * (double)i);
startTestRender(&renderer, "god_rays_penetration", i);
}
// resistance
scenery.getAtmosphere()->childGodRays()->propPenetration()->setValue(0.01);
for (int i = 0; i < 3; i++)
{
scenery.getAtmosphere()->childGodRays()->propResistance()->setValue(0.1 + 0.1 * (double)i);
startTestRender(&renderer, "god_rays_resistance", i);
}
// boost
scenery.getAtmosphere()->childGodRays()->propResistance()->setValue(0.3);
for (int i = 0; i < 3; i++)
{
scenery.getAtmosphere()->childGodRays()->propBoost()->setValue(2.0 + 4.0 * (double)i);
startTestRender(&renderer, "god_rays_boost", i);
}
}
void runTestSuite() void runTestSuite()
{ {
testGroundShadowQuality(); testGroundShadowQuality();
testRasterizationQuality(); testRasterizationQuality();
testCloudQuality(); testCloudQuality();
testGodRays();
} }

View file

@ -9,6 +9,7 @@
#include "CloudsRenderer.h" #include "CloudsRenderer.h"
#include "Scenery.h" #include "Scenery.h"
#include "LightingManager.h" #include "LightingManager.h"
#include "GodRaysSampler.h"
#include "Logs.h" #include "Logs.h"
#include "Vector3.h" #include "Vector3.h"
@ -71,6 +72,7 @@ void OpenGLRenderer::initialize()
getCloudsRenderer()->setEnabled(false); getCloudsRenderer()->setEnabled(false);
getLightingManager()->setSpecularity(false); getLightingManager()->setSpecularity(false);
getGodRaysSampler()->setEnabled(false);
skybox->initialize(); skybox->initialize();
skybox->updateScenery(); skybox->updateScenery();

View file

@ -5,6 +5,7 @@
#include "AtmosphereDefinition.h" #include "AtmosphereDefinition.h"
#include "AtmosphereModelBruneton.h" #include "AtmosphereModelBruneton.h"
#include "AtmosphereResult.h" #include "AtmosphereResult.h"
#include "GodRaysSampler.h"
#include "LightComponent.h" #include "LightComponent.h"
#include "LightStatus.h" #include "LightStatus.h"
#include "Scenery.h" #include "Scenery.h"
@ -98,7 +99,7 @@ AtmosphereResult BaseAtmosphereRenderer::getSkyColor(Vector3)
return result; return result;
} }
Vector3 BaseAtmosphereRenderer::getSunDirection(bool cache) const Vector3 BaseAtmosphereRenderer::getSunDirection(bool) const
{ {
AtmosphereDefinition* atmosphere = getDefinition(); AtmosphereDefinition* atmosphere = getDefinition();
double sun_angle = (atmosphere->propDayTime()->getValue() + 0.75) * M_PI * 2.0; double sun_angle = (atmosphere->propDayTime()->getValue() + 0.75) * M_PI * 2.0;
@ -131,7 +132,7 @@ AtmosphereResult SoftwareBrunetonAtmosphereRenderer::applyAerialPerspective(Vect
AtmosphereDefinition* definition = getDefinition(); AtmosphereDefinition* definition = getDefinition();
AtmosphereResult result; AtmosphereResult result;
/* Get base perspective */ // Get base perspective
switch (definition->model) switch (definition->model)
{ {
case AtmosphereDefinition::ATMOSPHERE_MODEL_BRUNETON: case AtmosphereDefinition::ATMOSPHERE_MODEL_BRUNETON:
@ -141,7 +142,10 @@ AtmosphereResult SoftwareBrunetonAtmosphereRenderer::applyAerialPerspective(Vect
; ;
} }
/* Apply weather effects */ // Apply god rays ponderation
result.inscattering = parent->getGodRaysSampler()->apply(COLOR_BLACK, result.inscattering, location);
// Apply weather effects
_applyWeatherEffects(definition, &result); _applyWeatherEffects(definition, &result);
return result; return result;
@ -162,10 +166,10 @@ AtmosphereResult SoftwareBrunetonAtmosphereRenderer::getSkyColor(Vector3 directi
base = COLOR_BLACK; base = COLOR_BLACK;
/* Get night sky */ // Get night sky
base = base.add(parent->getNightSky()->getColor(camera_location.y, direction)); base = base.add(parent->getNightSky()->getColor(camera_location.y, direction));
/* Get sun shape */ // Get sun shape
/*if (v3Dot(sun_direction, direction) >= 0) /*if (v3Dot(sun_direction, direction) >= 0)
{ {
double sun_radius = definition->sun_radius * SUN_RADIUS_SCALED * 5.0; // FIXME Why should we multiply by 5 ? double sun_radius = definition->sun_radius * SUN_RADIUS_SCALED * 5.0; // FIXME Why should we multiply by 5 ?
@ -190,7 +194,7 @@ AtmosphereResult SoftwareBrunetonAtmosphereRenderer::getSkyColor(Vector3 directi
} }
}*/ }*/
/* Get scattering */ // Get scattering
AtmosphereResult result; AtmosphereResult result;
Vector3 location = camera_location.add(direction.scale(6421.0)); Vector3 location = camera_location.add(direction.scale(6421.0));
switch (definition->model) switch (definition->model)
@ -202,7 +206,10 @@ AtmosphereResult SoftwareBrunetonAtmosphereRenderer::getSkyColor(Vector3 directi
result = BaseAtmosphereRenderer::applyAerialPerspective(location, result.base); result = BaseAtmosphereRenderer::applyAerialPerspective(location, result.base);
} }
/* Apply weather effects */ // Apply god rays ponderation
result.inscattering = parent->getGodRaysSampler()->apply(COLOR_BLACK, result.inscattering, location);
// Apply weather effects
_applyWeatherEffects(definition, &result); _applyWeatherEffects(definition, &result);
return result; return result;

View file

@ -239,7 +239,7 @@ bool CloudBasicLayerRenderer::alterLight(BaseCloudsModel *model, LightComponent*
} }
} }
double miminum_light = 0.5; double miminum_light = 0.3;
factor = 1.0 - (1.0 - miminum_light) * factor; factor = 1.0 - (1.0 - miminum_light) * factor;
light->color.r *= factor; light->color.r *= factor;

View file

@ -198,3 +198,23 @@ bool CloudsRenderer::applyLightFilter(LightComponent &light, const Vector3 &at)
return true; return true;
} }
double CloudsRenderer::getHighestAltitude()
{
CloudsDefinition* definition = parent->getScenery()->getClouds();
double highest = 0.0;
int n = definition->count();
double low, high;
for (int i = 0; i < n; i++)
{
BaseCloudsModel* layer_model = getLayerModel(i);
layer_model->getAltitudeRange(&low, &high);
if (high > highest)
{
highest = high;
}
}
return highest;
}

View file

@ -67,6 +67,11 @@ public:
* Return true if the light was altered. * Return true if the light was altered.
*/ */
virtual bool applyLightFilter(LightComponent &light, const Vector3 &at) override; virtual bool applyLightFilter(LightComponent &light, const Vector3 &at) override;
/**
* Get the highest altitude of all layers.
*/
double getHighestAltitude();
private: private:
double quality; double quality;

View file

@ -0,0 +1,38 @@
#include "GodRaysResult.h"
GodRaysResult::GodRaysResult(double inside_length, double full_length):
inside_length(inside_length), full_length(full_length)
{
}
Color GodRaysResult::apply(const Color &raw, const Color &atmosphered, const GodRaysParams &params)
{
if (inside_length == 0.0)
{
return raw;
}
else if (inside_length < full_length)
{
double diff = full_length - inside_length;
double factor = 1.0 - params.penetration * diff;
double minimum = params.resistance;
double complement = 1.0 - minimum;
if (factor < minimum)
{
factor = minimum;
}
else
{
factor = pow((factor - minimum) / complement, params.boost) * complement + minimum;
}
return Color(raw.r + (atmosphered.r - raw.r) * factor,
raw.g + (atmosphered.g - raw.g) * factor,
raw.b + (atmosphered.b - raw.b) * factor,
atmosphered.a);
}
else
{
return atmosphered;
}
}

View file

@ -0,0 +1,40 @@
#ifndef GODRAYSRESULT_H
#define GODRAYSRESULT_H
#include "software_global.h"
#include "Color.h"
namespace paysages {
namespace software {
/**
* Result of god rays computation.
*/
class SOFTWARESHARED_EXPORT GodRaysResult
{
public:
typedef struct {
double penetration;
double resistance;
double boost;
} GodRaysParams;
public:
GodRaysResult() = default;
GodRaysResult(double inside_length, double full_length);
/**
* Apply the result on a base color.
*/
Color apply(const Color &raw, const Color &atmosphered, const GodRaysParams &params);
private:
double inside_length;
double full_length;
};
}
}
#endif // GODRAYSRESULT_H

View file

@ -0,0 +1,220 @@
#include "GodRaysSampler.h"
#include "GodRaysDefinition.h"
#include "AtmosphereDefinition.h"
#include "SoftwareRenderer.h"
#include "Vector3.h"
#include "SpaceSegment.h"
#include "GodRaysResult.h"
#include "LightingManager.h"
#include "FloatNode.h"
#include "LightStatus.h"
#include "Scenery.h"
#include "TerrainDefinition.h"
#include "CloudsRenderer.h"
#include "Interpolation.h"
#include <cmath>
GodRaysSampler::GodRaysSampler()
{
enabled = true;
bounds = new SpaceSegment();
definition = new GodRaysDefinition(NULL);
camera_location = new Vector3(0, 0, 0);
lighting = NULL;
low_altitude = -1.0;
high_altitude = 1.0;
sampling_step = 1.0;
max_length = 1.0;
data = new double[1];
setQuality(0.5);
}
GodRaysSampler::~GodRaysSampler()
{
delete definition;
delete bounds;
delete camera_location;
delete[] data;
}
void GodRaysSampler::prepare(SoftwareRenderer *renderer)
{
setCameraLocation(renderer->getCameraLocation(VECTOR_ZERO));
setLighting(renderer->getLightingManager());
setAltitudes(renderer->getScenery()->getTerrain()->getHeightInfo().min_height, renderer->getCloudsRenderer()->getHighestAltitude());
renderer->getScenery()->getAtmosphere()->childGodRays()->copy(definition);
reset();
}
void GodRaysSampler::reset()
{
*bounds = SpaceSegment(Vector3(camera_location->x - max_length, low_altitude, camera_location->z - max_length),
Vector3(camera_location->x + max_length, high_altitude, camera_location->z + max_length));
samples_x = round(bounds->getXDiff() / sampling_step) + 1;
samples_y = round(bounds->getYDiff() / sampling_step) + 1;
samples_z = round(bounds->getZDiff() / sampling_step) + 1;
long n = samples_x * samples_y * samples_z;
delete[] data;
data = new double[n];
for (long i = 0; i < n; i++)
{
data[i] = -1.0;
}
}
void GodRaysSampler::setEnabled(bool enabled)
{
this->enabled = enabled;
}
void GodRaysSampler::setLighting(LightingManager *manager)
{
this->lighting = manager;
}
void GodRaysSampler::setQuality(double factor)
{
setQuality(5.0 / (factor * 8.0 + 1.0), 100.0, 5.0 / (factor * 80.0 + 1.0));
reset();
}
void GodRaysSampler::setQuality(double sampling_step, double max_length, double walk_step)
{
this->sampling_step = sampling_step;
this->max_length = max_length;
this->walk_step = walk_step;
}
void GodRaysSampler::setCameraLocation(const Vector3 &location)
{
*camera_location = location;
}
void GodRaysSampler::setAltitudes(double low, double high)
{
low_altitude = low;
high_altitude = high;
}
void GodRaysSampler::getSamples(int *x, int *y, int *z) const
{
*x = samples_x;
*y = samples_y;
*z = samples_z;
}
Color GodRaysSampler::getRawLight(const Vector3 &location, bool filtered) const
{
if (lighting)
{
LightStatus status(lighting, location, *camera_location, filtered);
lighting->fillStatus(status, location);
return status.getSum();
}
else
{
return COLOR_TRANSPARENT;
}
}
double GodRaysSampler::getCachedLight(const Vector3 &location)
{
double x = location.x - bounds->getStart().x;
double y = location.y - bounds->getStart().y;
double z = location.z - bounds->getStart().z;
int ix = (int)floor(x / sampling_step);
int iy = (int)floor(y / sampling_step);
int iz = (int)floor(z / sampling_step);
// Check cache limits
if (ix < 0 || ix >= samples_x - 1 || iy < 0 || iy >= samples_y - 1 || iz < 0 || iz >= samples_z - 1)
{
return 1.0;
}
// Hit cache
double p[8] = {
getCache(ix, iy, iz),
getCache(ix + 1, iy, iz),
getCache(ix + 1, iy + 1, iz),
getCache(ix, iy + 1, iz),
getCache(ix, iy, iz + 1),
getCache(ix + 1, iy, iz + 1),
getCache(ix + 1, iy + 1, iz + 1),
getCache(ix, iy + 1, iz + 1)
};
return Interpolation::trilinear(p,
(x - sampling_step * double(ix)) / sampling_step,
(y - sampling_step * double(iy)) / sampling_step,
(z - sampling_step * double(iz)) / sampling_step);
}
GodRaysResult GodRaysSampler::getResult(const SpaceSegment &segment)
{
Vector3 step = segment.getEnd().sub(segment.getStart());
double max_length = step.getNorm();
step = step.normalize().scale(walk_step);
Vector3 walker = segment.getStart();
double travelled = 0.0;
double inside = 0.0;
if (max_length > this->max_length)
{
max_length = this->max_length;
}
while (travelled < max_length)
{
double light = getCachedLight(walker);
inside += light * walk_step;
walker = walker.add(step);
travelled += walk_step;
}
return GodRaysResult(inside, travelled);
}
Color GodRaysSampler::apply(const Color &raw, const Color &atmosphered, const Vector3 &location)
{
if (enabled)
{
GodRaysResult result = getResult(SpaceSegment(*camera_location, location));
GodRaysResult::GodRaysParams params;
params.penetration = definition->propPenetration()->getValue();
params.resistance = definition->propResistance()->getValue();
params.boost = definition->propBoost()->getValue();
return result.apply(raw, atmosphered, params);
}
else
{
return atmosphered;
}
}
inline double GodRaysSampler::getCache(int x, int y, int z)
{
double *cache = data + z * samples_x * samples_y + y * samples_x + x;
if (*cache < 0.0)
{
Vector3 location = Vector3(bounds->getStart().x + sampling_step * (double)x,
bounds->getStart().y + sampling_step * (double)y,
bounds->getStart().z + sampling_step * (double)z);
double unfiltered_power = getRawLight(location, false).getPower();
if (unfiltered_power == 0.0)
{
*cache = 1.0;
}
else
{
*cache = getRawLight(location, true).getPower() / unfiltered_power;
}
}
return *cache;
}

View file

@ -0,0 +1,124 @@
#ifndef GODRAYSSAMPLER_H
#define GODRAYSSAMPLER_H
#include "software_global.h"
namespace paysages {
namespace software {
/**
* 3D sampler for "god rays".
*/
class SOFTWARESHARED_EXPORT GodRaysSampler
{
public:
GodRaysSampler();
~GodRaysSampler();
inline const SpaceSegment &getBounds() const {return *bounds;}
inline double getSamplingStep() const {return sampling_step;}
inline double getMaxLength() const {return max_length;}
inline double getWalkStep() const {return walk_step;}
/**
* Prepare the sampler from a renderer.
*/
void prepare(SoftwareRenderer *renderer);
/**
* Reset the sampler cache.
*
* This needs to be called after all setXXX method have been called.
*/
void reset();
/**
* Enable or disable the god rays effect.
*/
void setEnabled(bool enabled);
/**
* Set the lighting manager to use for raw sampling.
*/
void setLighting(LightingManager *manager);
/**
* Set the overall quality factor (0.0-1.0).
*/
void setQuality(double factor);
/**
* Set the quality indicators.
*/
void setQuality(double sampling_step, double max_length, double walk_step);
/**
* Set the camera location.
*
* This will fix the limits of sampling data, around the camera location.
*/
void setCameraLocation(const Vector3 &location);
/**
* Set the altitude boundaries.
*/
void setAltitudes(double low, double high);
/**
* Get the number of samples in each dimension.
*/
void getSamples(int *x, int *y, int *z) const;
/**
* Get the raw light at a location (without using the cached sampling).
*/
Color getRawLight(const Vector3 &location, bool filtered) const;
/**
* Get the lighting factor at a given point, using (and filling) the cached sampling.
*/
double getCachedLight(const Vector3 &location);
/**
* Get the god rays effect on a space segment.
*/
GodRaysResult getResult(const SpaceSegment &segment);
/**
* Apply the god rays effect to a location.
*
* *raw* is the shaded color, without atmospheric effects.
* *atmosphered* is the shaded color, with atmospheric effects applied.
*/
Color apply(const Color &raw, const Color &atmosphered, const Vector3 &location);
private:
double getCache(int x, int y, int z);
private:
bool enabled;
SpaceSegment *bounds;
GodRaysDefinition *definition;
double sampling_step;
double max_length;
double walk_step;
int samples_x;
int samples_y;
int samples_z;
double low_altitude;
double high_altitude;
Vector3 *camera_location;
LightingManager *lighting;
double *data;
};
}
}
#endif // GODRAYSSAMPLER_H

View file

@ -1,5 +1,6 @@
#include "LightComponent.h" #include "LightComponent.h"
/*LightComponent::LightComponent() LightComponent::LightComponent(const Color &color, const Vector3 &direction, double reflection, bool altered):
color(color), direction(direction), reflection(reflection), altered(altered)
{ {
}*/ }

View file

@ -17,7 +17,8 @@ namespace software {
class SOFTWARESHARED_EXPORT LightComponent class SOFTWARESHARED_EXPORT LightComponent
{ {
public: public:
//LightComponent(); LightComponent() = default;
LightComponent(const Color &color, const Vector3 &direction, double reflection = 0.0, bool altered = true);
Color color; // Light power Color color; // Light power
Vector3 direction; // Direction the light is travelling Vector3 direction; // Direction the light is travelling

View file

@ -1,18 +1,22 @@
#include "LightStatus.h" #include "LightStatus.h"
#include "LightingManager.h" #include "LightingManager.h"
#include "LightComponent.h"
#include "Color.h" #include "Color.h"
#include "SurfaceMaterial.h" #include "SurfaceMaterial.h"
LightStatus::LightStatus(LightingManager *manager, const Vector3 &location, const Vector3 &eye) LightStatus::LightStatus(LightingManager *manager, const Vector3 &location, const Vector3 &eye, bool filtered)
{ {
this->max_power = 0.0; this->max_power = 0.0;
this->manager = manager; this->manager = manager;
this->location = location; this->location = location;
this->eye = eye; this->eye = eye;
this->filtered = filtered;
} }
void LightStatus::pushComponent(LightComponent component) void LightStatus::pushComponent(LightComponent component)
{
if (filtered)
{ {
double power = component.color.getPower(); double power = component.color.getPower();
if (component.altered && (power < max_power * 0.05 || power < 0.001)) if (component.altered && (power < max_power * 0.05 || power < 0.001))
@ -30,6 +34,11 @@ void LightStatus::pushComponent(LightComponent component)
components.push_back(component); components.push_back(component);
} }
} }
else
{
components.push_back(component);
}
}
Color LightStatus::apply(const Vector3 &normal, const SurfaceMaterial &material) Color LightStatus::apply(const Vector3 &normal, const SurfaceMaterial &material)
{ {
@ -43,3 +52,15 @@ Color LightStatus::apply(const Vector3 &normal, const SurfaceMaterial &material)
final.a = material.base->a; final.a = material.base->a;
return final; return final;
} }
Color LightStatus::getSum() const
{
Color final = COLOR_BLACK;
for (auto component: components)
{
final = final.add(component.color);
}
return final;
}

View file

@ -16,7 +16,7 @@ namespace software {
class SOFTWARESHARED_EXPORT LightStatus class SOFTWARESHARED_EXPORT LightStatus
{ {
public: public:
LightStatus(LightingManager *manager, const Vector3 &location, const Vector3 &eye); LightStatus(LightingManager *manager, const Vector3 &location, const Vector3 &eye, bool filtered=true);
inline Vector3 getLocation() const {return location;} inline Vector3 getLocation() const {return location;}
@ -24,11 +24,17 @@ public:
Color apply(const Vector3 &normal, const SurfaceMaterial &material); Color apply(const Vector3 &normal, const SurfaceMaterial &material);
/**
* Return the sum of all received lights.
*/
Color getSum() const;
private: private:
double max_power; double max_power;
LightingManager* manager; LightingManager* manager;
Vector3 location; Vector3 location;
Vector3 eye; Vector3 eye;
bool filtered;
std::vector<LightComponent> components; std::vector<LightComponent> components;
}; };

View file

@ -37,6 +37,11 @@ void LightingManager::addStaticLight(const LightComponent &light)
static_lights.push_back(light); static_lights.push_back(light);
} }
void LightingManager::clearSources()
{
sources.clear();
}
void LightingManager::registerSource(LightSource *source) void LightingManager::registerSource(LightSource *source)
{ {
if (std::find(sources.begin(), sources.end(), source) == sources.end()) if (std::find(sources.begin(), sources.end(), source) == sources.end())
@ -46,9 +51,17 @@ void LightingManager::registerSource(LightSource *source)
} }
void LightingManager::unregisterSource(LightSource *source) void LightingManager::unregisterSource(LightSource *source)
{
if (std::find(sources.begin(), sources.end(), source) != sources.end())
{ {
sources.erase(std::find(sources.begin(), sources.end(), source)); sources.erase(std::find(sources.begin(), sources.end(), source));
} }
}
void LightingManager::clearFilters()
{
filters.clear();
}
void LightingManager::registerFilter(LightFilter* filter) void LightingManager::registerFilter(LightFilter* filter)
{ {
@ -59,9 +72,12 @@ void LightingManager::registerFilter(LightFilter* filter)
} }
void LightingManager::unregisterFilter(LightFilter *filter) void LightingManager::unregisterFilter(LightFilter *filter)
{
if (std::find(filters.begin(), filters.end(), filter) != filters.end())
{ {
filters.erase(std::find(filters.begin(), filters.end(), filter)); filters.erase(std::find(filters.begin(), filters.end(), filter));
} }
}
bool LightingManager::alterLight(LightComponent &component, const Vector3 &location) bool LightingManager::alterLight(LightComponent &component, const Vector3 &location)
{ {
@ -168,10 +184,8 @@ Color LightingManager::applyFinalComponent(const LightComponent &component, cons
return result; return result;
} }
Color LightingManager::apply(const Vector3 &eye, const Vector3 &location, const Vector3 &normal, const SurfaceMaterial &material) void LightingManager::fillStatus(LightStatus &status, const Vector3 &location) const
{ {
LightStatus status(this, location, eye);
for (auto &light: static_lights) for (auto &light: static_lights)
{ {
status.pushComponent(light); status.pushComponent(light);
@ -187,6 +201,11 @@ Color LightingManager::apply(const Vector3 &eye, const Vector3 &location, const
} }
} }
} }
}
Color LightingManager::apply(const Vector3 &eye, const Vector3 &location, const Vector3 &normal, const SurfaceMaterial &material)
{
LightStatus status(this, location, eye);
fillStatus(status, location);
return status.apply(normal, material); return status.apply(normal, material);
} }

View file

@ -35,6 +35,11 @@ public:
*/ */
void addStaticLight(const LightComponent &light); void addStaticLight(const LightComponent &light);
/**
* Remove all registered sources.
*/
void clearSources();
/** /**
* Register a source of dynamic lighting. * Register a source of dynamic lighting.
*/ */
@ -45,6 +50,11 @@ public:
*/ */
void unregisterSource(LightSource *source); void unregisterSource(LightSource *source);
/**
* Remove all registered filters.
*/
void clearFilters();
/** /**
* Register a filter that will receive all alterable lights. * Register a filter that will receive all alterable lights.
*/ */
@ -72,6 +82,11 @@ public:
*/ */
Color applyFinalComponent(const LightComponent &component, const Vector3 &eye, const Vector3 &location, const Vector3 &normal, const SurfaceMaterial &material); Color applyFinalComponent(const LightComponent &component, const Vector3 &eye, const Vector3 &location, const Vector3 &normal, const SurfaceMaterial &material);
/**
* Compute the light status at a given location.
*/
void fillStatus(LightStatus &status, const Vector3 &location) const;
/** /**
* Apply lighting to a surface at a given location. * Apply lighting to a surface at a given location.
*/ */

View file

@ -95,7 +95,7 @@ const Color NightSky::getColor(double altitude, const Vector3 &direction)
} }
bool NightSky::getLightsAt(std::vector<LightComponent> &result, const Vector3 &location) const bool NightSky::getLightsAt(std::vector<LightComponent> &result, const Vector3 &) const
{ {
LightComponent moon, sky; LightComponent moon, sky;

View file

@ -9,6 +9,7 @@
#include "Rasterizer.h" #include "Rasterizer.h"
#include "CanvasFragment.h" #include "CanvasFragment.h"
#include "RenderProgress.h" #include "RenderProgress.h"
#include "GodRaysSampler.h"
#define SPHERE_SIZE 20000.0 #define SPHERE_SIZE 20000.0
@ -68,7 +69,7 @@ void SkyRasterizer::rasterizeToCanvas(CanvasPortion* canvas)
direction.z = SPHERE_SIZE * sin(current_i) * cos(current_j + step_j); direction.z = SPHERE_SIZE * sin(current_i) * cos(current_j + step_j);
vertex4 = camera_location.add(direction); vertex4 = camera_location.add(direction);
/* TODO Triangles at poles */ // TODO Triangles at poles
pushQuad(canvas, vertex1, vertex4, vertex3, vertex2); pushQuad(canvas, vertex1, vertex4, vertex3, vertex2);
} }
progress->add(res_i); progress->add(res_i);
@ -84,7 +85,7 @@ Color SkyRasterizer::shadeFragment(const CanvasFragment &fragment) const
camera_location = renderer->getCameraLocation(location); camera_location = renderer->getCameraLocation(location);
direction = location.sub(camera_location); direction = location.sub(camera_location);
/* TODO Don't compute result->color if it's fully covered by clouds */ // TODO Don't compute sky color if it's fully covered by clouds
result = renderer->getAtmosphereRenderer()->getSkyColor(direction.normalize()).final; result = renderer->getAtmosphereRenderer()->getSkyColor(direction.normalize()).final;
result = renderer->getCloudsRenderer()->getColor(camera_location, camera_location.add(direction.scale(10.0)), result); result = renderer->getCloudsRenderer()->getColor(camera_location, camera_location.add(direction.scale(10.0)), result);

View file

@ -17,6 +17,8 @@
#include "NightSky.h" #include "NightSky.h"
#include "LightStatus.h" #include "LightStatus.h"
#include "LightingManager.h" #include "LightingManager.h"
#include "GodRaysSampler.h"
#include "GodRaysResult.h"
#include "System.h" #include "System.h"
#include "Thread.h" #include "Thread.h"
@ -37,6 +39,7 @@ SoftwareRenderer::SoftwareRenderer(Scenery* scenery):
fluid_medium = new FluidMediumManager(this); fluid_medium = new FluidMediumManager(this);
lighting = new LightingManager(); lighting = new LightingManager();
godrays = new GodRaysSampler();
lighting->registerFilter(water_renderer); lighting->registerFilter(water_renderer);
lighting->registerFilter(terrain_renderer); lighting->registerFilter(terrain_renderer);
@ -52,6 +55,7 @@ SoftwareRenderer::~SoftwareRenderer()
delete fluid_medium; delete fluid_medium;
delete lighting; delete lighting;
delete godrays;
delete nightsky_renderer; delete nightsky_renderer;
@ -88,6 +92,7 @@ void SoftwareRenderer::prepare()
nightsky_renderer->update(); nightsky_renderer->update();
// Prepare global tools // Prepare global tools
godrays->prepare(this);
fluid_medium->clearMedia(); fluid_medium->clearMedia();
//fluid_medium->registerMedium(water_renderer); //fluid_medium->registerMedium(water_renderer);
} }
@ -96,6 +101,7 @@ void SoftwareRenderer::setQuality(double quality)
{ {
terrain_renderer->setQuality(quality); terrain_renderer->setQuality(quality);
clouds_renderer->setQuality(quality); clouds_renderer->setQuality(quality);
godrays->setQuality(quality);
// TEMP compat with old code // TEMP compat with old code
render_quality = (int)(quality * 9.0) + 1; render_quality = (int)(quality * 9.0) + 1;

View file

@ -57,6 +57,7 @@ public:
inline FluidMediumManager* getFluidMediumManager() const {return fluid_medium;} inline FluidMediumManager* getFluidMediumManager() const {return fluid_medium;}
inline LightingManager* getLightingManager() const {return lighting;} inline LightingManager* getLightingManager() const {return lighting;}
inline GodRaysSampler* getGodRaysSampler() const {return godrays;}
virtual Color applyLightingToSurface(const Vector3 &location, const Vector3 &normal, const SurfaceMaterial &material); virtual Color applyLightingToSurface(const Vector3 &location, const Vector3 &normal, const SurfaceMaterial &material);
virtual Color applyMediumTraversal(Vector3 location, Color color); virtual Color applyMediumTraversal(Vector3 location, Color color);
@ -67,6 +68,7 @@ private:
FluidMediumManager* fluid_medium; FluidMediumManager* fluid_medium;
LightingManager* lighting; LightingManager* lighting;
GodRaysSampler* godrays;
BaseAtmosphereRenderer* atmosphere_renderer; BaseAtmosphereRenderer* atmosphere_renderer;
CloudsRenderer* clouds_renderer; CloudsRenderer* clouds_renderer;

View file

@ -52,7 +52,9 @@ SOURCES += SoftwareRenderer.cpp \
clouds/CloudModelCirrus.cpp \ clouds/CloudModelCirrus.cpp \
clouds/CloudModelCumuloNimbus.cpp \ clouds/CloudModelCumuloNimbus.cpp \
RenderProgress.cpp \ RenderProgress.cpp \
LightSource.cpp LightSource.cpp \
GodRaysSampler.cpp \
GodRaysResult.cpp
HEADERS += SoftwareRenderer.h\ HEADERS += SoftwareRenderer.h\
software_global.h \ software_global.h \
@ -94,7 +96,9 @@ HEADERS += SoftwareRenderer.h\
clouds/CloudModelCirrus.h \ clouds/CloudModelCirrus.h \
clouds/CloudModelCumuloNimbus.h \ clouds/CloudModelCumuloNimbus.h \
RenderProgress.h \ RenderProgress.h \
LightSource.h LightSource.h \
GodRaysSampler.h \
GodRaysResult.h
unix:!symbian { unix:!symbian {
maemo5 { maemo5 {

View file

@ -49,6 +49,9 @@ namespace software {
class TerrainRayWalker; class TerrainRayWalker;
class GodRaysSampler;
class GodRaysResult;
class Canvas; class Canvas;
class CanvasPortion; class CanvasPortion;
class CanvasPixel; class CanvasPixel;

View file

@ -0,0 +1,149 @@
#include "BaseTestCase.h"
#include "GodRaysSampler.h"
#include "SpaceSegment.h"
#include "Vector3.h"
#include "LightingManager.h"
#include "LightSource.h"
#include "LightFilter.h"
#include "LightComponent.h"
#include "Color.h"
TEST(GodRaysSampler, getBounds)
{
GodRaysSampler sampler;
sampler.setAltitudes(-5.0, 300.0);
sampler.setCameraLocation(Vector3(100.0, 0.0, 8000.0));
sampler.setQuality(10.0, 500.0, 1.0);
sampler.reset();
SpaceSegment bounds = sampler.getBounds();
EXPECT_VECTOR3_COORDS(bounds.getStart(), -400.0, -5.0, 7500.0);
EXPECT_VECTOR3_COORDS(bounds.getEnd(), 600.0, 300.0, 8500.0);
}
TEST(GodRaysSampler, getSamples)
{
GodRaysSampler sampler;
sampler.setAltitudes(-50.0, 100.0);
sampler.setQuality(10.0, 100.0, 1.0);
sampler.reset();
int x, y, z;
sampler.getSamples(&x, &y, &z);
EXPECT_EQ(21, x);
EXPECT_EQ(16, y);
EXPECT_EQ(21, z);
}
TEST(GodRaysSampler, setQuality)
{
GodRaysSampler sampler;
sampler.setQuality(0.0);
EXPECT_DOUBLE_EQ(5.0, sampler.getSamplingStep());
EXPECT_DOUBLE_EQ(100.0, sampler.getMaxLength());
EXPECT_DOUBLE_EQ(5.0, sampler.getWalkStep());
sampler.setQuality(0.5);
EXPECT_DOUBLE_EQ(1.0, sampler.getSamplingStep());
EXPECT_DOUBLE_EQ(100.0, sampler.getMaxLength());
EXPECT_DOUBLE_EQ(0.12195121951, sampler.getWalkStep());
sampler.setQuality(1.0);
EXPECT_DOUBLE_EQ(0.55555555555, sampler.getSamplingStep());
EXPECT_DOUBLE_EQ(100.0, sampler.getMaxLength());
EXPECT_DOUBLE_EQ(0.06172839506, sampler.getWalkStep());
}
class GodRayLightSource: public LightSource
{
virtual bool getLightsAt(std::vector<LightComponent> &result, const Vector3 &location) const override
{
LightComponent light;
light.color = Color(fabs(location.x), fabs(location.y), fabs(location.z));
result.push_back(light);
return true;
}
};
TEST(GodRaysSampler, getRawLight)
{
class TestLightFilter: public LightFilter
{
virtual bool applyLightFilter(LightComponent &light, const Vector3 &at) override
{
if (at.x > 0.5)
{
light.color.r = 0.1;
return true;
}
else
{
return true;
}
}
};
GodRayLightSource light_source;
TestLightFilter filter;
LightingManager lighting;
lighting.registerSource(&light_source);
lighting.registerFilter(&filter);
GodRaysSampler sampler;
sampler.setLighting(&lighting);
EXPECT_COLOR_RGBA(sampler.getRawLight(Vector3(0.0, 0.0, 0.0), false), 0.0, 0.0, 0.0, 1.0);
EXPECT_COLOR_RGBA(sampler.getRawLight(Vector3(1.0, 2.0, -6.0), false), 1.0, 2.0, 6.0, 1.0);
EXPECT_COLOR_RGBA(sampler.getRawLight(Vector3(0.0, 0.0, 0.0), true), 0.0, 0.0, 0.0, 1.0);
EXPECT_COLOR_RGBA(sampler.getRawLight(Vector3(1.0, 2.0, -6.0), true), 0.1, 2.0, 6.0, 1.0);
}
TEST(GodRaysSampler, getCachedLight)
{
class TestLightFilter: public LightFilter
{
virtual bool applyLightFilter(LightComponent &, const Vector3 &at) override
{
return at.x <= 10.0 or at.x >= 50.0;
}
};
GodRayLightSource light_source;
TestLightFilter filter;
LightingManager lighting;
lighting.registerSource(&light_source);
lighting.registerFilter(&filter);
GodRaysSampler sampler;
sampler.setLighting(&lighting);
sampler.setQuality(5.0, 100.0, 1.0);
sampler.setAltitudes(-10.0, 10.0);
sampler.reset();
// outside the sampler, considered full light
EXPECT_DOUBLE_EQ(1.0, sampler.getCachedLight(Vector3(0.0, 15.0, 0.0)));
EXPECT_DOUBLE_EQ(1.0, sampler.getCachedLight(Vector3(0.0, -15.0, 0.0)));
EXPECT_DOUBLE_EQ(1.0, sampler.getCachedLight(Vector3(-120.0, 0.0, 0.0)));
EXPECT_DOUBLE_EQ(1.0, sampler.getCachedLight(Vector3(120.0, 0.0, 0.0)));
EXPECT_DOUBLE_EQ(1.0, sampler.getCachedLight(Vector3(0.0, 0.0, -100.1)));
EXPECT_DOUBLE_EQ(1.0, sampler.getCachedLight(Vector3(0.0, 0.0, 100.1)));
// at sampling point
EXPECT_DOUBLE_EQ(1.0, sampler.getCachedLight(Vector3(0.0, 0.0, 0.0)));
EXPECT_DOUBLE_EQ(1.0, sampler.getCachedLight(Vector3(0.0, 5.0, 10.0)));
EXPECT_DOUBLE_EQ(1.0, sampler.getCachedLight(Vector3(5.0, 5.0, 10.0)));
EXPECT_DOUBLE_EQ(1.0, sampler.getCachedLight(Vector3(10.0, 5.0, 10.0)));
EXPECT_DOUBLE_EQ(0.0, sampler.getCachedLight(Vector3(15.0, 5.0, 10.0)));
EXPECT_DOUBLE_EQ(0.0, sampler.getCachedLight(Vector3(20.0, 5.0, 10.0)));
// in between sampling points
EXPECT_DOUBLE_EQ(0.5, sampler.getCachedLight(Vector3(12.5, 5.0, 10.0)));
}

View file

@ -0,0 +1,27 @@
#include "BaseTestCase.h"
#include "Interpolation.h"
TEST(Interpolation, trilinear)
{
double p[8] = {0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0};
EXPECT_DOUBLE_EQ(0.0, Interpolation::trilinear(p, 0.0, 0.0, 0.0));
EXPECT_DOUBLE_EQ(1.0, Interpolation::trilinear(p, 1.0, 0.0, 0.0));
EXPECT_DOUBLE_EQ(2.0, Interpolation::trilinear(p, 1.0, 1.0, 0.0));
EXPECT_DOUBLE_EQ(3.0, Interpolation::trilinear(p, 0.0, 1.0, 0.0));
EXPECT_DOUBLE_EQ(4.0, Interpolation::trilinear(p, 0.0, 0.0, 1.0));
EXPECT_DOUBLE_EQ(5.0, Interpolation::trilinear(p, 1.0, 0.0, 1.0));
EXPECT_DOUBLE_EQ(6.0, Interpolation::trilinear(p, 1.0, 1.0, 1.0));
EXPECT_DOUBLE_EQ(7.0, Interpolation::trilinear(p, 0.0, 1.0, 1.0));
EXPECT_DOUBLE_EQ(0.5, Interpolation::trilinear(p, 0.5, 0.0, 0.0));
EXPECT_DOUBLE_EQ(1.5, Interpolation::trilinear(p, 0.0, 0.5, 0.0));
EXPECT_DOUBLE_EQ(2.0, Interpolation::trilinear(p, 0.0, 0.0, 0.5));
EXPECT_DOUBLE_EQ(1.5, Interpolation::trilinear(p, 0.5, 0.5, 0.0));
EXPECT_DOUBLE_EQ(3.5, Interpolation::trilinear(p, 0.0, 0.5, 0.5));
EXPECT_DOUBLE_EQ(2.5, Interpolation::trilinear(p, 0.5, 0.0, 0.5));
EXPECT_DOUBLE_EQ(3.5, Interpolation::trilinear(p, 0.5, 0.5, 0.5));
}

View file

@ -29,7 +29,9 @@ SOURCES += main.cpp \
ColorHSL_Test.cpp \ ColorHSL_Test.cpp \
RenderProgress_Test.cpp \ RenderProgress_Test.cpp \
IntNode_Test.cpp \ IntNode_Test.cpp \
LightingManager_Test.cpp LightingManager_Test.cpp \
GodRaysSampler_Test.cpp \
Interpolation_Test.cpp
HEADERS += \ HEADERS += \
BaseTestCase.h BaseTestCase.h