Improved vegetation rendering
- Added early check on terrain height range during iteration - Added test render of vegetation model - Tweaked basic tree model
This commit is contained in:
parent
cd006e1093
commit
68945111d1
16 changed files with 338 additions and 37 deletions
|
@ -2,10 +2,6 @@
|
||||||
|
|
||||||
#include "PackStream.h"
|
#include "PackStream.h"
|
||||||
|
|
||||||
Disk::Disk()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Disk::Disk(const Vector3 &point, const Vector3 &normal, double radius):
|
Disk::Disk(const Vector3 &point, const Vector3 &normal, double radius):
|
||||||
InfinitePlane(point, normal), radius(radius)
|
InfinitePlane(point, normal), radius(radius)
|
||||||
{
|
{
|
||||||
|
|
|
@ -14,7 +14,7 @@ namespace basics {
|
||||||
class BASICSSHARED_EXPORT Disk: public InfinitePlane
|
class BASICSSHARED_EXPORT Disk: public InfinitePlane
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Disk();
|
Disk() = default;
|
||||||
Disk(const Vector3 &point, const Vector3 &normal, double radius);
|
Disk(const Vector3 &point, const Vector3 &normal, double radius);
|
||||||
|
|
||||||
inline double getRadius() const {return radius;}
|
inline double getRadius() const {return radius;}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include <climits>
|
#include <climits>
|
||||||
#include "SpaceGridIterator.h"
|
#include "SpaceGridIterator.h"
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
SpaceSegment::SpaceSegment(const Vector3& start, const Vector3& end):
|
SpaceSegment::SpaceSegment(const Vector3& start, const Vector3& end):
|
||||||
start(start), end(end)
|
start(start), end(end)
|
||||||
|
@ -58,6 +59,44 @@ bool SpaceSegment::intersectYInterval(double ymin, double ymax)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SpaceSegment::intersectBoundingBox(const SpaceSegment &bbox) const
|
||||||
|
{
|
||||||
|
Vector3 dir = getDirection();
|
||||||
|
// r.dir is unit direction vector of ray
|
||||||
|
double dfx = 1.0 / dir.x;
|
||||||
|
double dfy = 1.0 / dir.y;
|
||||||
|
double dfz = 1.0 / dir.z;
|
||||||
|
// lb is the corner of AABB with minimal coordinates - left bottom, rt is maximal corner
|
||||||
|
// r.org is origin of ray
|
||||||
|
double t1 = (bbox.start.x - start.x) * dfx;
|
||||||
|
double t2 = (bbox.end.x - start.x) * dfx;
|
||||||
|
double t3 = (bbox.start.y - start.y) * dfy;
|
||||||
|
double t4 = (bbox.end.y - start.y) * dfy;
|
||||||
|
double t5 = (bbox.start.z - start.z) * dfz;
|
||||||
|
double t6 = (bbox.end.z - start.z) * dfz;
|
||||||
|
|
||||||
|
double tmin = max(max(min(t1, t2), min(t3, t4)), min(t5, t6));
|
||||||
|
double tmax = min(min(max(t1, t2), max(t3, t4)), max(t5, t6));
|
||||||
|
|
||||||
|
// if tmax < 0, ray (line) is intersecting AABB, but whole AABB is behing us
|
||||||
|
double t;
|
||||||
|
if (tmax < 0.0)
|
||||||
|
{
|
||||||
|
t = tmax;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if tmin > tmax, ray doesn't intersect AABB
|
||||||
|
if (tmin > tmax)
|
||||||
|
{
|
||||||
|
t = tmax;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
t = tmin;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
SpaceSegment SpaceSegment::projectedOnXPlane(double x) const
|
SpaceSegment SpaceSegment::projectedOnXPlane(double x) const
|
||||||
{
|
{
|
||||||
return SpaceSegment(Vector3(x, start.y, start.z), Vector3(x, end.y, end.z));
|
return SpaceSegment(Vector3(x, start.y, start.z), Vector3(x, end.y, end.z));
|
||||||
|
|
|
@ -33,6 +33,11 @@ public:
|
||||||
*/
|
*/
|
||||||
bool intersectYInterval(double ymin, double ymax);
|
bool intersectYInterval(double ymin, double ymax);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the segment intersects a bounding box, represented by another segment (crossing diagonal).
|
||||||
|
*/
|
||||||
|
bool intersectBoundingBox(const SpaceSegment &bbox) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a version of this segment, projected on a X plane.
|
* Return a version of this segment, projected on a X plane.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -119,7 +119,6 @@ static void addBranchRecurse(std::vector<CappedCylinder> &branches, const Vector
|
||||||
if (length > 0.1)
|
if (length > 0.1)
|
||||||
{
|
{
|
||||||
int split_count = 3;
|
int split_count = 3;
|
||||||
Vector3 new_base = base.add(direction.scale(length));
|
|
||||||
Matrix4 pivot1 = Matrix4::newRotateAxis(randomizeValue(1.0 - 0.6 * length, 0.9, 1.1), VECTOR_EAST);
|
Matrix4 pivot1 = Matrix4::newRotateAxis(randomizeValue(1.0 - 0.6 * length, 0.9, 1.1), VECTOR_EAST);
|
||||||
Vector3 new_direction = pivot1.multPoint(direction);
|
Vector3 new_direction = pivot1.multPoint(direction);
|
||||||
for (int i = 0; i < split_count; i++)
|
for (int i = 0; i < split_count; i++)
|
||||||
|
@ -127,7 +126,8 @@ static void addBranchRecurse(std::vector<CappedCylinder> &branches, const Vector
|
||||||
Matrix4 pivot2 = Matrix4::newRotateAxis(randomizeValue(M_PI * 2.0 / (double)split_count, 0.9, 1.1), direction);
|
Matrix4 pivot2 = Matrix4::newRotateAxis(randomizeValue(M_PI * 2.0 / (double)split_count, 0.9, 1.1), direction);
|
||||||
new_direction = pivot2.multPoint(new_direction);
|
new_direction = pivot2.multPoint(new_direction);
|
||||||
|
|
||||||
addBranchRecurse(branches, new_base, new_direction, randomizeValue(radius, 0.65, 0.75), randomizeValue(length, 0.55, 0.65));
|
Vector3 new_base = base.add(direction.scale(randomizeValue(length, 0.4, 1.0)));
|
||||||
|
addBranchRecurse(branches, new_base, new_direction, randomizeValue(radius, 0.45, 0.6), randomizeValue(length, 0.55, 0.85));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,7 +140,7 @@ void VegetationModelDefinition::randomize()
|
||||||
foliage_items.clear();
|
foliage_items.clear();
|
||||||
|
|
||||||
// Add trunk and branches
|
// Add trunk and branches
|
||||||
addBranchRecurse(solid_volumes, VECTOR_ZERO, VECTOR_UP, 0.05, 0.5);
|
addBranchRecurse(solid_volumes, VECTOR_ZERO, VECTOR_UP, 0.04, 0.5);
|
||||||
|
|
||||||
// Add foliage groups
|
// Add foliage groups
|
||||||
for (const auto &branch: solid_volumes)
|
for (const auto &branch: solid_volumes)
|
||||||
|
@ -150,7 +150,7 @@ void VegetationModelDefinition::randomize()
|
||||||
{
|
{
|
||||||
double radius = length * 0.5;
|
double radius = length * 0.5;
|
||||||
Vector3 center = branch.getAxis().getOrigin().add(branch.getAxis().getDirection().scale(radius));
|
Vector3 center = branch.getAxis().getOrigin().add(branch.getAxis().getDirection().scale(radius));
|
||||||
foliage_groups.push_back(Sphere(center, radius * 5.0));
|
foliage_groups.push_back(Sphere(center, radius * 3.0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,7 +159,7 @@ void VegetationModelDefinition::randomize()
|
||||||
{
|
{
|
||||||
double radius = 0.15;
|
double radius = 0.15;
|
||||||
Vector3 dir = Vector3::randomInSphere(1.0 - radius);
|
Vector3 dir = Vector3::randomInSphere(1.0 - radius);
|
||||||
Vector3 normal = dir.add(Vector3::randomInSphere(0.4)).add(Vector3(0.0, 0.6, 0.0)).normalize();
|
Vector3 normal = dir.add(Vector3::randomInSphere(0.4)).add(Vector3(0.0, 0.3, 0.0)).normalize();
|
||||||
Disk leaf(dir, normal, randomizeValue(radius, 0.8, 1.0));
|
Disk leaf(dir, normal, randomizeValue(radius, 0.8, 1.0));
|
||||||
foliage_items.push_back(leaf);
|
foliage_items.push_back(leaf);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,12 @@
|
||||||
#include "LightFilter.h"
|
#include "LightFilter.h"
|
||||||
#include "GodRaysSampler.h"
|
#include "GodRaysSampler.h"
|
||||||
#include "Rasterizer.h"
|
#include "Rasterizer.h"
|
||||||
|
#include "SpaceSegment.h"
|
||||||
|
#include "OverlayRasterizer.h"
|
||||||
|
#include "VegetationModelDefinition.h"
|
||||||
|
#include "VegetationInstance.h"
|
||||||
|
#include "VegetationRenderer.h"
|
||||||
|
#include "RayCastingResult.h"
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
|
@ -251,6 +257,50 @@ static void testSunNearHorizon()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void testVegetationModels()
|
||||||
|
{
|
||||||
|
class TestRasterizer: public OverlayRasterizer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TestRasterizer(SoftwareCanvasRenderer *renderer, const VegetationModelDefinition &model):
|
||||||
|
OverlayRasterizer(renderer, renderer->getProgressHelper()),
|
||||||
|
instance(model, VECTOR_ZERO),
|
||||||
|
vegetation(renderer->getVegetationRenderer())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Color processPixel(int, int, double relx, double rely) const override
|
||||||
|
{
|
||||||
|
relx *= 0.75;
|
||||||
|
rely *= 0.75;
|
||||||
|
SpaceSegment segment(Vector3(relx, rely + 0.5, -5.0), Vector3(relx, rely + 0.5, 5.0));
|
||||||
|
RayCastingResult result = vegetation->renderInstance(segment, instance, false, true);
|
||||||
|
return result.hit ? result.hit_color : Color(0.6, 0.7, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
VegetationInstance instance;
|
||||||
|
VegetationRenderer *vegetation;
|
||||||
|
};
|
||||||
|
|
||||||
|
Scenery scenery;
|
||||||
|
scenery.autoPreset(1);
|
||||||
|
scenery.getClouds()->clear();
|
||||||
|
scenery.getTerrain()->propWaterHeight()->setValue(1.0);
|
||||||
|
scenery.getCamera()->setTarget(VECTOR_ZERO);
|
||||||
|
scenery.getCamera()->setLocation(Vector3(0.0, 0.0, -5.0));
|
||||||
|
int width = 800;
|
||||||
|
int height = 800;
|
||||||
|
|
||||||
|
SoftwareCanvasRenderer renderer(&scenery);
|
||||||
|
renderer.setSize(width, height);
|
||||||
|
renderer.setQuality(0.5);
|
||||||
|
|
||||||
|
VegetationModelDefinition model(NULL);
|
||||||
|
renderer.setSoloRasterizer(new TestRasterizer(&renderer, model));
|
||||||
|
|
||||||
|
startTestRender(&renderer, "vegetation_model_basic");
|
||||||
|
}
|
||||||
|
|
||||||
void runTestSuite()
|
void runTestSuite()
|
||||||
{
|
{
|
||||||
testGroundShadowQuality();
|
testGroundShadowQuality();
|
||||||
|
@ -260,4 +310,5 @@ void runTestSuite()
|
||||||
testNearFrustum();
|
testNearFrustum();
|
||||||
testCloudsNearGround();
|
testCloudsNearGround();
|
||||||
testSunNearHorizon();
|
testSunNearHorizon();
|
||||||
|
testVegetationModels();
|
||||||
}
|
}
|
||||||
|
|
52
src/render/software/OverlayRasterizer.cpp
Normal file
52
src/render/software/OverlayRasterizer.cpp
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
#include "OverlayRasterizer.h"
|
||||||
|
|
||||||
|
#include "Color.h"
|
||||||
|
#include "SoftwareRenderer.h"
|
||||||
|
#include "CameraDefinition.h"
|
||||||
|
#include "CanvasFragment.h"
|
||||||
|
|
||||||
|
OverlayRasterizer::OverlayRasterizer(SoftwareRenderer *renderer, RenderProgress *progress):
|
||||||
|
Rasterizer(renderer, progress, 0, COLOR_WHITE)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int OverlayRasterizer::prepareRasterization()
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OverlayRasterizer::rasterizeToCanvas(CanvasPortion *canvas)
|
||||||
|
{
|
||||||
|
double width = (double)renderer->render_camera->getWidth();
|
||||||
|
double height = (double)renderer->render_camera->getHeight();
|
||||||
|
Vector3 topleft = renderer->unprojectPoint(Vector3(height, 0.0, 1.0));
|
||||||
|
Vector3 bottomleft = renderer->unprojectPoint(Vector3(0.0, 0.0, 1.0));
|
||||||
|
Vector3 topright = renderer->unprojectPoint(Vector3(height, width, 1.0));
|
||||||
|
Vector3 bottomright = renderer->unprojectPoint(Vector3(0.0, width, 1.0));
|
||||||
|
|
||||||
|
pushQuad(canvas, topleft, bottomleft, bottomright, topright);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color OverlayRasterizer::shadeFragment(const CanvasFragment &fragment, const CanvasFragment *) const
|
||||||
|
{
|
||||||
|
double width = (double)renderer->render_camera->getWidth() - 1.0;
|
||||||
|
double height = (double)renderer->render_camera->getHeight() - 1.0;
|
||||||
|
double relx;
|
||||||
|
double rely;
|
||||||
|
double x = floor(fragment.getPixel().x);
|
||||||
|
double y = floor(fragment.getPixel().y);
|
||||||
|
|
||||||
|
if (width > height)
|
||||||
|
{
|
||||||
|
relx = 2.0 * ((x - (width - height) * 0.5) / height - 0.5);
|
||||||
|
rely = 2.0 * (y / height - 0.5);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
relx = 2.0 * (x / height - 0.5);
|
||||||
|
rely = 2.0 * ((y - (height - width) * 0.5) / height - 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
return processPixel((int)x, (int)y, relx, rely);
|
||||||
|
}
|
||||||
|
|
35
src/render/software/OverlayRasterizer.h
Normal file
35
src/render/software/OverlayRasterizer.h
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
#ifndef OVERLAYRASTERIZER_H
|
||||||
|
#define OVERLAYRASTERIZER_H
|
||||||
|
|
||||||
|
#include "software_global.h"
|
||||||
|
|
||||||
|
#include "Rasterizer.h"
|
||||||
|
|
||||||
|
namespace paysages {
|
||||||
|
namespace software {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for overlay rasterizer.
|
||||||
|
*
|
||||||
|
* It's a rasterizer that puts a single quad in front of camera, in order to apply a shader on each pixel.
|
||||||
|
*/
|
||||||
|
class SOFTWARESHARED_EXPORT OverlayRasterizer: public Rasterizer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
OverlayRasterizer(SoftwareRenderer *renderer, RenderProgress *progress);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract method to implement to shade each pixel.
|
||||||
|
*/
|
||||||
|
virtual Color processPixel(int x, int y, double relx, double rely) const = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual int prepareRasterization();
|
||||||
|
virtual void rasterizeToCanvas(CanvasPortion *canvas);
|
||||||
|
virtual Color shadeFragment(const CanvasFragment &fragment, const CanvasFragment *previous) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // OVERLAYRASTERIZER_H
|
|
@ -220,3 +220,18 @@ bool TerrainRenderer::applyLightFilter(LightComponent &light, const Vector3 &at)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TerrainRenderer::estimateMinMaxHeight(double x1, double z1, double x2, double z2, double *ymin, double *ymax)
|
||||||
|
{
|
||||||
|
double y1 = getHeight(x1, z1, true);
|
||||||
|
double y2 = getHeight(x2, z2, true);
|
||||||
|
|
||||||
|
// TODO Add quality factor
|
||||||
|
// TODO Use all 4 corners
|
||||||
|
// TODO Apply max slope
|
||||||
|
// TODO Estimate displacement
|
||||||
|
|
||||||
|
std::pair<double, double>minmax = std::minmax(y1, y2);
|
||||||
|
*ymin = minmax.first;
|
||||||
|
*ymax = minmax.second;
|
||||||
|
}
|
||||||
|
|
|
@ -34,6 +34,11 @@ public:
|
||||||
virtual Color getFinalColor(const Vector3 &location, double precision);
|
virtual Color getFinalColor(const Vector3 &location, double precision);
|
||||||
virtual bool applyLightFilter(LightComponent &light, const Vector3 &at) override;
|
virtual bool applyLightFilter(LightComponent &light, const Vector3 &at) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estimate a probable range of altitudes, given a rectangle area.
|
||||||
|
*/
|
||||||
|
void estimateMinMaxHeight(double x1, double z1, double x2, double z2, double *ymin, double *ymax);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SoftwareRenderer *parent;
|
SoftwareRenderer *parent;
|
||||||
TerrainRayWalker *walker_ray;
|
TerrainRayWalker *walker_ray;
|
||||||
|
|
|
@ -113,7 +113,6 @@ VegetationResult VegetationModelRenderer::getResult(const SpaceSegment &segment,
|
||||||
material.shininess = 3.0;
|
material.shininess = 3.0;
|
||||||
material.hardness = 0.3;
|
material.hardness = 0.3;
|
||||||
material.validate();
|
material.validate();
|
||||||
// FIXME Can't use reference to temporary material
|
|
||||||
return VegetationResult(location, normal, material);
|
return VegetationResult(location, normal, material);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -21,8 +21,8 @@
|
||||||
class VegetationGridIterator: public SpaceGridIterator
|
class VegetationGridIterator: public SpaceGridIterator
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
VegetationGridIterator(const SpaceSegment &segment, VegetationRenderer *renderer, VegetationLayerDefinition *layer, bool only_hit):
|
VegetationGridIterator(const SpaceSegment &segment, VegetationRenderer *renderer, bool only_hit):
|
||||||
segment(segment), renderer(renderer), layer(layer), only_hit(only_hit)
|
segment(segment), renderer(renderer), only_hit(only_hit)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,24 +30,12 @@ public:
|
||||||
|
|
||||||
virtual bool onCell(int x, int, int z) override
|
virtual bool onCell(int x, int, int z) override
|
||||||
{
|
{
|
||||||
std::vector<VegetationInstance> instances;
|
result = renderer->getBoundResult(segment, (double)x, (double)z, only_hit);
|
||||||
|
return not result.hit;
|
||||||
layer->getPresence()->collectInstances(&instances, *layer->getModel(), x - 0.5, z - 0.5, x + 0.5, z + 0.5);
|
|
||||||
|
|
||||||
for (auto &instance: instances)
|
|
||||||
{
|
|
||||||
result = renderer->renderInstance(segment, instance, only_hit);
|
|
||||||
if (result.hit)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
const SpaceSegment &segment;
|
const SpaceSegment &segment;
|
||||||
VegetationRenderer *renderer;
|
VegetationRenderer *renderer;
|
||||||
VegetationLayerDefinition *layer;
|
|
||||||
RayCastingResult result;
|
RayCastingResult result;
|
||||||
bool only_hit;
|
bool only_hit;
|
||||||
};
|
};
|
||||||
|
@ -97,17 +85,11 @@ RayCastingResult VegetationRenderer::getResult(const SpaceSegment &segment, bool
|
||||||
{
|
{
|
||||||
if (enabled)
|
if (enabled)
|
||||||
{
|
{
|
||||||
VegetationDefinition *vegetation = parent->getScenery()->getVegetation();
|
// Find instances potentially crossing the segment
|
||||||
int n = vegetation->count();
|
VegetationGridIterator it(segment, this, only_hit);
|
||||||
// TODO Don't stop at first layer, find the nearest hit
|
if (not segment.projectedOnYPlane().iterateOnGrid(it))
|
||||||
for (int i = 0; i < n; i++)
|
|
||||||
{
|
{
|
||||||
// Find instances potentially crossing the segment
|
return it.getResult();
|
||||||
VegetationGridIterator it(segment, this, vegetation->getVegetationLayer(i), only_hit);
|
|
||||||
if (not segment.projectedOnYPlane().iterateOnGrid(it))
|
|
||||||
{
|
|
||||||
return it.getResult();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return RayCastingResult();
|
return RayCastingResult();
|
||||||
}
|
}
|
||||||
|
@ -117,6 +99,42 @@ RayCastingResult VegetationRenderer::getResult(const SpaceSegment &segment, bool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RayCastingResult VegetationRenderer::getBoundResult(const SpaceSegment &segment, double x, double z, bool only_hit, double xsize, double zsize)
|
||||||
|
{
|
||||||
|
// Early check if we may cross any vegetation
|
||||||
|
double ymin, ymax;
|
||||||
|
double vegetation_max_height = 0.0; // TODO
|
||||||
|
parent->getTerrainRenderer()->estimateMinMaxHeight(x, z, x + xsize, z + zsize, &ymin, &ymax);
|
||||||
|
ymax += vegetation_max_height;
|
||||||
|
SpaceSegment bbox(Vector3(x, ymin, z), Vector3(x + xsize, ymax, z + zsize));
|
||||||
|
if (not segment.intersectBoundingBox(bbox)) {
|
||||||
|
return RayCastingResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate all layers and instances
|
||||||
|
VegetationDefinition *vegetation = parent->getScenery()->getVegetation();
|
||||||
|
int n = vegetation->count();
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
VegetationLayerDefinition *layer = vegetation->getVegetationLayer(i);
|
||||||
|
|
||||||
|
std::vector<VegetationInstance> instances;
|
||||||
|
layer->getPresence()->collectInstances(&instances, *layer->getModel(), x, z, x + xsize, z + zsize);
|
||||||
|
|
||||||
|
for (auto &instance: instances)
|
||||||
|
{
|
||||||
|
RayCastingResult result = renderInstance(segment, instance, only_hit);
|
||||||
|
if (result.hit)
|
||||||
|
{
|
||||||
|
// TODO Don't stop at first hit, find the nearest one
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return RayCastingResult();
|
||||||
|
}
|
||||||
|
|
||||||
bool VegetationRenderer::applyLightFilter(LightComponent &light, const Vector3 &at)
|
bool VegetationRenderer::applyLightFilter(LightComponent &light, const Vector3 &at)
|
||||||
{
|
{
|
||||||
if (enabled)
|
if (enabled)
|
||||||
|
|
|
@ -34,6 +34,11 @@ public:
|
||||||
*/
|
*/
|
||||||
RayCastingResult getResult(const SpaceSegment &segment, bool only_hit=false);
|
RayCastingResult getResult(const SpaceSegment &segment, bool only_hit=false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform ray casting on a squared region.
|
||||||
|
*/
|
||||||
|
RayCastingResult getBoundResult(const SpaceSegment &segment, double x, double z, bool only_hit=false, double xsize=1.0, double zsize=1.0);
|
||||||
|
|
||||||
virtual bool applyLightFilter(LightComponent &light, const Vector3 &at) override;
|
virtual bool applyLightFilter(LightComponent &light, const Vector3 &at) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -36,6 +36,7 @@ namespace software {
|
||||||
class WaterRenderer;
|
class WaterRenderer;
|
||||||
|
|
||||||
class Rasterizer;
|
class Rasterizer;
|
||||||
|
class OverlayRasterizer;
|
||||||
class SkyRasterizer;
|
class SkyRasterizer;
|
||||||
class TerrainRasterizer;
|
class TerrainRasterizer;
|
||||||
|
|
||||||
|
|
65
src/tests/OverlayRasterizer_Test.cpp
Normal file
65
src/tests/OverlayRasterizer_Test.cpp
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
#include "BaseTestCase.h"
|
||||||
|
#include "OverlayRasterizer.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include "Scenery.h"
|
||||||
|
#include "SoftwareCanvasRenderer.h"
|
||||||
|
#include "Color.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
double relx;
|
||||||
|
double rely;
|
||||||
|
} PixelCall;
|
||||||
|
|
||||||
|
std::vector<PixelCall> calls;
|
||||||
|
|
||||||
|
class MockOverlayRasterizer: public OverlayRasterizer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MockOverlayRasterizer(SoftwareCanvasRenderer *renderer):
|
||||||
|
OverlayRasterizer(renderer, renderer->getProgressHelper())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Color processPixel(int x, int y, double relx, double rely) const override
|
||||||
|
{
|
||||||
|
PixelCall call = {x, y, relx, rely};
|
||||||
|
calls.push_back(call);
|
||||||
|
return COLOR_BLUE;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void checkCall(const PixelCall &call, int x, int y, double relx, double rely)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(x, call.x);
|
||||||
|
EXPECT_EQ(y, call.y);
|
||||||
|
EXPECT_DOUBLE_EQ(relx, call.relx);
|
||||||
|
EXPECT_DOUBLE_EQ(rely, call.rely);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(OverlayRasterizer, pixelProcessing)
|
||||||
|
{
|
||||||
|
calls.clear();
|
||||||
|
|
||||||
|
Scenery scenery;
|
||||||
|
SoftwareCanvasRenderer renderer(&scenery);
|
||||||
|
renderer.setSize(4, 3);
|
||||||
|
renderer.setSoloRasterizer(new MockOverlayRasterizer(&renderer));
|
||||||
|
renderer.render();
|
||||||
|
|
||||||
|
ASSERT_EQ(12, (int)calls.size());
|
||||||
|
checkCall(calls[0], 0, 0, -1.5, -1.0);
|
||||||
|
checkCall(calls[1], 0, 2, -1.5, 1.0);
|
||||||
|
checkCall(calls[2], 2, 0, 0.5, -1.0);
|
||||||
|
checkCall(calls[3], 2, 2, 0.5, 1.0);
|
||||||
|
checkCall(calls[4], 0, 1, -1.5, 0.0);
|
||||||
|
checkCall(calls[5], 1, 0, -0.5, -1.0);
|
||||||
|
checkCall(calls[6], 1, 1, -0.5, 0.0);
|
||||||
|
checkCall(calls[7], 1, 2, -0.5, 1.0);
|
||||||
|
checkCall(calls[8], 2, 1, 0.5, 0.0);
|
||||||
|
checkCall(calls[9], 3, 0, 1.5, -1.0);
|
||||||
|
checkCall(calls[10], 3, 1, 1.5, 0.0);
|
||||||
|
checkCall(calls[11], 3, 2, 1.5, 1.0);
|
||||||
|
}
|
|
@ -59,3 +59,18 @@ TEST(SpaceSegment, iterateOnGrid_Negative)
|
||||||
ASSERT_EQ(1, (int)it.locations.size());
|
ASSERT_EQ(1, (int)it.locations.size());
|
||||||
EXPECT_VECTOR3_COORDS(it.locations[0], -9.0, -9.0, -9.0);
|
EXPECT_VECTOR3_COORDS(it.locations[0], -9.0, -9.0, -9.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(SpaceSegment, intersectBoundingBox)
|
||||||
|
{
|
||||||
|
SpaceSegment bbox(Vector3(-1.0, -1.0, -1.0), Vector3(1.0, 4.0, 1.0));
|
||||||
|
|
||||||
|
EXPECT_TRUE(SpaceSegment(Vector3(0.0, 0.0, 0.0), Vector3(1.0, 1.0, 1.0)).intersectBoundingBox(bbox));
|
||||||
|
|
||||||
|
EXPECT_TRUE(SpaceSegment(Vector3(-5.0, 0.0, 0.0), Vector3(5.0, 0.0, 0.0)).intersectBoundingBox(bbox));
|
||||||
|
EXPECT_FALSE(SpaceSegment(Vector3(-5.0, -2.0, 0.0), Vector3(5.0, -2.0, 0.0)).intersectBoundingBox(bbox));
|
||||||
|
|
||||||
|
EXPECT_TRUE(SpaceSegment(Vector3(-5.0, 0.0, -5.0), Vector3(5.0, 0.0, 5.0)).intersectBoundingBox(bbox));
|
||||||
|
EXPECT_FALSE(SpaceSegment(Vector3(-5.0, -2.0, 0.0), Vector3(5.0, -2.0, 5.0)).intersectBoundingBox(bbox));
|
||||||
|
|
||||||
|
EXPECT_FALSE(SpaceSegment(Vector3(-10.0, 0.0, 0.0), Vector3(10.0, -6.0, 0.0)).intersectBoundingBox(bbox));
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue