Merge branch 'vegetation'

This commit is contained in:
Michaël Lemaire 2015-12-13 17:48:26 +01:00
commit 5d24edad32
92 changed files with 2591 additions and 214 deletions

View file

@ -3,42 +3,45 @@
#include "Vector3.h" #include "Vector3.h"
#include "PackStream.h" #include "PackStream.h"
CappedCylinder::CappedCylinder() {
}
CappedCylinder::CappedCylinder(const Vector3 &base, const Vector3 &direction, double radius, double length) CappedCylinder::CappedCylinder(const Vector3 &base, const Vector3 &direction, double radius, double length)
: InfiniteCylinder(InfiniteRay(base, direction), radius), length(length) { : InfiniteCylinder(InfiniteRay(base, direction), radius), length(length),
container(base.add(direction.scale(length * 0.5)), length * 0.5) {
} }
int CappedCylinder::checkRayIntersection(const InfiniteRay &ray, Vector3 *first_intersection, int CappedCylinder::findRayIntersection(const InfiniteRay &ray, Vector3 *first_intersection,
Vector3 *second_intersection) const { Vector3 *second_intersection) const {
// TODO Apply the caps if (not container.checkRayIntersection(ray)) {
int count = InfiniteCylinder::checkRayIntersection(ray, first_intersection, second_intersection); // We don't hit the containing sphere at all
if (count == 0) {
return 0; return 0;
} else if (count == 2) { } else {
if (checkPointProjection(first_intersection)) { // TODO Apply the caps
if (checkPointProjection(second_intersection)) { int count = InfiniteCylinder::findRayIntersection(ray, first_intersection, second_intersection);
return 2;
if (count == 0) {
return 0;
} else if (count == 2) {
if (checkPointProjection(first_intersection)) {
if (checkPointProjection(second_intersection)) {
return 2;
} else {
return 1;
}
} else { } else {
return 1; if (checkPointProjection(second_intersection)) {
*first_intersection = *second_intersection;
return 1;
} else {
return 0;
}
} }
} else { } else {
if (checkPointProjection(second_intersection)) { // count == 1
*first_intersection = *second_intersection; if (checkPointProjection(first_intersection)) {
return 1; return 1;
} else { } else {
return 0; return 0;
} }
} }
} else // count == 1
{
if (checkPointProjection(first_intersection)) {
return 1;
} else {
return 0;
}
} }
} }

View file

@ -4,6 +4,7 @@
#include "basics_global.h" #include "basics_global.h"
#include "InfiniteCylinder.h" #include "InfiniteCylinder.h"
#include "Sphere.h"
namespace paysages { namespace paysages {
namespace basics { namespace basics {
@ -13,7 +14,7 @@ namespace basics {
*/ */
class BASICSSHARED_EXPORT CappedCylinder : public InfiniteCylinder { class BASICSSHARED_EXPORT CappedCylinder : public InfiniteCylinder {
public: public:
CappedCylinder(); CappedCylinder() = default;
CappedCylinder(const Vector3 &base, const Vector3 &direction, double radius, double length); CappedCylinder(const Vector3 &base, const Vector3 &direction, double radius, double length);
inline double getLength() const { inline double getLength() const {
@ -22,8 +23,10 @@ class BASICSSHARED_EXPORT CappedCylinder : public InfiniteCylinder {
/** /**
* Check the intersection between the cylinder and an infinite ray. * Check the intersection between the cylinder and an infinite ray.
*
* Returns the number of intersections (0, 1 or 2) and fill the intersection points.
*/ */
int checkRayIntersection(const InfiniteRay &ray, Vector3 *first_intersection, Vector3 *second_intersection) const; int findRayIntersection(const InfiniteRay &ray, Vector3 *first_intersection, Vector3 *second_intersection) const;
/** /**
* Check if a point projects in the length of the finite cylinder. * Check if a point projects in the length of the finite cylinder.
@ -35,6 +38,7 @@ class BASICSSHARED_EXPORT CappedCylinder : public InfiniteCylinder {
private: private:
double length; double length;
Sphere container;
}; };
} }
} }

View file

@ -132,6 +132,12 @@ double Color::normalize() {
return max;*/ return max;*/
} }
Color Color::normalized() {
Color col = *this;
col.normalize();
return col;
}
double Color::getValue() const { double Color::getValue() const {
double max; double max;

View file

@ -27,6 +27,7 @@ class BASICSSHARED_EXPORT Color {
void mask(const Color &mask); void mask(const Color &mask);
double normalize(); double normalize();
Color normalized();
double getValue() const; double getValue() const;
double getPower() const; double getPower() const;
void limitPower(double max_power); void limitPower(double max_power);

View file

@ -2,9 +2,6 @@
#include "PackStream.h" #include "PackStream.h"
Disk::Disk() {
}
Disk::Disk(const Vector3 &point, const Vector3 &normal, double radius) : InfinitePlane(point, normal), radius(radius) { Disk::Disk(const Vector3 &point, const Vector3 &normal, double radius) : InfinitePlane(point, normal), radius(radius) {
radius2 = radius * radius; radius2 = radius * radius;
} }

View file

@ -13,7 +13,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 { inline double getRadius() const {

View file

@ -5,23 +5,85 @@
#define EPS 1E-8 #define EPS 1E-8
InfiniteCylinder::InfiniteCylinder() {
}
InfiniteCylinder::InfiniteCylinder(const InfiniteRay &axis, double radius) : axis(axis), radius(radius) { InfiniteCylinder::InfiniteCylinder(const InfiniteRay &axis, double radius) : axis(axis), radius(radius) {
validate();
} }
int InfiniteCylinder::checkRayIntersection(const InfiniteRay &ray, Vector3 *first_intersection, int InfiniteCylinder::findRayIntersection(const InfiniteRay &ray, Vector3 *first_intersection,
Vector3 *second_intersection) const { Vector3 *second_intersection) const {
/* /*
* Original algorithm has been altered, because it didn't work with non-(0,0,0) axis origin. * Original algorithm has been altered, because it didn't work with non-(0,0,0) axis origin.
* Maybe some optimizations could be made from this. * Maybe some optimizations could be made from this.
*/ */
Vector3 Q, G, AG, AQ;
double c0, c1, c2, discr, invC2, root0, root1, root;
Vector3 U, V, F = axis.getDirection(), P, B, Q, G, AG, AQ; /* line */
Q = ray.getOrigin().sub(axis.getOrigin());
G = ray.getDirection();
/* compute A*G */
AG.x = A[0][0] * G.x + A[0][1] * G.y + A[0][2] * G.z;
AG.y = A[1][0] * G.x + A[1][1] * G.y + A[1][2] * G.z;
AG.z = A[2][0] * G.x + A[2][1] * G.y + A[2][2] * G.z;
/* compute A*Q */
AQ.x = A[0][0] * Q.x + A[0][1] * Q.y + A[0][2] * Q.z;
AQ.y = A[1][0] * Q.x + A[1][1] * Q.y + A[1][2] * Q.z;
AQ.z = A[2][0] * Q.x + A[2][1] * Q.y + A[2][2] * Q.z;
/* compute quadratic equation c0+c1*t+c2*t^2 = 0 */
c2 = G.x * AG.x + G.y * AG.y + G.z * AG.z;
c1 = 2.0f * (Q.x * AG.x + Q.y * AG.y + Q.z * AG.z);
c0 = Q.x * AQ.x + Q.y * AQ.y + Q.z * AQ.z + C;
/* solve for intersections */
int numIntersections;
discr = c1 * c1 - 4.0 * c0 * c2;
if (discr > EPS) {
numIntersections = 2;
discr = sqrt(discr);
invC2 = 1.0 / c2;
root0 = -0.5 * (c1 + discr) * invC2;
root1 = -0.5 * (c1 - discr) * invC2;
first_intersection->x = Q.x + root0 * G.x + ox;
first_intersection->y = Q.y + root0 * G.y + oy;
first_intersection->z = Q.z + root0 * G.z + oz;
second_intersection->x = Q.x + root1 * G.x + ox;
second_intersection->y = Q.y + root1 * G.y + oy;
second_intersection->z = Q.z + root1 * G.z + oz;
} else if (discr < -EPS) {
numIntersections = 0;
} else {
numIntersections = 1;
root = -0.5 * c1 / c2;
first_intersection->x = Q.x + root * G.x + ox;
first_intersection->y = Q.y + root * G.y + oy;
first_intersection->z = Q.z + root * G.z + oz;
}
return numIntersections;
}
void InfiniteCylinder::save(PackStream *stream) const {
axis.save(stream);
stream->write(&radius);
}
void InfiniteCylinder::load(PackStream *stream) {
axis.load(stream);
stream->read(&radius);
validate();
}
void InfiniteCylinder::validate() {
Vector3 U, V, F = axis.getDirection();
double length, invLength, prod; double length, invLength, prod;
double R[3][3], A[3][3];
double e0, e1, C, c0, c1, c2, discr, invC2, root0, root1, root; ox = axis.getOrigin().x;
oy = axis.getOrigin().y;
oz = axis.getOrigin().z;
/* choose U, V so that U,V,F is orthonormal set */ /* choose U, V so that U,V,F is orthonormal set */
@ -71,75 +133,6 @@ int InfiniteCylinder::checkRayIntersection(const InfiniteRay &ray, Vector3 *firs
A[2][1] = R[0][2] * R[0][1] + R[1][2] * R[1][1]; A[2][1] = R[0][2] * R[0][1] + R[1][2] * R[1][1];
A[2][2] = R[0][2] * R[0][2] + R[1][2] * R[1][2]; A[2][2] = R[0][2] * R[0][2] + R[1][2] * R[1][2];
/* vector B */
P = Vector3(0.0, 0.0, 0.0);
B.x = -2.0 * P.x;
B.y = -2.0 * P.y;
B.z = -2.0 * P.z;
/* constant C */ /* constant C */
e0 = -2.0 * (R[0][0] * P.x + R[0][1] * P.y + R[0][2] * P.z); C = -radius * radius;
e1 = -2.0 * (R[1][0] * P.x + R[1][1] * P.y + R[1][2] * P.z);
C = 0.25 * (e0 * e0 + e1 * e1) - radius * radius;
/* line */
Q = ray.getOrigin().sub(axis.getOrigin());
G = ray.getDirection();
/* compute A*G */
AG.x = A[0][0] * G.x + A[0][1] * G.y + A[0][2] * G.z;
AG.y = A[1][0] * G.x + A[1][1] * G.y + A[1][2] * G.z;
AG.z = A[2][0] * G.x + A[2][1] * G.y + A[2][2] * G.z;
/* compute A*Q */
AQ.x = A[0][0] * Q.x + A[0][1] * Q.y + A[0][2] * Q.z;
AQ.y = A[1][0] * Q.x + A[1][1] * Q.y + A[1][2] * Q.z;
AQ.z = A[2][0] * Q.x + A[2][1] * Q.y + A[2][2] * Q.z;
/* compute quadratic equation c0+c1*t+c2*t^2 = 0 */
c2 = G.x * AG.x + G.y * AG.y + G.z * AG.z;
c1 = B.x * G.x + B.y * G.y + B.z * G.z + 2.0f * (Q.x * AG.x + Q.y * AG.y + Q.z * AG.z);
c0 = Q.x * AQ.x + Q.y * AQ.y + Q.z * AQ.z + B.x * Q.x + B.y * Q.y + B.z * Q.z + C;
/* solve for intersections */
int numIntersections;
discr = c1 * c1 - 4.0 * c0 * c2;
if (discr > EPS) {
numIntersections = 2;
discr = sqrt(discr);
invC2 = 1.0 / c2;
root0 = -0.5 * (c1 + discr) * invC2;
root1 = -0.5 * (c1 - discr) * invC2;
first_intersection->x = Q.x + root0 * G.x;
first_intersection->y = Q.y + root0 * G.y;
first_intersection->z = Q.z + root0 * G.z;
second_intersection->x = Q.x + root1 * G.x;
second_intersection->y = Q.y + root1 * G.y;
second_intersection->z = Q.z + root1 * G.z;
*first_intersection = first_intersection->add(axis.getOrigin());
*second_intersection = second_intersection->add(axis.getOrigin());
} else if (discr < -EPS) {
numIntersections = 0;
} else {
numIntersections = 1;
root = -0.5 * c1 / c2;
first_intersection->x = Q.x + root * G.x;
first_intersection->y = Q.y + root * G.y;
first_intersection->z = Q.z + root * G.z;
*first_intersection = first_intersection->add(axis.getOrigin());
}
return numIntersections;
}
void InfiniteCylinder::save(PackStream *stream) const {
axis.save(stream);
stream->write(&radius);
}
void InfiniteCylinder::load(PackStream *stream) {
axis.load(stream);
stream->read(&radius);
} }

View file

@ -13,7 +13,7 @@ namespace basics {
*/ */
class BASICSSHARED_EXPORT InfiniteCylinder { class BASICSSHARED_EXPORT InfiniteCylinder {
public: public:
InfiniteCylinder(); InfiniteCylinder() = default;
InfiniteCylinder(const InfiniteRay &axis, double radius); InfiniteCylinder(const InfiniteRay &axis, double radius);
inline const InfiniteRay &getAxis() const { inline const InfiniteRay &getAxis() const {
@ -28,14 +28,26 @@ class BASICSSHARED_EXPORT InfiniteCylinder {
* *
* Returns the number of intersections (0, 1 or 2) and fill the intersection points. * Returns the number of intersections (0, 1 or 2) and fill the intersection points.
*/ */
int checkRayIntersection(const InfiniteRay &ray, Vector3 *first_intersection, Vector3 *second_intersection) const; int findRayIntersection(const InfiniteRay &ray, Vector3 *first_intersection, Vector3 *second_intersection) const;
virtual void save(PackStream *stream) const; virtual void save(PackStream *stream) const;
virtual void load(PackStream *stream); virtual void load(PackStream *stream);
private:
void validate();
protected: protected:
InfiniteRay axis; InfiniteRay axis;
double radius; double radius;
private:
// Stored equation factors, to speed up ray intersection
double R[3][3];
double A[3][3];
double C;
double ox;
double oy;
double oz;
}; };
} }
} }

View file

@ -1,8 +1,5 @@
#include "InfiniteRay.h" #include "InfiniteRay.h"
InfiniteRay::InfiniteRay() {
}
InfiniteRay::InfiniteRay(const Vector3 &origin, const Vector3 &direction) InfiniteRay::InfiniteRay(const Vector3 &origin, const Vector3 &direction)
: origin(origin), direction(direction.normalize()) { : origin(origin), direction(direction.normalize()) {
} }

View file

@ -13,7 +13,7 @@ namespace basics {
*/ */
class BASICSSHARED_EXPORT InfiniteRay { class BASICSSHARED_EXPORT InfiniteRay {
public: public:
InfiniteRay(); InfiniteRay() = default;
InfiniteRay(const Vector3 &origin, const Vector3 &direction); InfiniteRay(const Vector3 &origin, const Vector3 &direction);
static InfiniteRay fromPoints(const Vector3 &point1, const Vector3 &point2); static InfiniteRay fromPoints(const Vector3 &point1, const Vector3 &point2);

View file

@ -3,6 +3,7 @@
#include <cmath> #include <cmath>
#include <climits> #include <climits>
#include "SpaceGridIterator.h" #include "SpaceGridIterator.h"
using namespace std;
SpaceSegment::SpaceSegment(const Vector3 &start, const Vector3 &end) : start(start), end(end) { SpaceSegment::SpaceSegment(const Vector3 &start, const Vector3 &end) : start(start), end(end) {
} }
@ -41,6 +42,41 @@ 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));
} }

View file

@ -47,6 +47,11 @@ class BASICSSHARED_EXPORT SpaceSegment {
*/ */
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.
*/ */

View file

@ -4,15 +4,27 @@
#include "PackStream.h" #include "PackStream.h"
#include "InfiniteRay.h" #include "InfiniteRay.h"
Sphere::Sphere() {
}
Sphere::Sphere(const Vector3 &center, double radius) : center(center), radius(radius) { Sphere::Sphere(const Vector3 &center, double radius) : center(center), radius(radius) {
radius2 = radius * radius; radius2 = radius * radius;
} }
int Sphere::checkRayIntersection(const InfiniteRay &ray, Vector3 *first_intersection, int Sphere::checkRayIntersection(const InfiniteRay &ray) const {
Vector3 *second_intersection) const { Vector3 L = ray.getOrigin().sub(center);
double b = 2.0 * ray.getDirection().dotProduct(L);
double c = L.dotProduct(L) - radius2;
double discr = b * b - 4.0 * c;
if (discr < 0) {
return 0;
} else if (discr == 0) {
return 1;
} else {
return 2;
}
}
int Sphere::findRayIntersection(const InfiniteRay &ray, Vector3 *first_intersection,
Vector3 *second_intersection) const {
Vector3 L = ray.getOrigin().sub(center); Vector3 L = ray.getOrigin().sub(center);
double b = 2.0 * ray.getDirection().dotProduct(L); double b = 2.0 * ray.getDirection().dotProduct(L);
double c = L.dotProduct(L) - radius2; double c = L.dotProduct(L) - radius2;

View file

@ -13,7 +13,7 @@ namespace basics {
*/ */
class BASICSSHARED_EXPORT Sphere { class BASICSSHARED_EXPORT Sphere {
public: public:
Sphere(); Sphere() = default;
Sphere(const Vector3 &center, double radius); Sphere(const Vector3 &center, double radius);
inline const Vector3 &getCenter() const { inline const Vector3 &getCenter() const {
@ -24,11 +24,18 @@ class BASICSSHARED_EXPORT Sphere {
} }
/** /**
* Check the intersection between the sphere and an infinite ray. * Check for intersections between the sphere and an infinite ray.
*
* Returns the number of intersections (0, 1 or 2).
*/
int checkRayIntersection(const InfiniteRay &ray) const;
/**
* Get the intersections between the sphere and an infinite ray.
* *
* Returns the number of intersections (0, 1 or 2) and fill the intersection points. * Returns the number of intersections (0, 1 or 2) and fill the intersection points.
*/ */
int checkRayIntersection(const InfiniteRay &ray, Vector3 *first_intersection, Vector3 *second_intersection) const; int findRayIntersection(const InfiniteRay &ray, Vector3 *first_intersection, Vector3 *second_intersection) const;
void save(PackStream *stream) const; void save(PackStream *stream) const;
void load(PackStream *stream); void load(PackStream *stream);

View file

@ -45,7 +45,7 @@ void DefinitionNode::setName(const string &name) {
this->name = name; this->name = name;
} }
Scenery *DefinitionNode::getScenery() { const Scenery *DefinitionNode::getScenery() const {
if (parent) { if (parent) {
return parent->getScenery(); return parent->getScenery();
} else { } else {

View file

@ -31,7 +31,7 @@ class DEFINITIONSHARED_EXPORT DefinitionNode {
return type_name; return type_name;
} }
virtual Scenery *getScenery(); virtual const Scenery *getScenery() const;
inline const DefinitionNode *getParent() const { inline const DefinitionNode *getParent() const {
return parent; return parent;

View file

@ -24,7 +24,7 @@ class DEFINITIONSHARED_EXPORT DefinitionWatcher {
/** /**
* Start watching a path in a definition tree. * Start watching a path in a definition tree.
*/ */
void startWatching(const DefinitionNode *root, const std::string &path, bool init_diff = true); void startWatching(const DefinitionNode *root, const string &path, bool init_diff = true);
}; };
} }
} }

View file

@ -8,6 +8,7 @@
#include "CloudsDefinition.h" #include "CloudsDefinition.h"
#include "TerrainDefinition.h" #include "TerrainDefinition.h"
#include "TexturesDefinition.h" #include "TexturesDefinition.h"
#include "VegetationDefinition.h"
#include "WaterDefinition.h" #include "WaterDefinition.h"
#include "Logs.h" #include "Logs.h"
#include "RandomGenerator.h" #include "RandomGenerator.h"
@ -22,6 +23,7 @@ Scenery::Scenery() : DefinitionNode(NULL, "scenery", "scenery") {
terrain = new TerrainDefinition(this); terrain = new TerrainDefinition(this);
textures = new TexturesDefinition(this); textures = new TexturesDefinition(this);
water = new WaterDefinition(this); water = new WaterDefinition(this);
vegetation = new VegetationDefinition(this);
} }
void Scenery::validate() { void Scenery::validate() {
@ -89,7 +91,7 @@ Scenery::FileOperationResult Scenery::loadGlobal(const string &filepath) {
return FILE_OPERATION_OK; return FILE_OPERATION_OK;
} }
Scenery *Scenery::getScenery() { const Scenery *Scenery::getScenery() const {
return this; return this;
} }
@ -99,6 +101,7 @@ void Scenery::autoPreset(RandomGenerator &random) {
atmosphere->applyPreset(AtmosphereDefinition::ATMOSPHERE_PRESET_CLEAR_DAY, random); atmosphere->applyPreset(AtmosphereDefinition::ATMOSPHERE_PRESET_CLEAR_DAY, random);
water->applyPreset(WaterDefinition::WATER_PRESET_LAKE, random); water->applyPreset(WaterDefinition::WATER_PRESET_LAKE, random);
clouds->applyPreset(CloudsDefinition::CLOUDS_PRESET_PARTLY_CLOUDY, random); clouds->applyPreset(CloudsDefinition::CLOUDS_PRESET_PARTLY_CLOUDY, random);
vegetation->applyPreset(VegetationDefinition::VEGETATION_PRESET_TEMPERATE, random);
camera->setLocation(VECTOR_ZERO); camera->setLocation(VECTOR_ZERO);
camera->setTarget(VECTOR_NORTH); camera->setTarget(VECTOR_NORTH);
@ -154,6 +157,14 @@ void Scenery::getTextures(TexturesDefinition *textures) {
this->textures->copy(textures); this->textures->copy(textures);
} }
void Scenery::setVegetation(VegetationDefinition *vegetation) {
vegetation->copy(this->vegetation);
}
void Scenery::getVegetation(VegetationDefinition *vegetation) {
this->vegetation->copy(vegetation);
}
void Scenery::setWater(WaterDefinition *water) { void Scenery::setWater(WaterDefinition *water) {
water->copy(this->water); water->copy(this->water);
} }

View file

@ -32,7 +32,7 @@ class DEFINITIONSHARED_EXPORT Scenery : public DefinitionNode {
FileOperationResult saveGlobal(const string &filepath) const; FileOperationResult saveGlobal(const string &filepath) const;
FileOperationResult loadGlobal(const string &filepath); FileOperationResult loadGlobal(const string &filepath);
virtual Scenery *getScenery() override; virtual const Scenery *getScenery() const override;
void autoPreset(RandomGenerator &random = RandomGeneratorDefault); void autoPreset(RandomGenerator &random = RandomGeneratorDefault);
void autoPreset(unsigned int seed); void autoPreset(unsigned int seed);
@ -67,6 +67,12 @@ class DEFINITIONSHARED_EXPORT Scenery : public DefinitionNode {
} }
void getTextures(TexturesDefinition *textures); void getTextures(TexturesDefinition *textures);
void setVegetation(VegetationDefinition *Vegetation);
inline VegetationDefinition *getVegetation() const {
return vegetation;
}
void getVegetation(VegetationDefinition *Vegetation);
void setWater(WaterDefinition *water); void setWater(WaterDefinition *water);
inline WaterDefinition *getWater() const { inline WaterDefinition *getWater() const {
return water; return water;
@ -81,6 +87,7 @@ class DEFINITIONSHARED_EXPORT Scenery : public DefinitionNode {
CloudsDefinition *clouds; CloudsDefinition *clouds;
TerrainDefinition *terrain; TerrainDefinition *terrain;
TexturesDefinition *textures; TexturesDefinition *textures;
VegetationDefinition *vegetation;
WaterDefinition *water; WaterDefinition *water;
}; };
} }

View file

@ -16,6 +16,10 @@ SurfaceMaterial::SurfaceMaterial(const Color &color) {
receive_shadows = 1.0; receive_shadows = 1.0;
} }
SurfaceMaterial::SurfaceMaterial(const SurfaceMaterial &other) : SurfaceMaterial(COLOR_BLACK) {
other.copy(this);
}
SurfaceMaterial::~SurfaceMaterial() { SurfaceMaterial::~SurfaceMaterial() {
delete base; delete base;
} }

View file

@ -10,6 +10,7 @@ class DEFINITIONSHARED_EXPORT SurfaceMaterial {
public: public:
SurfaceMaterial(); SurfaceMaterial();
SurfaceMaterial(const Color &color); SurfaceMaterial(const Color &color);
SurfaceMaterial(const SurfaceMaterial &other);
~SurfaceMaterial(); ~SurfaceMaterial();
static const SurfaceMaterial &getDefault(); static const SurfaceMaterial &getDefault();

View file

@ -46,7 +46,7 @@ void TextureLayerDefinition::validate() {
material->validate(); material->validate();
/* Update zone height range */ /* Update zone height range */
Scenery *scenery = getScenery(); const Scenery *scenery = getScenery();
if (scenery) { if (scenery) {
TerrainDefinition *terrain = scenery->getTerrain(); TerrainDefinition *terrain = scenery->getTerrain();
HeightInfo height_info = terrain->getHeightInfo(); HeightInfo height_info = terrain->getHeightInfo();

View file

@ -0,0 +1,37 @@
#include "VegetationDefinition.h"
#include "VegetationLayerDefinition.h"
#include "VegetationModelDefinition.h"
static DefinitionNode *_layer_constructor(Layers *parent, const string &name) {
return new VegetationLayerDefinition(parent, name);
}
VegetationDefinition::VegetationDefinition(DefinitionNode *parent) : Layers(parent, "vegetation", _layer_constructor) {
}
double VegetationDefinition::getMaxHeight() const {
double max_height = 0.0;
int n = getLayerCount();
for (int i = 0; i < n; i++) {
double layer_height = getVegetationLayer(i)->getMaxHeight();
if (layer_height > max_height) {
max_height = layer_height;
}
}
return max_height;
}
void VegetationDefinition::applyPreset(VegetationPreset preset, RandomGenerator &random) {
VegetationLayerDefinition layer(this, "temp");
clear();
/*if (preset == VEGETATION_PRESET_TEMPERATE) {
layer.applyPreset(VegetationLayerDefinition::VEGETATION_BASIC_TREES, random);
layer.setName("Basic tree");
addLayer(layer);
}*/
}

View file

@ -0,0 +1,36 @@
#ifndef VEGETATIONDEFINITION_H
#define VEGETATIONDEFINITION_H
#include "definition_global.h"
#include "Layers.h"
namespace paysages {
namespace definition {
/**
* Definition of all vegetation layers in the scenery.
*/
class DEFINITIONSHARED_EXPORT VegetationDefinition : public Layers {
public:
VegetationDefinition(DefinitionNode *parent);
/**
* Get a vegetation layer by its position.
*/
inline VegetationLayerDefinition *getVegetationLayer(int position) const {
return (VegetationLayerDefinition *)getLayer(position);
}
/**
* Get the max height of all layers assembled.
*/
double getMaxHeight() const;
typedef enum { VEGETATION_PRESET_TEMPERATE } VegetationPreset;
void applyPreset(VegetationPreset preset, RandomGenerator &random = RandomGeneratorDefault);
};
}
}
#endif // VEGETATIONDEFINITION_H

View file

@ -0,0 +1,10 @@
#include "VegetationInstance.h"
VegetationInstance::VegetationInstance(const VegetationModelDefinition &model, const Vector3 &base, double size,
double angle)
: model(model), base(base), size(size), angle(angle) {
}
VegetationInstance VegetationInstance::displace(const Vector3 &location, const Vector3 &) const {
return VegetationInstance(model, location, size, angle);
}

View file

@ -0,0 +1,45 @@
#ifndef VEGETATIONINSTANCE_H
#define VEGETATIONINSTANCE_H
#include "definition_global.h"
#include "Vector3.h"
namespace paysages {
namespace definition {
/**
* Single instance of a vegetation layer (e.g. a single tree).
*
* This is used as potential hit on vegetation lookup.
*/
class DEFINITIONSHARED_EXPORT VegetationInstance {
public:
VegetationInstance(const VegetationModelDefinition &model, const Vector3 &base, double size = 1.0,
double angle = 0.0);
inline const VegetationModelDefinition &getModel() const {
return model;
}
inline const Vector3 &getBase() const {
return base;
}
inline double getSize() const {
return size;
}
/**
* Return a copy of this instance, with terrain displacement applied.
*/
VegetationInstance displace(const Vector3 &location, const Vector3 &normal) const;
private:
const VegetationModelDefinition &model;
Vector3 base;
double size;
double angle;
};
}
}
#endif // VEGETATIONINSTANCE_H

View file

@ -0,0 +1,18 @@
#include "VegetationLayerDefinition.h"
#include "VegetationModelDefinition.h"
#include "VegetationPresenceDefinition.h"
VegetationLayerDefinition::VegetationLayerDefinition(DefinitionNode *parent, const string &name)
: DefinitionNode(parent, name, "vegetationlayer") {
model = new VegetationModelDefinition(this);
presence = new VegetationPresenceDefinition(this);
}
double VegetationLayerDefinition::getMaxHeight() const {
return presence->getMaxHeight();
}
void VegetationLayerDefinition::applyPreset(VegetationLayerPreset, RandomGenerator &random) {
model->randomize(random);
}

View file

@ -0,0 +1,44 @@
#ifndef VEGETATIONLAYERDEFINITION_H
#define VEGETATIONLAYERDEFINITION_H
#include "definition_global.h"
#include "DefinitionNode.h"
namespace paysages {
namespace definition {
/**
* Definition of one vegetation layer.
*/
class DEFINITIONSHARED_EXPORT VegetationLayerDefinition : public DefinitionNode {
public:
VegetationLayerDefinition(DefinitionNode *parent, const string &name);
inline const VegetationPresenceDefinition *getPresence() const {
return presence;
}
inline const VegetationModelDefinition *getModel() const {
return model;
}
double getMaxHeight() const;
typedef enum { VEGETATION_BASIC_TREES } VegetationLayerPreset;
void applyPreset(VegetationLayerPreset preset, RandomGenerator &random = RandomGeneratorDefault);
private:
/**
* Geographic area of presence of this layer.
*/
VegetationPresenceDefinition *presence;
/**
* Base vegetation model to use for this layer.
*/
VegetationModelDefinition *model;
};
}
}
#endif // VEGETATIONLAYERDEFINITION_H

View file

@ -0,0 +1,159 @@
#include "VegetationModelDefinition.h"
#include "VegetationDefinition.h"
#include "RandomGenerator.h"
#include "Matrix4.h"
#include "SurfaceMaterial.h"
#include "Color.h"
#include "PackStream.h"
VegetationModelDefinition::VegetationModelDefinition(DefinitionNode *parent) : DefinitionNode(parent, "model") {
solid_material = new SurfaceMaterial(Color(0.2, 0.15, 0.15));
solid_material->reflection = 0.002;
solid_material->shininess = 1.0;
solid_material->hardness = 0.3;
solid_material->validate();
foliage_material = new SurfaceMaterial(Color(0.4, 0.8, 0.45));
foliage_material->reflection = 0.007;
foliage_material->shininess = 2.0;
foliage_material->hardness = 0.2;
foliage_material->validate();
randomize();
}
VegetationModelDefinition::~VegetationModelDefinition() {
delete solid_material;
delete foliage_material;
}
void VegetationModelDefinition::save(PackStream *stream) const {
int n;
solid_material->save(stream);
foliage_material->save(stream);
n = solid_volumes.size();
stream->write(&n);
for (const auto &solid_volume : solid_volumes) {
solid_volume.save(stream);
}
n = foliage_groups.size();
stream->write(&n);
for (const auto &foliage_group : foliage_groups) {
foliage_group.save(stream);
}
n = foliage_items.size();
stream->write(&n);
for (const auto &foliage_item : foliage_items) {
foliage_item.save(stream);
}
}
void VegetationModelDefinition::load(PackStream *stream) {
int i, n;
solid_material->load(stream);
foliage_material->load(stream);
stream->read(&n);
solid_volumes.clear();
for (i = 0; i < n; i++) {
CappedCylinder solid_volume;
solid_volume.load(stream);
solid_volumes.push_back(solid_volume);
}
stream->read(&n);
foliage_groups.clear();
for (i = 0; i < n; i++) {
Sphere foliage_group;
foliage_group.load(stream);
foliage_groups.push_back(foliage_group);
}
stream->read(&n);
foliage_items.clear();
for (i = 0; i < n; i++) {
Disk foliage_item;
foliage_item.load(stream);
foliage_items.push_back(foliage_item);
}
}
void VegetationModelDefinition::copy(DefinitionNode *destination_) const {
VegetationModelDefinition *destination = (VegetationModelDefinition *)destination_;
solid_material->copy(destination->solid_material);
foliage_material->copy(destination->foliage_material);
destination->solid_volumes.clear();
for (const auto &solid_volume : solid_volumes) {
destination->solid_volumes.push_back(CappedCylinder(solid_volume));
}
destination->foliage_groups.clear();
for (const auto &foliage_group : foliage_groups) {
destination->foliage_groups.push_back(Sphere(foliage_group));
}
destination->foliage_items.clear();
for (const auto &foliage_item : foliage_items) {
destination->foliage_items.push_back(Disk(foliage_item));
}
}
void VegetationModelDefinition::validate() {
}
static inline double randomizeValue(RandomGenerator &random, double base, double min_factor, double max_factor) {
return base * (min_factor + random.genDouble() * (max_factor - min_factor));
}
static void addBranchRecurse(RandomGenerator &random, vector<CappedCylinder> &branches, const Vector3 &base,
const Vector3 &direction, double radius, double length) {
branches.push_back(CappedCylinder(base, direction, radius, length));
if (length > 0.1) {
int split_count = 3;
Matrix4 pivot1 = Matrix4::newRotateAxis(randomizeValue(random, 1.0 - 0.6 * length, 0.9, 1.1), VECTOR_EAST);
Vector3 new_direction = pivot1.multPoint(direction);
for (int i = 0; i < split_count; i++) {
Matrix4 pivot2 =
Matrix4::newRotateAxis(randomizeValue(random, M_PI * 2.0 / (double)split_count, 0.9, 1.1), direction);
new_direction = pivot2.multPoint(new_direction);
Vector3 new_base = base.add(direction.scale(randomizeValue(random, length, 0.4, 1.0)));
if (new_base.add(new_direction).y > 0.1) {
addBranchRecurse(random, branches, new_base, new_direction, randomizeValue(random, radius, 0.45, 0.6),
randomizeValue(random, length, 0.55, 0.85));
}
}
}
}
void VegetationModelDefinition::randomize(RandomGenerator &random) {
// Clear structure
solid_volumes.clear();
foliage_groups.clear();
foliage_items.clear();
// Add trunk and branches
addBranchRecurse(random, solid_volumes, VECTOR_ZERO, VECTOR_UP, randomizeValue(random, 0.05, 0.6, 1.0),
randomizeValue(random, 0.5, 0.8, 1.0));
// Add foliage groups
for (const auto &branch : solid_volumes) {
double length = branch.getLength();
if (length < 0.2) {
double radius = length * 0.5;
Vector3 center = branch.getAxis().getOrigin().add(branch.getAxis().getDirection().scale(radius));
foliage_groups.push_back(Sphere(center, radius * 3.0));
}
}
// Add foliage items
for (int i = 0; i < 30; i++) {
double radius = 0.15;
double scale = randomizeValue(random, radius, 0.5, 1.0);
Vector3 dir = Vector3::randomInSphere(1.0 - radius, false, random);
Vector3 normal = dir.add(Vector3::randomInSphere(0.4, false, random)).add(Vector3(0.0, 0.3, 0.0)).normalize();
Disk leaf(dir, normal, scale);
foliage_items.push_back(leaf);
}
}

View file

@ -0,0 +1,60 @@
#ifndef VEGETATIONMODELDEFINITION_H
#define VEGETATIONMODELDEFINITION_H
#include "definition_global.h"
#include <vector>
#include "DefinitionNode.h"
#include "Sphere.h"
#include "CappedCylinder.h"
#include "Disk.h"
namespace paysages {
namespace definition {
/**
* Model of vegetation.
*/
class DEFINITIONSHARED_EXPORT VegetationModelDefinition : public DefinitionNode {
public:
VegetationModelDefinition(DefinitionNode *parent);
virtual ~VegetationModelDefinition();
inline const SurfaceMaterial &getSolidMaterial() const {
return *solid_material;
}
inline const SurfaceMaterial &getFoliageMaterial() const {
return *foliage_material;
}
inline const vector<CappedCylinder> &getSolidVolumes() const {
return solid_volumes;
}
inline const vector<Sphere> &getFoliageGroups() const {
return foliage_groups;
}
inline const vector<Disk> &getFoliageItems() const {
return foliage_items;
}
virtual void save(PackStream *stream) const override;
virtual void load(PackStream *stream) override;
virtual void copy(DefinitionNode *destination) const override;
virtual void validate() override;
/**
* Randomize the model geometry.
*/
void randomize(RandomGenerator &random = RandomGeneratorDefault);
private:
SurfaceMaterial *solid_material;
SurfaceMaterial *foliage_material;
vector<CappedCylinder> solid_volumes;
vector<Sphere> foliage_groups;
vector<Disk> foliage_items;
};
}
}
#endif // VEGETATIONMODELDEFINITION_H

View file

@ -0,0 +1,62 @@
#include "VegetationPresenceDefinition.h"
#include <cmath>
#include "Scenery.h"
#include "TerrainDefinition.h"
#include "VegetationLayerDefinition.h"
#include "VegetationModelDefinition.h"
#include "VegetationInstance.h"
#include "FloatNode.h"
#include "NoiseNode.h"
#include "NoiseGenerator.h"
VegetationPresenceDefinition::VegetationPresenceDefinition(VegetationLayerDefinition *parent)
: DefinitionNode(parent, "presence") {
noise = new NoiseNode(this);
noise->setLevels(4);
interval = new FloatNode(this, "interval", 0.1);
}
bool VegetationPresenceDefinition::collectInstances(vector<VegetationInstance> *result,
const VegetationModelDefinition &model, double xmin, double zmin,
double xmax, double zmax, bool outcomers) const {
if (outcomers) {
// Expand the area to include outcoming instances
double max_radius = getMaxHeight();
xmin -= max_radius;
zmin -= max_radius;
xmax += max_radius;
zmax += max_radius;
}
bool added = 0;
const NoiseGenerator *generator = noise->getGenerator();
double interval_value = interval->getValue();
double xstart = xmin - fmod(xmin, interval_value);
double zstart = zmin - fmod(zmin, interval_value);
for (double x = xstart; x < xmax; x += interval_value) {
for (double z = zstart; z < zmax; z += interval_value) {
double noise_presence = generator->get2DTotal(x * 0.1, z * 0.1);
if (noise_presence > 0.0) {
double size =
0.1 + 0.2 * fabs(generator->get2DTotal(z * 10.0, x * 10.0)) * (noise_presence * 0.5 + 0.5);
double angle = 3.0 * generator->get2DTotal(-x * 20.0, z * 20.0); // TODO balanced distribution
double xo = x + fabs(generator->get2DTotal(x * 12.0, -z * 12.0));
double zo = z + fabs(generator->get2DTotal(-x * 27.0, -z * 27.0));
if (xo >= xmin and xo < xmax and zo >= zmin and zo < zmax) {
double y = getScenery()->getTerrain()->getInterpolatedHeight(xo, zo, true, true);
result->push_back(VegetationInstance(model, Vector3(xo, y, zo), size, angle));
added++;
}
}
}
}
return added > 0;
}
double VegetationPresenceDefinition::getMaxHeight() const {
return 0.3;
}

View file

@ -0,0 +1,40 @@
#ifndef VEGETATIONPRESENCEDEFINITION_H
#define VEGETATIONPRESENCEDEFINITION_H
#include "definition_global.h"
#include "DefinitionNode.h"
namespace paysages {
namespace definition {
/**
* Definition of the presence of vegetation at locations.
*/
class DEFINITIONSHARED_EXPORT VegetationPresenceDefinition : public DefinitionNode {
public:
VegetationPresenceDefinition(VegetationLayerDefinition *parent);
/**
* Collect instances in a rectangle area.
*
* If *outcomers* is true, the rectangle area will be expanded to included potential instances that does not
*originate
* in the area, but may have leaves in it.
*
* The location vector set in collected instances is based on raw terrain height, without displacement.
* It's the renderer role to apply the correct displacement.
*/
bool collectInstances(vector<VegetationInstance> *result, const VegetationModelDefinition &model, double xmin,
double zmin, double xmax, double zmax, bool outcomers = true) const;
double getMaxHeight() const;
private:
NoiseNode *noise;
FloatNode *interval;
};
}
}
#endif // VEGETATIONPRESENCEDEFINITION_H

View file

@ -35,6 +35,11 @@ class TexturesDefinition;
class TextureLayerDefinition; class TextureLayerDefinition;
class TerrainDefinition; class TerrainDefinition;
class TerrainHeightMap; class TerrainHeightMap;
class VegetationDefinition;
class VegetationLayerDefinition;
class VegetationModelDefinition;
class VegetationPresenceDefinition;
class VegetationInstance;
class PaintedGrid; class PaintedGrid;
class PaintedGridData; class PaintedGridData;
class PaintedGridBrush; class PaintedGridBrush;

View file

@ -32,3 +32,9 @@ else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../../render/softwa
else:unix: LIBS += -L$$OUT_PWD/../../render/software/ -lpaysages_render_software else:unix: LIBS += -L$$OUT_PWD/../../render/software/ -lpaysages_render_software
INCLUDEPATH += $$PWD/../../render/software INCLUDEPATH += $$PWD/../../render/software
DEPENDPATH += $$PWD/../../render/software DEPENDPATH += $$PWD/../../render/software
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../../render/opengl/release/ -lpaysages_render_opengl
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../../render/opengl/debug/ -lpaysages_render_opengl
else:unix: LIBS += -L$$OUT_PWD/../../render/opengl/ -lpaysages_render_opengl
INCLUDEPATH += $$PWD/../../render/opengl
DEPENDPATH += $$PWD/../../render/opengl

View file

@ -17,13 +17,22 @@
#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 "OpenGLVegetationImpostor.h"
#include "Texture2D.h"
#include "RandomGenerator.h" #include "RandomGenerator.h"
#include <sstream> #include <sstream>
#include <iostream>
void startRender(SoftwareCanvasRenderer *renderer, const char *outputpath); void startRender(SoftwareCanvasRenderer *renderer, const char *outputpath);
static void startTestRender(SoftwareCanvasRenderer *renderer, const string &name, int iteration = -1) { static string getFileName(const string &name, int iteration = -1) {
ostringstream stream; ostringstream stream;
stream << "pic_test_" << name; stream << "pic_test_" << name;
@ -35,7 +44,11 @@ static void startTestRender(SoftwareCanvasRenderer *renderer, const string &name
} }
stream << ".png"; stream << ".png";
startRender(renderer, stream.str().data()); return stream.str();
}
static void startTestRender(SoftwareCanvasRenderer *renderer, const string &name, int iteration = -1) {
startRender(renderer, getFileName(name, iteration).data());
} }
static void testGroundShadowQuality() { static void testGroundShadowQuality() {
@ -99,11 +112,11 @@ static void testCloudQuality() {
SoftwareCanvasRenderer renderer(&scenery); SoftwareCanvasRenderer renderer(&scenery);
renderer.setSize(600, 800); renderer.setSize(600, 800);
SkyRasterizer *rasterizer = new SkyRasterizer(&renderer, renderer.getProgressHelper(), 0); SkyRasterizer rasterizer(&renderer, renderer.getProgressHelper(), 0);
renderer.setSoloRasterizer(rasterizer); renderer.setSoloRasterizer(&rasterizer);
for (int i = 0; i < 6; i++) { for (int i = 0; i < 6; i++) {
renderer.setQuality((double)i / 5.0); renderer.setQuality((double)i / 5.0);
rasterizer->setQuality(0.2); rasterizer.setQuality(0.2);
startTestRender(&renderer, "cloud_quality", i); startTestRender(&renderer, "cloud_quality", i);
} }
} }
@ -146,8 +159,8 @@ static void testGodRays() {
TestRenderer renderer(&scenery); TestRenderer renderer(&scenery);
renderer.setSize(500, 300); renderer.setSize(500, 300);
SkyRasterizer *rasterizer = new SkyRasterizer(&renderer, renderer.getProgressHelper(), 0); SkyRasterizer rasterizer(&renderer, renderer.getProgressHelper(), 0);
renderer.setSoloRasterizer(rasterizer); renderer.setSoloRasterizer(&rasterizer);
TestLightFilter filter; TestLightFilter filter;
renderer.getLightingManager()->clearFilters(); renderer.getLightingManager()->clearFilters();
renderer.getLightingManager()->registerFilter(&filter); renderer.getLightingManager()->registerFilter(&filter);
@ -155,7 +168,7 @@ static void testGodRays() {
// quality // quality
for (int i = 0; i < 6; i++) { for (int i = 0; i < 6; i++) {
renderer.setQuality((double)i / 5.0); renderer.setQuality((double)i / 5.0);
rasterizer->setQuality(0.2); rasterizer.setQuality(0.2);
startTestRender(&renderer, "god_rays_quality", i); startTestRender(&renderer, "god_rays_quality", i);
} }
renderer.setQuality(0.5); renderer.setQuality(0.5);
@ -231,6 +244,76 @@ static void testSunNearHorizon() {
} }
} }
static void testVegetationModels() {
class InstanceRenderer : public SoftwareCanvasRenderer, public OverlayRasterizer, public LightFilter {
public:
InstanceRenderer(Scenery *scenery, const VegetationModelDefinition &model)
: SoftwareCanvasRenderer(scenery), OverlayRasterizer(this, this->getProgressHelper()),
instance(model, VECTOR_ZERO), vegetation(renderer->getVegetationRenderer()) {
}
virtual void prepare() override {
SoftwareCanvasRenderer::prepare();
getLightingManager()->clearFilters();
getLightingManager()->registerFilter(this);
// TODO Add filter for vegetation instance (for self shadows)
}
virtual Color applyMediumTraversal(const Vector3 &, const Color &color) override {
return color;
}
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);
}
virtual bool applyLightFilter(LightComponent &light, const Vector3 &at) override {
SpaceSegment segment(at, at.add(light.direction.scale(-5.0)));
RayCastingResult result = vegetation->renderInstance(segment, instance, true, true);
return not result.hit;
}
VegetationInstance instance;
VegetationRenderer *vegetation;
};
Scenery scenery;
scenery.autoPreset(1);
scenery.getClouds()->clear();
scenery.getCamera()->setTarget(VECTOR_ZERO);
scenery.getCamera()->setLocation(Vector3(0.0, 0.0, 5.0));
int width = 800;
int height = 800;
for (int i = 0; i < 10; i++) {
// TODO Make random sequence repeatable
VegetationModelDefinition model(NULL);
InstanceRenderer renderer(&scenery, model);
renderer.setSize(width, height);
renderer.setSoloRasterizer(&renderer);
startTestRender(&renderer, "vegetation_model_basic", i);
}
}
static void testOpenGLVegetationImpostor() {
for (int i = 0; i < 4; i++) {
string filename = getFileName("opengl_vegetation_impostor", i);
cout << "Rendering " << filename << "..." << endl;
Scenery scenery;
scenery.autoPreset(i);
OpenGLVegetationImpostor impostor(128);
VegetationModelDefinition model(NULL);
bool interrupted = false;
impostor.prepareTexture(model, scenery, &interrupted);
impostor.getTexture()->saveToFile(filename);
}
}
void runTestSuite() { void runTestSuite() {
testGroundShadowQuality(); testGroundShadowQuality();
testRasterizationQuality(); testRasterizationQuality();
@ -239,4 +322,6 @@ void runTestSuite() {
testNearFrustum(); testNearFrustum();
testCloudsNearGround(); testCloudsNearGround();
testSunNearHorizon(); testSunNearHorizon();
testVegetationModels();
testOpenGLVegetationImpostor();
} }

View file

@ -63,3 +63,11 @@ void OpenGLPart::updateScenery(bool onlyCommon) {
update(); update();
} }
} }
Scenery *OpenGLPart::getScenery() const {
return renderer->getScenery();
}
OpenGLFunctions *OpenGLPart::getOpenGlFunctions() const {
return renderer->getOpenGlFunctions();
}

View file

@ -40,6 +40,16 @@ class OPENGLSHARED_EXPORT OpenGLPart {
void updateScenery(bool onlyCommon = false); void updateScenery(bool onlyCommon = false);
/**
* Get access to rendered scenery.
*/
Scenery *getScenery() const;
/**
* Get access to OpenGL functions.
*/
OpenGLFunctions *getOpenGlFunctions() const;
inline const string &getName() const { inline const string &getName() const {
return name; return name;
} }

View file

@ -6,7 +6,9 @@
#include "OpenGLSkybox.h" #include "OpenGLSkybox.h"
#include "OpenGLWater.h" #include "OpenGLWater.h"
#include "OpenGLTerrain.h" #include "OpenGLTerrain.h"
#include "OpenGLVegetation.h"
#include "CloudsRenderer.h" #include "CloudsRenderer.h"
#include "VegetationRenderer.h"
#include "Color.h" #include "Color.h"
#include "Scenery.h" #include "Scenery.h"
#include "LightingManager.h" #include "LightingManager.h"
@ -41,6 +43,7 @@ OpenGLRenderer::OpenGLRenderer(Scenery *scenery) : SoftwareRenderer(scenery) {
parts.push_back(skybox = new OpenGLSkybox(this)); parts.push_back(skybox = new OpenGLSkybox(this));
parts.push_back(water = new OpenGLWater(this)); parts.push_back(water = new OpenGLWater(this));
parts.push_back(terrain = new OpenGLTerrain(this)); parts.push_back(terrain = new OpenGLTerrain(this));
parts.push_back(vegetation = new OpenGLVegetation(this));
} }
OpenGLRenderer::~OpenGLRenderer() { OpenGLRenderer::~OpenGLRenderer() {
@ -59,6 +62,15 @@ OpenGLRenderer::~OpenGLRenderer() {
delete shared_state; delete shared_state;
} }
void OpenGLRenderer::prepare() {
SoftwareRenderer::prepare();
getCloudsRenderer()->setEnabled(false);
getLightingManager()->setSpecularity(false);
getGodRaysSampler()->setEnabled(false);
getVegetationRenderer()->setEnabled(false);
}
void OpenGLRenderer::checkForErrors(const string &domain) { void OpenGLRenderer::checkForErrors(const string &domain) {
int error_code; int error_code;
while ((error_code = functions->glGetError()) != GL_NO_ERROR) { while ((error_code = functions->glGetError()) != GL_NO_ERROR) {
@ -77,9 +89,9 @@ void OpenGLRenderer::destroy() {
} }
void OpenGLRenderer::initialize() { void OpenGLRenderer::initialize() {
ready = functions->initializeOpenGLFunctions(); bool init = functions->initializeOpenGLFunctions();
if (ready) { if (init) {
Logs::debug() << "[OpenGL] renderer started (version " << functions->glGetString(GL_VERSION) Logs::debug() << "[OpenGL] renderer started (version " << functions->glGetString(GL_VERSION)
<< ", glsl version " << functions->glGetString(GL_SHADING_LANGUAGE_VERSION) << ")" << endl; << ", glsl version " << functions->glGetString(GL_SHADING_LANGUAGE_VERSION) << ")" << endl;
@ -87,10 +99,6 @@ void OpenGLRenderer::initialize() {
prepare(); prepare();
getCloudsRenderer()->setEnabled(false);
getLightingManager()->setSpecularity(false);
getGodRaysSampler()->setEnabled(false);
for (auto part : parts) { for (auto part : parts) {
part->initialize(); part->initialize();
part->updateScenery(); part->updateScenery();
@ -99,6 +107,7 @@ void OpenGLRenderer::initialize() {
cameraChangeEvent(render_camera); cameraChangeEvent(render_camera);
checkForErrors("initialize"); checkForErrors("initialize");
ready = true;
} else { } else {
Logs::error() << "[OpenGL] Failed to initialize api bindings" << endl; Logs::error() << "[OpenGL] Failed to initialize api bindings" << endl;
} }
@ -111,7 +120,7 @@ void OpenGLRenderer::prepareOpenGLState(bool clear) {
functions->glEnable(GL_CULL_FACE); functions->glEnable(GL_CULL_FACE);
functions->glDepthFunc(GL_LESS); functions->glDepthFunc(GL_LESS);
functions->glDepthMask(1); functions->glDepthMask(GL_TRUE);
functions->glEnable(GL_DEPTH_TEST); functions->glEnable(GL_DEPTH_TEST);
functions->glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); functions->glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
@ -241,6 +250,9 @@ void OpenGLRenderer::cameraChangeEvent(CameraDefinition *camera) {
// Set in shaders // Set in shaders
shared_state->set("cameraLocation", location); shared_state->set("cameraLocation", location);
shared_state->set("viewMatrix", *view_matrix); shared_state->set("viewMatrix", *view_matrix);
// Broadcast to parts
vegetation->cameraChanged(camera);
} }
double OpenGLRenderer::getPrecision(const Vector3 &) { double OpenGLRenderer::getPrecision(const Vector3 &) {

View file

@ -29,6 +29,9 @@ class OPENGLSHARED_EXPORT OpenGLRenderer : public SoftwareRenderer {
inline OpenGLTerrain *getTerrain() const { inline OpenGLTerrain *getTerrain() const {
return terrain; return terrain;
} }
inline OpenGLVegetation *getVegetation() const {
return vegetation;
}
inline bool isDisplayed() const { inline bool isDisplayed() const {
return displayed; return displayed;
} }
@ -39,6 +42,8 @@ class OPENGLSHARED_EXPORT OpenGLRenderer : public SoftwareRenderer {
return stopped; return stopped;
} }
virtual void prepare() override;
/** /**
* Check for errors in OpenGL context. * Check for errors in OpenGL context.
* *
@ -137,6 +142,7 @@ class OPENGLSHARED_EXPORT OpenGLRenderer : public SoftwareRenderer {
OpenGLSkybox *skybox; OpenGLSkybox *skybox;
OpenGLWater *water; OpenGLWater *water;
OpenGLTerrain *terrain; OpenGLTerrain *terrain;
OpenGLVegetation *vegetation;
vector<OpenGLPart *> parts; vector<OpenGLPart *> parts;
}; };

View file

@ -16,11 +16,13 @@ OpenGLShaderProgram::OpenGLShaderProgram(const string &name, OpenGLRenderer *ren
: renderer(renderer), name(name) { : renderer(renderer), name(name) {
program = new QOpenGLShaderProgram(); program = new QOpenGLShaderProgram();
functions = renderer->getOpenGlFunctions(); functions = renderer->getOpenGlFunctions();
state = new OpenGLSharedState();
compiled = false; compiled = false;
} }
OpenGLShaderProgram::~OpenGLShaderProgram() { OpenGLShaderProgram::~OpenGLShaderProgram() {
delete program; delete program;
delete state;
} }
void OpenGLShaderProgram::addVertexSource(const string &path) { void OpenGLShaderProgram::addVertexSource(const string &path) {
@ -87,9 +89,9 @@ void OpenGLShaderProgram::release() {
program->release(); program->release();
} }
void OpenGLShaderProgram::draw(OpenGLVertexArray *vertices, OpenGLSharedState *state) { void OpenGLShaderProgram::draw(OpenGLVertexArray *vertices, OpenGLSharedState *state, int start, int count) {
if (bind(state)) { if (bind(state)) {
vertices->render(functions); vertices->render(functions, start, count);
release(); release();
} }

View file

@ -31,7 +31,7 @@ class OPENGLSHARED_EXPORT OpenGLShaderProgram {
* *
* *state* is optional and may add ponctual variables to the global state. * *state* is optional and may add ponctual variables to the global state.
*/ */
void draw(OpenGLVertexArray *vertices, OpenGLSharedState *state = NULL); void draw(OpenGLVertexArray *vertices, OpenGLSharedState *state = NULL, int start=0, int count=-1);
inline QOpenGLShaderProgram *getProgram() const { inline QOpenGLShaderProgram *getProgram() const {
return program; return program;
@ -42,6 +42,9 @@ class OPENGLSHARED_EXPORT OpenGLShaderProgram {
inline OpenGLRenderer *getRenderer() const { inline OpenGLRenderer *getRenderer() const {
return renderer; return renderer;
} }
inline OpenGLSharedState *getState() const {
return state;
}
protected: protected:
friend class OpenGLVariable; friend class OpenGLVariable;
@ -59,6 +62,8 @@ class OPENGLSHARED_EXPORT OpenGLShaderProgram {
QOpenGLShaderProgram *program; QOpenGLShaderProgram *program;
OpenGLFunctions *functions; OpenGLFunctions *functions;
OpenGLSharedState *state;
string source_vertex; string source_vertex;
string source_fragment; string source_fragment;
}; };

View file

@ -3,7 +3,7 @@
OpenGLSharedState::OpenGLSharedState() { OpenGLSharedState::OpenGLSharedState() {
} }
paysages::opengl::OpenGLSharedState::~OpenGLSharedState() { OpenGLSharedState::~OpenGLSharedState() {
for (const auto &pair : variables) { for (const auto &pair : variables) {
delete pair.second; delete pair.second;
} }

View file

@ -37,6 +37,12 @@ class OPENGLSHARED_EXPORT OpenGLSharedState {
OpenGLVariable *get(const string &name); OpenGLVariable *get(const string &name);
// Shortcuts // Shortcuts
inline void setInt(const string &name, int value) {
get(name)->set(value);
}
inline void set(const string &name, float value) {
get(name)->set(value);
}
inline void set(const string &name, const Texture2D *texture, bool repeat = false, bool color = true) { inline void set(const string &name, const Texture2D *texture, bool repeat = false, bool color = true) {
get(name)->set(texture, repeat, color); get(name)->set(texture, repeat, color);
} }
@ -49,9 +55,6 @@ class OPENGLSHARED_EXPORT OpenGLSharedState {
inline void set(const string &name, const Texture4D *texture, bool repeat = false, bool color = true) { inline void set(const string &name, const Texture4D *texture, bool repeat = false, bool color = true) {
get(name)->set(texture, repeat, color); get(name)->set(texture, repeat, color);
} }
inline void set(const string &name, float value) {
get(name)->set(value);
}
inline void set(const string &name, const Vector3 &vector) { inline void set(const string &name, const Vector3 &vector) {
get(name)->set(vector); get(name)->set(vector);
} }

View file

@ -12,9 +12,9 @@
#include "FloatNode.h" #include "FloatNode.h"
#include "Logs.h" #include "Logs.h"
static const std::string path_daytime = "/atmosphere/daytime"; static const string path_daytime = "/atmosphere/daytime";
static const std::string path_humidity = "/atmosphere/humidity"; static const string path_humidity = "/atmosphere/humidity";
static const std::string path_sun_radius = "/atmosphere/sun_radius"; static const string path_sun_radius = "/atmosphere/sun_radius";
OpenGLSkybox::OpenGLSkybox(OpenGLRenderer *renderer) : OpenGLPart(renderer, "skybox") { OpenGLSkybox::OpenGLSkybox(OpenGLRenderer *renderer) : OpenGLPart(renderer, "skybox") {
program = createShader("skybox"); program = createShader("skybox");

View file

@ -49,6 +49,9 @@ void OpenGLVariable::apply(OpenGLShaderProgram *program, int &texture_unit) {
} }
switch (type) { switch (type) {
case TYPE_INTEGER:
pr->setUniformValue(name.c_str(), value_int);
break;
case TYPE_FLOAT: case TYPE_FLOAT:
pr->setUniformValue(name.c_str(), value_float); pr->setUniformValue(name.c_str(), value_float);
break; break;
@ -217,6 +220,13 @@ void OpenGLVariable::set(const Texture4D *texture, bool repeat, bool color) {
texture_color = color; texture_color = color;
} }
void OpenGLVariable::set(int value) {
assert(type == TYPE_NONE or type == TYPE_INTEGER);
type = TYPE_INTEGER;
value_int = value;
}
void OpenGLVariable::set(float value) { void OpenGLVariable::set(float value) {
assert(type == TYPE_NONE or type == TYPE_FLOAT); assert(type == TYPE_NONE or type == TYPE_FLOAT);

View file

@ -21,6 +21,7 @@ class OpenGLVariable {
TYPE_TEXTURE_2D, TYPE_TEXTURE_2D,
TYPE_TEXTURE_3D, TYPE_TEXTURE_3D,
TYPE_TEXTURE_4D, TYPE_TEXTURE_4D,
TYPE_INTEGER,
TYPE_FLOAT, TYPE_FLOAT,
TYPE_VECTOR3, TYPE_VECTOR3,
TYPE_MATRIX4, TYPE_MATRIX4,
@ -44,6 +45,7 @@ class OpenGLVariable {
void set(const QImage &texture, bool repeat = false, bool color = true); void set(const QImage &texture, bool repeat = false, bool color = true);
void set(const Texture3D *texture, bool repeat = false, bool color = true); void set(const Texture3D *texture, bool repeat = false, bool color = true);
void set(const Texture4D *texture, bool repeat = false, bool color = true); void set(const Texture4D *texture, bool repeat = false, bool color = true);
void set(int value);
void set(float value); void set(float value);
void set(const Vector3 &vector); void set(const Vector3 &vector);
void set(const QVector3D &vector); void set(const QVector3D &vector);
@ -58,6 +60,7 @@ class OpenGLVariable {
string name; string name;
OpenGLVariableType type; OpenGLVariableType type;
int value_int;
float value_float; float value_float;
QColor *value_color; QColor *value_color;
QVector3D *value_vector3; QVector3D *value_vector3;

View file

@ -0,0 +1,153 @@
#include "OpenGLVegetation.h"
#include "OpenGLRenderer.h"
#include "OpenGLShaderProgram.h"
#include "OpenGLVegetationLayer.h"
#include "Thread.h"
#include "Mutex.h"
#include "Scenery.h"
#include "VegetationDefinition.h"
#include "VegetationLayerDefinition.h"
class paysages::opengl::VegetationUpdater : public Thread {
public:
VegetationUpdater(OpenGLVegetation *vegetation) : vegetation(vegetation) {
interrupted = false;
}
void interrupt(bool wait = true) {
interrupted = true;
if (wait) {
join();
}
}
protected:
virtual void run() override {
while (not interrupted) {
vector<OpenGLVegetationLayer *> layers;
vegetation->acquireLayers(layers);
for (auto layer : layers) {
layer->threadedUpdate();
}
vegetation->releaseLayers(layers);
timeSleepMs(100);
}
}
private:
bool interrupted;
OpenGLVegetation *vegetation;
};
OpenGLVegetation::OpenGLVegetation(OpenGLRenderer *renderer) : OpenGLPart(renderer, "vegetation") {
layers_lock = new Mutex();
updater = new VegetationUpdater(this);
enabled = true;
// Watch scenery changes
renderer->getScenery()->getVegetation()->addWatcher(this, true);
}
OpenGLVegetation::~OpenGLVegetation() {
for (auto layer : layers) {
delete layer;
}
layers.clear();
delete layers_lock;
updater->interrupt();
delete updater;
}
void OpenGLVegetation::initialize() {
// Start the threaded updater
updater->start();
// Prepare shader programs
program = createShader("vegetation");
program->addVertexSource("vegetation");
program->addFragmentSource("atmosphere");
program->addFragmentSource("tonemapping");
program->addFragmentSource("ui");
program->addFragmentSource("vegetation");
}
void OpenGLVegetation::update() {
}
void OpenGLVegetation::render() {
if (enabled) {
vector<OpenGLVegetationLayer *> layers;
acquireLayers(layers);
for (auto layer : layers) {
layer->render();
}
releaseLayers(layers);
}
}
void OpenGLVegetation::nodeChanged(const DefinitionNode *node, const DefinitionDiff *) {
if (node->getPath() == "/vegetation") {
updateLayers();
}
}
Scenery *OpenGLVegetation::getScenery() const {
return renderer->getScenery();
}
void OpenGLVegetation::cameraChanged(const CameraDefinition *camera) {
vector<OpenGLVegetationLayer *> layers;
acquireLayers(layers);
for (auto layer : layers) {
layer->setCamera(camera);
}
releaseLayers(layers);
}
void OpenGLVegetation::acquireLayers(vector<OpenGLVegetationLayer *> &layers) {
layers_lock->acquire();
for (auto layer : this->layers) {
// TODO Reference count
layers.push_back(layer);
}
layers_lock->release();
}
void OpenGLVegetation::releaseLayers(const vector<OpenGLVegetationLayer *> &layers) {
// TODO Reference count
}
OpenGLVegetationLayer *OpenGLVegetation::findLayer(VegetationLayerDefinition *layer) {
for (auto &l : layers) {
if (l->getDefinition() == layer) {
return l;
}
}
return NULL;
}
void OpenGLVegetation::setEnabled(bool enabled) {
this->enabled = enabled;
}
void OpenGLVegetation::updateLayers() {
layers_lock->acquire();
// Add missing layers
int n = renderer->getScenery()->getVegetation()->getLayerCount();
for (int i = 0; i < n; i++) {
VegetationLayerDefinition *layer = renderer->getScenery()->getVegetation()->getVegetationLayer(i);
if (!findLayer(layer)) {
layers.push_back(new OpenGLVegetationLayer(this, layer));
}
}
// TODO Mark extraneous layers for deletion
layers_lock->release();
}

View file

@ -0,0 +1,89 @@
#ifndef OPENGLVEGETATION_H
#define OPENGLVEGETATION_H
#include "opengl_global.h"
#include "OpenGLPart.h"
#include "DefinitionWatcher.h"
#include <map>
namespace paysages {
namespace opengl {
class VegetationUpdater;
class OPENGLSHARED_EXPORT OpenGLVegetation : public OpenGLPart, public DefinitionWatcher {
public:
OpenGLVegetation(OpenGLRenderer *renderer);
virtual ~OpenGLVegetation();
inline int getLayerCount() {
return layers.size();
}
inline OpenGLVegetationLayer *getLayer(int i) {
return layers[i];
}
inline OpenGLShaderProgram *getProgram() {
return program;
}
virtual void initialize() override;
virtual void update() override;
virtual void render() override;
virtual void nodeChanged(const DefinitionNode *node, const DefinitionDiff *diff) override;
/**
* Get the currently rendered scenery.
*/
Scenery *getScenery() const;
/**
* Call this when the dynamic camera (not the scenery one) changed.
*/
void cameraChanged(const CameraDefinition *camera);
/**
* Acquire the current layers for processing.
*
* Don't forget to call releaseLayers once done with them.
*
* This will not hold a lock on them, but increment a reference counter to not delete them while in use.
*/
void acquireLayers(vector<OpenGLVegetationLayer *> &layers);
/**
* Release the layers acquired by acquireLayers.
*/
void releaseLayers(const vector<OpenGLVegetationLayer *> &layers);
/**
* Find a rendering layer, by the matching definition layer.
*/
OpenGLVegetationLayer *findLayer(VegetationLayerDefinition *layer);
/**
* Enable or disable the whole vegetation rendering.
*/
void setEnabled(bool enabled);
/**
* Update the *layers* member from the scenery.
*
* This will create missing layers, and mark extraneous ones for deletion.
* This will not update existing layers (they should update themselves by watching their definition).
*/
void updateLayers();
private:
OpenGLShaderProgram *program;
bool enabled;
vector<OpenGLVegetationLayer *> layers;
Mutex *layers_lock;
VegetationUpdater *updater;
};
}
}
#endif // OPENGLVEGETATION_H

View file

@ -0,0 +1,148 @@
#include "OpenGLVegetationImpostor.h"
#include <cassert>
#include "OpenGLShaderProgram.h"
#include "OpenGLSharedState.h"
#include "OpenGLVertexArray.h"
#include "OpenGLVegetationInstance.h"
#include "Texture2D.h"
#include "SoftwareRenderer.h"
#include "Scenery.h"
#include "AtmosphereDefinition.h"
#include "GodRaysSampler.h"
#include "VegetationRenderer.h"
#include "VegetationInstance.h"
#include "RayCastingResult.h"
#include "SpaceSegment.h"
#include "Matrix4.h"
#include "LightingManager.h"
#include "CameraDefinition.h"
// Get the rotation matrix for an impostor grid index
static inline Matrix4 matrixForIndex(int index) {
if (index == 0) {
return Matrix4::newRotateZ(M_PI_2);
} else if (index < 6) {
return Matrix4::newRotateY(M_2PI * (double)(index - 1) * 0.2).mult(Matrix4::newRotateZ(M_PI_4));
} else {
return Matrix4::newRotateY(M_2PI * (double)(index - 6) * 0.1);
}
}
OpenGLVegetationImpostor::OpenGLVegetationImpostor(int partsize) {
int parts = 4;
vertices = new OpenGLVertexArray(true, true);
vertices->setVertexCount(4 * parts * parts);
texture_size = partsize * parts;
texture = new Texture2D(texture_size, texture_size);
texture_changed = false;
state = new OpenGLSharedState();
setVertex(0, 0.0f, 0.0f);
setVertex(1, 0.0f, 1.0f);
setVertex(2, 1.0f, 0.0f);
setVertex(3, 1.0f, 1.0f);
}
OpenGLVegetationImpostor::~OpenGLVegetationImpostor() {
delete vertices;
delete state;
delete texture;
}
void OpenGLVegetationImpostor::render(OpenGLShaderProgram *program, const OpenGLVegetationInstance *instance,
int instance_index, const Vector3 &camera_location) {
if (instance_index == 0 or texture_changed) {
texture_changed = false;
state->set("impostorTexture", texture);
}
int index = getIndex(camera_location, instance->getBase());
state->setInt("index", index);
state->set("offset", instance->getBase());
state->set("size", 2.0 * instance->getSize());
program->draw(vertices, state, index * 4, 4);
}
void OpenGLVegetationImpostor::prepareTexture(const VegetationModelDefinition &model, const Scenery &environment,
bool *interrupt) {
Scenery scenery;
environment.getAtmosphere()->copy(scenery.getAtmosphere());
SoftwareRenderer renderer(&scenery);
// FIXME Self light filtering
renderer.getLightingManager()->clearFilters();
renderer.getGodRaysSampler()->setEnabled(false);
VegetationRenderer *vegetation = renderer.getVegetationRenderer();
VegetationInstance instance(model, VECTOR_ZERO);
int parts = 4;
int partsize = texture_size / parts;
for (int py = 0; py < parts; py++) {
for (int px = 0; px < parts; px++) {
int index = py * parts + px;
Matrix4 rotation = matrixForIndex(index);
Vector3 cam(5.0, 0.0, 0.0);
scenery.getCamera()->setLocation(rotation.multPoint(cam));
scenery.getCamera()->setTarget(VECTOR_ZERO);
renderer.prepare();
int startx = px * partsize;
int starty = py * partsize;
for (int x = 0; x < partsize; x++) {
double dx = (double)x / (double)partsize;
for (int y = 0; y < partsize; y++) {
double dy = (double)y / (double)partsize;
Vector3 near(5.0, dy - 0.5, -(dx - 0.5));
Vector3 far(-5.0, dy - 0.5, -(dx - 0.5));
SpaceSegment segment(rotation.multPoint(near.scale(2.0)).add(VECTOR_UP.scale(0.5)),
rotation.multPoint(far.scale(2.0)).add(VECTOR_UP.scale(0.5)));
RayCastingResult result = vegetation->renderInstance(segment, instance, false, true);
texture->setPixel(startx + x, starty + y,
result.hit ? result.hit_color.normalized() : COLOR_TRANSPARENT);
}
}
}
}
texture_changed = true;
}
int OpenGLVegetationImpostor::getIndex(const Vector3 &camera, const Vector3 &instance) const {
int result;
VectorSpherical diff = camera.sub(instance).toSpherical();
if (diff.theta > 1.0) {
return 0;
} else {
double angle = diff.phi / M_2PI;
if (diff.theta > 0.4) {
angle = (angle >= 0.9) ? 0.0 : (angle + 0.1);
return 1 + (int)(5.0 * angle);
} else {
angle = (angle >= 0.95) ? 0.0 : (angle + 0.05);
return 6 + (int)(10.0 * angle);
}
}
assert(result >= 0 and result <= 16);
return result;
}
void OpenGLVegetationImpostor::setVertex(int i, float u, float v) {
int parts = 4;
for (int py = 0; py < parts; py++) {
for (int px = 0; px < parts; px++) {
int index = py * parts + px;
Matrix4 rotation = matrixForIndex(index);
Vector3 vertex = rotation.multPoint(Vector3(1.0, u, -(v - 0.5)));
vertices->set(index * 4 + i, vertex, u, v);
}
}
}

View file

@ -0,0 +1,50 @@
#ifndef OPENGLVEGETATIONIMPOSTOR_H
#define OPENGLVEGETATIONIMPOSTOR_H
#include "opengl_global.h"
namespace paysages {
namespace opengl {
/**
* A tool to render an "impostor" of a vegetation layer.
*/
class OPENGLSHARED_EXPORT OpenGLVegetationImpostor {
public:
OpenGLVegetationImpostor(int partsize = 64);
~OpenGLVegetationImpostor();
inline const Texture2D *getTexture() const {
return texture;
}
/**
* Render a single instance using this impostor.
*/
void render(OpenGLShaderProgram *program, const OpenGLVegetationInstance *instance, int instance_index,
const Vector3 &camera_location);
/**
* Prepare the texture grid for a given model.
*/
void prepareTexture(const VegetationModelDefinition &model, const Scenery &environment, bool *interrupt);
/**
* Get the impostor grid index for an instance, to face the camera.
*/
int getIndex(const Vector3 &camera, const Vector3 &instance) const;
private:
void setVertex(int i, float u, float v);
private:
OpenGLVertexArray *vertices;
OpenGLSharedState *state;
int texture_size;
bool texture_changed;
Texture2D *texture;
};
}
}
#endif // OPENGLVEGETATIONIMPOSTOR_H

View file

@ -0,0 +1,10 @@
#include "OpenGLVegetationInstance.h"
#include "VegetationInstance.h"
OpenGLVegetationInstance::OpenGLVegetationInstance(const VegetationInstance &wrapped) : wrapped(wrapped) {
}
void OpenGLVegetationInstance::setDistance(double distance) {
this->distance = distance;
}

View file

@ -0,0 +1,43 @@
#ifndef OPENGLVEGETATIONINSTANCE_H
#define OPENGLVEGETATIONINSTANCE_H
#include "opengl_global.h"
#include "VegetationInstance.h"
namespace paysages {
namespace opengl {
/**
* A single instance of vegetation.
*/
class OPENGLSHARED_EXPORT OpenGLVegetationInstance {
public:
OpenGLVegetationInstance(const VegetationInstance &wrapped);
inline const VegetationModelDefinition &getModel() const {
return wrapped.getModel();
}
inline const Vector3 &getBase() const {
return wrapped.getBase();
}
inline double getSize() const {
return wrapped.getSize();
}
inline double getDistance() const {
return distance;
}
/**
* Set the distance to camera, mainly for sorting.
*/
void setDistance(double distance);
private:
VegetationInstance wrapped;
double distance;
};
}
}
#endif // OPENGLVEGETATIONINSTANCE_H

View file

@ -0,0 +1,147 @@
#include "OpenGLVegetationLayer.h"
#include <algorithm>
#include "Vector3.h"
#include "CameraDefinition.h"
#include "Mutex.h"
#include "OpenGLFunctions.h"
#include "OpenGLVegetation.h"
#include "OpenGLVegetationInstance.h"
#include "OpenGLVegetationImpostor.h"
#include "VegetationLayerDefinition.h"
#include "VegetationPresenceDefinition.h"
OpenGLVegetationLayer::OpenGLVegetationLayer(OpenGLVegetation *parent, VegetationLayerDefinition *definition,
bool own_instances)
: parent(parent), definition(definition), own_instances(own_instances) {
lock_instances = new Mutex;
camera_location = new Vector3(0.0, 0.0, 0.0);
impostor = new OpenGLVegetationImpostor();
range = 10.0;
reset();
}
OpenGLVegetationLayer::~OpenGLVegetationLayer() {
delete camera_location;
delete lock_instances;
delete impostor;
}
void OpenGLVegetationLayer::produceInstancesInArea(double xmin, double xmax, double zmin, double zmax,
vector<OpenGLVegetationInstance *> *instances) const {
vector<VegetationInstance> result;
definition->getPresence()->collectInstances(&result, *definition->getModel(), xmin, zmin, xmax, zmax, false);
for (auto raw_instance : result) {
instances->push_back(new OpenGLVegetationInstance(raw_instance));
}
}
static bool isNull(void *ptr) {
return ptr == NULL;
}
static bool compareInstances(OpenGLVegetationInstance *instance1, OpenGLVegetationInstance *instance2) {
return instance1->getDistance() > instance2->getDistance();
}
void OpenGLVegetationLayer::removeInstancesOutsideArea(double xmin, double xmax, double zmin, double zmax,
vector<OpenGLVegetationInstance *> *instances) const {
for (auto &instance : *instances) {
Vector3 base = instance->getBase();
if (base.x < xmin or base.x >= xmax or base.z < zmin or base.z >= zmax) {
if (own_instances) {
delete instance;
}
instance = NULL;
}
}
instances->erase(remove_if(instances->begin(), instances->end(), isNull), instances->end());
}
void OpenGLVegetationLayer::updateInstances() {
// Compute new area around camera
double newxmin, newxmax, newzmin, newzmax;
newxmin = camera_location->x - range;
newxmax = camera_location->x + range;
newzmin = camera_location->z - range;
newzmax = camera_location->z + range;
// Prepare instances where area grew
vector<OpenGLVegetationInstance *> new_instances;
if (newxmin < xmin) {
produceInstancesInArea(newxmin, xmin, newzmin, newzmax, &new_instances);
}
if (newxmax > xmax) {
produceInstancesInArea(xmax, newxmax, newzmin, newzmax, &new_instances);
}
if (newzmin < zmin) {
produceInstancesInArea(xmin, xmax, newzmin, zmin, &new_instances);
}
if (newzmax > zmax) {
produceInstancesInArea(xmin, xmax, zmax, newzmax, &new_instances);
}
// Apply the changes
lock_instances->acquire();
xmin = newxmin;
xmax = newxmax;
zmin = newzmin;
zmax = newzmax;
removeInstancesOutsideArea(xmin, xmax, zmin, zmax, &instances);
instances.insert(instances.end(), new_instances.begin(), new_instances.end());
for (auto instance : instances) {
instance->setDistance(instance->getBase().sub(*camera_location).getNorm());
}
sort(instances.begin(), instances.end(), compareInstances);
lock_instances->release();
}
void OpenGLVegetationLayer::updateImpostor() {
bool interrupted = false;
impostor->prepareTexture(*definition->getModel(), *parent->getScenery(), &interrupted);
}
void OpenGLVegetationLayer::threadedUpdate() {
if (camera_changed) {
camera_changed = false;
updateInstances();
updateImpostor();
}
}
void OpenGLVegetationLayer::render() {
lock_instances->acquire();
// TODO Instanced rendering
int index = 0;
for (auto instance : instances) {
impostor->render(parent->getProgram(), instance, index++, *camera_location);
}
lock_instances->release();
}
void OpenGLVegetationLayer::reset() {
lock_instances->acquire();
camera_changed = true;
xmin = 0.0;
xmax = 0.0;
zmin = 0.0;
zmax = 0.0;
if (own_instances) {
for (auto instance : instances) {
delete instance;
}
}
instances.clear();
lock_instances->release();
}
void OpenGLVegetationLayer::setCamera(const CameraDefinition *camera) {
if (camera_location->sub(camera->getLocation()).getNorm() > 1.0) {
*camera_location = camera->getLocation();
camera_changed = true;
}
}

View file

@ -0,0 +1,96 @@
#ifndef OPENGLVEGETATIONLAYER_H
#define OPENGLVEGETATIONLAYER_H
#include "opengl_global.h"
#include <vector>
namespace paysages {
namespace opengl {
class OPENGLSHARED_EXPORT OpenGLVegetationLayer {
public:
OpenGLVegetationLayer(OpenGLVegetation *parent, VegetationLayerDefinition *definition, bool own_instances = true);
virtual ~OpenGLVegetationLayer();
inline auto getDefinition() const {
return definition;
}
inline int getInstanceCount() const {
return (int)instances.size();
}
/**
* Collect instances in a given area, and add them to the array *instances*.
*
* The array is not checked for already present instances.
*/
virtual void produceInstancesInArea(double xmin, double xmax, double zmin, double zmax,
vector<OpenGLVegetationInstance *> *instances) const;
/**
* Remove instances outside of a given area.
*/
virtual void removeInstancesOutsideArea(double xmin, double xmax, double zmin, double zmax,
vector<OpenGLVegetationInstance *> *instances) const;
/**
* Update the instances list.
*
* This should be called when the camera has moved enough to make a change.
*/
void updateInstances();
/**
* Update the impostor textures.
*
* This should be called when the camera has moved enough to make a change.
*/
void updateImpostor();
/**
* Perform maintenance tasks that can be perform in a thread.
*
* This will be called from a thread separate from the main GUI thread,
* so it should not call OpenGL functions.
*/
void threadedUpdate();
/**
* Render the vegetation layer.
*
* This is called from the GUI thread.
*/
void render();
/**
* Reset to an initial state.
*
* It is only useful (and safe) from unit testing.
*/
void reset();
/**
* Set the current camera in use.
*/
void setCamera(const CameraDefinition *camera);
private:
OpenGLVegetation *parent;
VegetationLayerDefinition *definition;
double xmin;
double xmax;
double zmin;
double zmax;
double range;
bool own_instances;
vector<OpenGLVegetationInstance *> instances;
Mutex *lock_instances;
OpenGLVegetationImpostor *impostor;
Vector3 *camera_location;
bool camera_changed;
};
}
}
#endif // OPENGLVEGETATIONLAYER_H

View file

@ -45,7 +45,7 @@ void OpenGLVertexArray::destroy(OpenGLFunctions *functions) {
} }
} }
void OpenGLVertexArray::render(OpenGLFunctions *functions) { void OpenGLVertexArray::render(OpenGLFunctions *functions, int start, int count) {
if (changed) { if (changed) {
changed = false; changed = false;
update(functions); update(functions);
@ -53,7 +53,7 @@ void OpenGLVertexArray::render(OpenGLFunctions *functions) {
if (vertexcount and vao) { if (vertexcount and vao) {
functions->glBindVertexArray(vao); functions->glBindVertexArray(vao);
functions->glDrawArrays(draw_mode, 0, vertexcount); functions->glDrawArrays(draw_mode, start, count < 0 ? vertexcount : count);
functions->glBindVertexArray(0); functions->glBindVertexArray(0);
} }
} }

View file

@ -34,7 +34,7 @@ class OpenGLVertexArray {
* *
* A shader program must be bound (and uniforms defined) when calling this. * A shader program must be bound (and uniforms defined) when calling this.
*/ */
void render(OpenGLFunctions *functions); void render(OpenGLFunctions *functions, int start=0, int count=-1);
/** /**
* Set the vertex total count. * Set the vertex total count.

View file

@ -14,9 +14,9 @@
#include "IntNode.h" #include "IntNode.h"
#include "Logs.h" #include "Logs.h"
static const std::string path_height = "/terrain/water_height"; static const string path_height = "/terrain/water_height";
static const std::string path_reflection = "/water/reflection"; static const string path_reflection = "/water/reflection";
static const std::string path_model = "/water/model"; static const string path_model = "/water/model";
OpenGLWater::OpenGLWater(OpenGLRenderer *renderer) : OpenGLPart(renderer, "water") { OpenGLWater::OpenGLWater(OpenGLRenderer *renderer) : OpenGLPart(renderer, "water") {
enabled = true; enabled = true;

View file

@ -55,3 +55,7 @@ RESOURCES += \
OTHER_FILES += \ OTHER_FILES += \
shaders/*.frag \ shaders/*.frag \
shaders/*.vert shaders/*.vert
DISTFILES += \
shaders/vegetation.frag \
shaders/vegetation.vert

View file

@ -20,6 +20,10 @@ class OpenGLVertexArray;
class OpenGLSkybox; class OpenGLSkybox;
class OpenGLWater; class OpenGLWater;
class OpenGLTerrain; class OpenGLTerrain;
class OpenGLVegetation;
class OpenGLVegetationLayer;
class OpenGLVegetationInstance;
class OpenGLVegetationImpostor;
class OpenGLTerrainChunk; class OpenGLTerrainChunk;
template <typename Vertex> class VertexArray; template <typename Vertex> class VertexArray;
} }

View file

@ -11,5 +11,7 @@
<file>fadeout.frag</file> <file>fadeout.frag</file>
<file>noise.frag</file> <file>noise.frag</file>
<file>ui.frag</file> <file>ui.frag</file>
<file>vegetation.vert</file>
<file>vegetation.frag</file>
</qresource> </qresource>
</RCC> </RCC>

View file

@ -0,0 +1,16 @@
in vec2 texcoord;
uniform sampler2D impostorTexture;
out vec4 final_color;
void main(void)
{
final_color = texture(impostorTexture, texcoord);
float alpha = final_color.a;
final_color = applyAerialPerspective(final_color);
final_color = applyToneMapping(final_color);
final_color = applyMouseTracking(unprojected, final_color);
final_color.a = alpha;
}

View file

@ -0,0 +1,16 @@
in highp vec3 vertex;
in highp vec2 uv;
uniform highp mat4 viewMatrix;
uniform highp vec3 offset;
uniform float size;
uniform int index;
out vec3 unprojected;
out vec2 texcoord;
uniform float waterOffset;
void main(void)
{
unprojected = offset + size * vertex; // + vec3(0, waterOffset, 0)
texcoord = vec2(0.25 * (uv.s + float(mod(index, 4))), 0.25 * (uv.t + float(index / 4)));
gl_Position = viewMatrix * vec4(unprojected, 1.0);
}

View file

@ -5,7 +5,7 @@
#include "CanvasFragment.h" #include "CanvasFragment.h"
const int MAX_FRAGMENTS_PER_PIXEL = 2; const int MAX_FRAGMENTS_PER_PIXEL = 5;
namespace paysages { namespace paysages {
namespace software { namespace software {

View file

@ -0,0 +1,46 @@
#include "OverlayRasterizer.h"
#include <cmath>
#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() {
setPerspectiveCorrection(false);
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);
}

View file

@ -0,0 +1,33 @@
#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

View file

@ -38,6 +38,7 @@ Rasterizer::Rasterizer(SoftwareRenderer *renderer, RenderProgress *progress, int
interrupted = false; interrupted = false;
backface_culling = false; backface_culling = false;
perspective_correction = true;
triangle_count = 0; triangle_count = 0;
auto_cut_limit = 0.01; auto_cut_limit = 0.01;
@ -52,7 +53,8 @@ void Rasterizer::interrupt() {
interrupted = true; interrupted = true;
} }
void Rasterizer::setQuality(double) { void Rasterizer::setQuality(double quality) {
this->perspective_correction = (quality > 0.4);
} }
void Rasterizer::setColor(const Color &color) { void Rasterizer::setColor(const Color &color) {
@ -63,6 +65,10 @@ void Rasterizer::setBackFaceCulling(bool cull) {
this->backface_culling = cull; this->backface_culling = cull;
} }
void Rasterizer::setPerspectiveCorrection(bool active) {
this->perspective_correction = active;
}
void Rasterizer::setAutoCutLimit(double limit) { void Rasterizer::setAutoCutLimit(double limit) {
this->auto_cut_limit = limit; this->auto_cut_limit = limit;
} }
@ -227,21 +233,32 @@ void Rasterizer::scanGetDiff(ScanPoint *v1, ScanPoint *v2, ScanPoint *result) {
void Rasterizer::scanInterpolate(CameraDefinition *camera, ScanPoint *v1, ScanPoint *diff, double value, void Rasterizer::scanInterpolate(CameraDefinition *camera, ScanPoint *v1, ScanPoint *diff, double value,
ScanPoint *result) { ScanPoint *result) {
Vector3 vec1(v1->pixel.x, v1->pixel.y, v1->pixel.z);
Vector3 vecdiff(diff->pixel.x, diff->pixel.y, diff->pixel.z);
double v1depth = 1.0 / camera->getRealDepth(vec1);
double v2depth = 1.0 / camera->getRealDepth(vec1.add(vecdiff));
double factor = 1.0 / ((1.0 - value) * v1depth + value * v2depth);
result->pixel.x = v1->pixel.x + diff->pixel.x * value; result->pixel.x = v1->pixel.x + diff->pixel.x * value;
result->pixel.y = v1->pixel.y + diff->pixel.y * value; result->pixel.y = v1->pixel.y + diff->pixel.y * value;
result->pixel.z = v1->pixel.z + diff->pixel.z * value; result->pixel.z = v1->pixel.z + diff->pixel.z * value;
result->location.x =
((1.0 - value) * (v1->location.x * v1depth) + value * (v1->location.x + diff->location.x) * v2depth) * factor; if (perspective_correction) {
result->location.y = Vector3 vec1(v1->pixel.x, v1->pixel.y, v1->pixel.z);
((1.0 - value) * (v1->location.y * v1depth) + value * (v1->location.y + diff->location.y) * v2depth) * factor; Vector3 vecdiff(diff->pixel.x, diff->pixel.y, diff->pixel.z);
result->location.z = double v1depth = 1.0 / camera->getRealDepth(vec1);
((1.0 - value) * (v1->location.z * v1depth) + value * (v1->location.z + diff->location.z) * v2depth) * factor; double v2depth = 1.0 / camera->getRealDepth(vec1.add(vecdiff));
double factor = 1.0 / ((1.0 - value) * v1depth + value * v2depth);
result->location.x =
((1.0 - value) * (v1->location.x * v1depth) + value * (v1->location.x + diff->location.x) * v2depth) *
factor;
result->location.y =
((1.0 - value) * (v1->location.y * v1depth) + value * (v1->location.y + diff->location.y) * v2depth) *
factor;
result->location.z =
((1.0 - value) * (v1->location.z * v1depth) + value * (v1->location.z + diff->location.z) * v2depth) *
factor;
} else {
result->location.x = v1->location.x + diff->location.x * value;
result->location.y = v1->location.y + diff->location.y * value;
result->location.z = v1->location.z + diff->location.z * value;
}
result->client = v1->client; result->client = v1->client;
result->front_facing = v1->front_facing; result->front_facing = v1->front_facing;
} }

View file

@ -9,6 +9,7 @@ namespace software {
const int RASTERIZER_CLIENT_SKY = 0; const int RASTERIZER_CLIENT_SKY = 0;
const int RASTERIZER_CLIENT_WATER = 1; const int RASTERIZER_CLIENT_WATER = 1;
const int RASTERIZER_CLIENT_TERRAIN = 2; const int RASTERIZER_CLIENT_TERRAIN = 2;
const int RASTERIZER_CLIENT_VEGETATION = 3;
typedef struct ScanPoint ScanPoint; typedef struct ScanPoint ScanPoint;
typedef struct RenderScanlines RenderScanlines; typedef struct RenderScanlines RenderScanlines;
@ -60,6 +61,7 @@ class SOFTWARESHARED_EXPORT Rasterizer {
void setColor(const Color &color); void setColor(const Color &color);
void setBackFaceCulling(bool cull); void setBackFaceCulling(bool cull);
void setPerspectiveCorrection(bool active);
/** /**
* Reset the internal triangle counter to 0. * Reset the internal triangle counter to 0.
@ -96,6 +98,7 @@ class SOFTWARESHARED_EXPORT Rasterizer {
int triangle_count; int triangle_count;
double auto_cut_limit; double auto_cut_limit;
bool backface_culling; bool backface_culling;
bool perspective_correction;
}; };
} }
} }

View file

@ -6,6 +6,7 @@
#include "TerrainRasterizer.h" #include "TerrainRasterizer.h"
#include "WaterRasterizer.h" #include "WaterRasterizer.h"
#include "SkyRasterizer.h" #include "SkyRasterizer.h"
#include "VegetationRasterizer.h"
#include "CameraDefinition.h" #include "CameraDefinition.h"
#include "ParallelWork.h" #include "ParallelWork.h"
#include "CanvasPortion.h" #include "CanvasPortion.h"
@ -25,9 +26,11 @@ SoftwareCanvasRenderer::SoftwareCanvasRenderer(Scenery *scenery) : SoftwareRende
postprocess_enabled = true; postprocess_enabled = true;
rasterizers.push_back(new SkyRasterizer(this, progress, RASTERIZER_CLIENT_SKY)); rasterizers.push_back(rasterizer_sky = new SkyRasterizer(this, progress, RASTERIZER_CLIENT_SKY));
rasterizers.push_back(new WaterRasterizer(this, progress, RASTERIZER_CLIENT_WATER)); rasterizers.push_back(rasterizer_water = new WaterRasterizer(this, progress, RASTERIZER_CLIENT_WATER));
rasterizers.push_back(new TerrainRasterizer(this, progress, RASTERIZER_CLIENT_TERRAIN)); rasterizers.push_back(rasterizer_terrain = new TerrainRasterizer(this, progress, RASTERIZER_CLIENT_TERRAIN));
rasterizers.push_back(rasterizer_vegetation =
new VegetationRasterizer(this, progress, RASTERIZER_CLIENT_VEGETATION));
current_work = NULL; current_work = NULL;
} }
@ -36,9 +39,10 @@ SoftwareCanvasRenderer::~SoftwareCanvasRenderer() {
delete canvas; delete canvas;
delete progress; delete progress;
for (auto &rasterizer : rasterizers) { delete rasterizer_sky;
delete rasterizer; delete rasterizer_water;
} delete rasterizer_terrain;
delete rasterizer_vegetation;
} }
void SoftwareCanvasRenderer::setQuality(double factor) { void SoftwareCanvasRenderer::setQuality(double factor) {
@ -50,9 +54,6 @@ void SoftwareCanvasRenderer::setQuality(double factor) {
} }
void SoftwareCanvasRenderer::setSoloRasterizer(Rasterizer *rasterizer) { void SoftwareCanvasRenderer::setSoloRasterizer(Rasterizer *rasterizer) {
for (auto &rast : rasterizers) {
delete rast;
}
rasterizers.clear(); rasterizers.clear();
rasterizers.push_back(rasterizer); rasterizers.push_back(rasterizer);
} }

View file

@ -34,21 +34,22 @@ class SOFTWARESHARED_EXPORT SoftwareCanvasRenderer : public SoftwareRenderer {
} }
inline Rasterizer *getSkyRasterizer() const { inline Rasterizer *getSkyRasterizer() const {
return rasterizers[0]; return rasterizer_sky;
} }
inline Rasterizer *getWaterRasterizer() const { inline Rasterizer *getWaterRasterizer() const {
return rasterizers[1]; return rasterizer_water;
} }
inline Rasterizer *getTerrainRasterizer() const { inline Rasterizer *getTerrainRasterizer() const {
return rasterizers[2]; return rasterizer_terrain;
}
inline Rasterizer *getVegetationRasterizer() const {
return rasterizer_vegetation;
} }
virtual void setQuality(double factor) override; virtual void setQuality(double factor) override;
/** /**
* Clear the rasterizers list, and put a single one. * Clear the rasterizers list, and put a single one.
*
* The renderer takes ownership of the rasterizer.
*/ */
void setSoloRasterizer(Rasterizer *rasterizer); void setSoloRasterizer(Rasterizer *rasterizer);
@ -112,7 +113,13 @@ class SOFTWARESHARED_EXPORT SoftwareCanvasRenderer : public SoftwareRenderer {
Canvas *canvas; Canvas *canvas;
int samples; int samples;
vector<Rasterizer *> rasterizers; vector<Rasterizer *> rasterizers;
Rasterizer *rasterizer_sky;
Rasterizer *rasterizer_water;
Rasterizer *rasterizer_terrain;
Rasterizer *rasterizer_vegetation;
bool started; bool started;
bool finished; bool finished;
bool interrupted; bool interrupted;

View file

@ -10,6 +10,7 @@
#include "CloudsDefinition.h" #include "CloudsDefinition.h"
#include "TerrainRenderer.h" #include "TerrainRenderer.h"
#include "TexturesRenderer.h" #include "TexturesRenderer.h"
#include "VegetationRenderer.h"
#include "WaterRenderer.h" #include "WaterRenderer.h"
#include "SkyRasterizer.h" #include "SkyRasterizer.h"
#include "TerrainRasterizer.h" #include "TerrainRasterizer.h"
@ -32,6 +33,7 @@ SoftwareRenderer::SoftwareRenderer(Scenery *scenery) : scenery(scenery) {
clouds_renderer = new CloudsRenderer(this); clouds_renderer = new CloudsRenderer(this);
terrain_renderer = new TerrainRenderer(this); terrain_renderer = new TerrainRenderer(this);
textures_renderer = new TexturesRenderer(this); textures_renderer = new TexturesRenderer(this);
vegetation_renderer = new VegetationRenderer(this);
water_renderer = new WaterRenderer(this); water_renderer = new WaterRenderer(this);
nightsky_renderer = new NightSky(this); nightsky_renderer = new NightSky(this);
@ -42,6 +44,7 @@ SoftwareRenderer::SoftwareRenderer(Scenery *scenery) : scenery(scenery) {
lighting->registerFilter(water_renderer); lighting->registerFilter(water_renderer);
lighting->registerFilter(terrain_renderer); lighting->registerFilter(terrain_renderer);
lighting->registerFilter(vegetation_renderer);
lighting->registerFilter(clouds_renderer); lighting->registerFilter(clouds_renderer);
lighting->registerSource(atmosphere_renderer); lighting->registerSource(atmosphere_renderer);
@ -61,6 +64,7 @@ SoftwareRenderer::~SoftwareRenderer() {
delete clouds_renderer; delete clouds_renderer;
delete terrain_renderer; delete terrain_renderer;
delete textures_renderer; delete textures_renderer;
delete vegetation_renderer;
delete water_renderer; delete water_renderer;
} }

View file

@ -61,6 +61,9 @@ class SOFTWARESHARED_EXPORT SoftwareRenderer {
inline WaterRenderer *getWaterRenderer() const { inline WaterRenderer *getWaterRenderer() const {
return water_renderer; return water_renderer;
} }
inline VegetationRenderer *getVegetationRenderer() const {
return vegetation_renderer;
}
inline NightSky *getNightSky() const { inline NightSky *getNightSky() const {
return nightsky_renderer; return nightsky_renderer;
@ -95,6 +98,7 @@ class SOFTWARESHARED_EXPORT SoftwareRenderer {
TexturesRenderer *textures_renderer; TexturesRenderer *textures_renderer;
WaterRenderer *water_renderer; WaterRenderer *water_renderer;
NightSky *nightsky_renderer; NightSky *nightsky_renderer;
VegetationRenderer *vegetation_renderer;
}; };
} }
} }

View file

@ -14,6 +14,8 @@
TerrainRasterizer::TerrainRasterizer(SoftwareRenderer *renderer, RenderProgress *progress, int client_id) TerrainRasterizer::TerrainRasterizer(SoftwareRenderer *renderer, RenderProgress *progress, int client_id)
: Rasterizer(renderer, progress, client_id, Color(1.0, 0.9, 0.9)) { : Rasterizer(renderer, progress, client_id, Color(1.0, 0.9, 0.9)) {
setBackFaceCulling(true);
yoffset = 0.0;
} }
void TerrainRasterizer::setQuality(double base_chunk_size, double detail_factor, int max_chunk_detail) { void TerrainRasterizer::setQuality(double base_chunk_size, double detail_factor, int max_chunk_detail) {
@ -68,18 +70,36 @@ void TerrainRasterizer::renderQuad(CanvasPortion *canvas, double x, double z, do
ov4.z = z; ov4.z = z;
dv4 = renderer->getTerrainRenderer()->getResult(x + size, z, true, true).location; dv4 = renderer->getTerrainRenderer()->getResult(x + size, z, true, true).location;
if (yoffset != 0.0) {
dv1.y += yoffset;
dv2.y += yoffset;
dv3.y += yoffset;
dv4.y += yoffset;
}
if (dv1.y > water_height || dv2.y > water_height || dv3.y > water_height || dv4.y > water_height) { if (dv1.y > water_height || dv2.y > water_height || dv3.y > water_height || dv4.y > water_height) {
pushDisplacedQuad(canvas, dv1, dv2, dv3, dv4, ov1, ov2, ov3, ov4); pushDisplacedQuad(canvas, dv1, dv2, dv3, dv4, ov1, ov2, ov3, ov4);
} }
} }
void TerrainRasterizer::getChunk(TerrainRasterizer::TerrainChunkInfo *chunk, double x, double z, double size, void TerrainRasterizer::setYOffset(double offset) {
bool displaced) { this->yoffset = offset;
}
void TerrainRasterizer::getChunk(SoftwareRenderer *renderer, TerrainRasterizer::TerrainChunkInfo *chunk, double x,
double z, double size, int displaced) {
chunk->point_nw = renderer->getTerrainRenderer()->getResult(x, z, true, displaced).location; chunk->point_nw = renderer->getTerrainRenderer()->getResult(x, z, true, displaced).location;
chunk->point_sw = renderer->getTerrainRenderer()->getResult(x, z + size, true, displaced).location; chunk->point_sw = renderer->getTerrainRenderer()->getResult(x, z + size, true, displaced).location;
chunk->point_se = renderer->getTerrainRenderer()->getResult(x + size, z + size, true, displaced).location; chunk->point_se = renderer->getTerrainRenderer()->getResult(x + size, z + size, true, displaced).location;
chunk->point_ne = renderer->getTerrainRenderer()->getResult(x + size, z, true, displaced).location; chunk->point_ne = renderer->getTerrainRenderer()->getResult(x + size, z, true, displaced).location;
if (yoffset != 0.0) {
chunk->point_nw.y += yoffset;
chunk->point_sw.y += yoffset;
chunk->point_se.y += yoffset;
chunk->point_ne.y += yoffset;
}
double displacement_power; double displacement_power;
if (displaced) { if (displaced) {
displacement_power = 0.0; displacement_power = 0.0;
@ -136,7 +156,7 @@ int TerrainRasterizer::performTessellation(CanvasPortion *canvas, bool displaced
while (radius_int < 20000.0) { while (radius_int < 20000.0) {
for (i = 0; i < chunk_count - 1; i++) { for (i = 0; i < chunk_count - 1; i++) {
getChunk(&chunk, cx - radius_ext + chunk_size * i, cz - radius_ext, chunk_size, displaced); getChunk(renderer, &chunk, cx - radius_ext + chunk_size * i, cz - radius_ext, chunk_size, displaced);
if (chunk.detail_hint > 0) { if (chunk.detail_hint > 0) {
result += chunk.detail_hint * chunk.detail_hint; result += chunk.detail_hint * chunk.detail_hint;
if (canvas) { if (canvas) {
@ -147,7 +167,7 @@ int TerrainRasterizer::performTessellation(CanvasPortion *canvas, bool displaced
return result; return result;
} }
getChunk(&chunk, cx + radius_int, cz - radius_ext + chunk_size * i, chunk_size, displaced); getChunk(renderer, &chunk, cx + radius_int, cz - radius_ext + chunk_size * i, chunk_size, displaced);
if (chunk.detail_hint > 0) { if (chunk.detail_hint > 0) {
result += chunk.detail_hint * chunk.detail_hint; result += chunk.detail_hint * chunk.detail_hint;
if (canvas) { if (canvas) {
@ -158,7 +178,7 @@ int TerrainRasterizer::performTessellation(CanvasPortion *canvas, bool displaced
return result; return result;
} }
getChunk(&chunk, cx + radius_int - chunk_size * i, cz + radius_int, chunk_size, displaced); getChunk(renderer, &chunk, cx + radius_int - chunk_size * i, cz + radius_int, chunk_size, displaced);
if (chunk.detail_hint > 0) { if (chunk.detail_hint > 0) {
result += chunk.detail_hint * chunk.detail_hint; result += chunk.detail_hint * chunk.detail_hint;
if (canvas) { if (canvas) {
@ -169,7 +189,7 @@ int TerrainRasterizer::performTessellation(CanvasPortion *canvas, bool displaced
return result; return result;
} }
getChunk(&chunk, cx - radius_ext, cz + radius_int - chunk_size * i, chunk_size, displaced); getChunk(renderer, &chunk, cx - radius_ext, cz + radius_int - chunk_size * i, chunk_size, displaced);
if (chunk.detail_hint > 0) { if (chunk.detail_hint > 0) {
result += chunk.detail_hint * chunk.detail_hint; result += chunk.detail_hint * chunk.detail_hint;
if (canvas) { if (canvas) {

View file

@ -36,6 +36,14 @@ class SOFTWARESHARED_EXPORT TerrainRasterizer : public Rasterizer {
virtual void rasterizeToCanvas(CanvasPortion *canvas) override; virtual void rasterizeToCanvas(CanvasPortion *canvas) override;
virtual Color shadeFragment(const CanvasFragment &fragment, const CanvasFragment *previous) const override; virtual Color shadeFragment(const CanvasFragment &fragment, const CanvasFragment *previous) const override;
protected:
/**
* Add a vertical offset to rasterized polygons.
*
* This may be used to rasterize a covering layer on top of ground.
*/
void setYOffset(double offset);
private: private:
/** /**
* Method called for each chunk tessellated by performTessellation. * Method called for each chunk tessellated by performTessellation.
@ -60,9 +68,12 @@ class SOFTWARESHARED_EXPORT TerrainRasterizer : public Rasterizer {
void renderQuad(CanvasPortion *canvas, double x, double z, double size, double water_height); void renderQuad(CanvasPortion *canvas, double x, double z, double size, double water_height);
void getChunk(TerrainRasterizer::TerrainChunkInfo *chunk, double x, double z, double size, bool displaced); void getChunk(SoftwareRenderer *renderer, TerrainRasterizer::TerrainChunkInfo *chunk, double x, double z,
double size, int displaced);
private: private:
double yoffset;
// Quality control // Quality control
double base_chunk_size; double base_chunk_size;
double detail_factor; double detail_factor;

View file

@ -1,5 +1,6 @@
#include "TerrainRenderer.h" #include "TerrainRenderer.h"
#include <algorithm>
#include "SoftwareRenderer.h" #include "SoftwareRenderer.h"
#include "Scenery.h" #include "Scenery.h"
#include "TerrainDefinition.h" #include "TerrainDefinition.h"
@ -185,3 +186,17 @@ 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
pair<double, double> limits = minmax(y1, y2);
*ymin = limits.first;
*ymax = limits.second;
}

View file

@ -32,6 +32,11 @@ class SOFTWARESHARED_EXPORT TerrainRenderer : public LightFilter {
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;

View file

@ -0,0 +1,130 @@
#include "VegetationModelRenderer.h"
#include "Color.h"
#include "SurfaceMaterial.h"
#include "SpaceSegment.h"
#include "InfiniteRay.h"
#include "Disk.h"
#include "SoftwareRenderer.h"
#include "LightComponent.h"
#include "VegetationModelDefinition.h"
#include "VegetationResult.h"
#ifndef NDEBUG
//#define DEBUG_VEGETATION_CONTAINERS 1
#endif
#ifdef DEBUG_VEGETATION_CONTAINERS
SurfaceMaterial DEBUG_MATERIAL1(Color(1.0, 0.0, 0.0));
#endif
VegetationModelRenderer::VegetationModelRenderer(SoftwareRenderer *parent, const VegetationModelDefinition *model)
: parent(parent), model(model) {
}
VegetationModelRenderer::~VegetationModelRenderer() {
}
VegetationResult VegetationModelRenderer::getResult(const SpaceSegment &segment, bool only_hit) const {
InfiniteRay ray(segment.getStart(), segment.getDirection());
int intersections;
const SurfaceMaterial *material = &SurfaceMaterial::getDefault();
bool hit = false;
Vector3 location, normal;
double distance, nearest, maximal;
maximal = segment.getLength();
nearest = maximal;
for (const auto &branch : model->getSolidVolumes()) {
Vector3 near, far;
if (branch.findRayIntersection(ray, &near, &far)) {
distance = ray.getCursor(near);
if (distance >= 0.0 and distance <= maximal) {
// Got a branch hit
if (only_hit) {
return VegetationResult(true);
}
if (distance < nearest) {
material = &model->getSolidMaterial();
nearest = distance;
hit = true;
location = near;
normal = near.sub(branch.getAxis().getOrigin())
.crossProduct(branch.getAxis().getDirection())
.normalize();
normal = branch.getAxis().getDirection().crossProduct(normal).normalize();
}
}
}
}
for (const auto &foliage : model->getFoliageGroups()) {
intersections = foliage.checkRayIntersection(ray);
if (intersections == 2) {
InfiniteRay subray(ray.getOrigin().sub(foliage.getCenter()).scale(1.0 / foliage.getRadius()),
ray.getDirection());
for (const auto &leaf : model->getFoliageItems()) {
Sphere leafcap(leaf.getPoint(), leaf.getRadius() * leaf.getRadius() / foliage.getRadius());
// TODO Add cap intersection to Sphere class
Vector3 near, far;
if (leafcap.findRayIntersection(subray, &near, &far) == 2) {
if (near.sub(leaf.getPoint()).normalize().dotProduct(leaf.getNormal()) < 0.5) {
if (far.sub(leaf.getPoint()).normalize().dotProduct(leaf.getNormal()) < 0.5) {
continue;
} else {
near = far;
}
}
Vector3 capnormal = near.sub(leaf.getPoint()).normalize();
near = near.scale(foliage.getRadius()).add(foliage.getCenter());
distance = ray.getCursor(near);
if (distance >= 0.0 and distance <= maximal) {
// Got a foliage hit
if (only_hit) {
return VegetationResult(true);
}
if (distance < nearest) {
material = &model->getFoliageMaterial();
nearest = distance;
hit = true;
location = near;
normal = capnormal;
if (normal.dotProduct(location.sub(segment.getStart())) > 0.0) {
// We look at backside
normal = normal.scale(-1.0);
}
}
}
}
}
#ifdef DEBUG_VEGETATION_CONTAINERS
if (!hit) {
Vector3 near, far;
intersections = foliage.findRayIntersection(ray, &near, &far);
location = near;
normal = VECTOR_ZERO;
material = &DEBUG_MATERIAL1;
hit = true;
}
#endif
}
}
if (hit) {
return VegetationResult(location, normal, *material);
} else {
return VegetationResult();
}
}
bool VegetationModelRenderer::applyLightFilter(LightComponent &light, const Vector3 &at) {
return getResult(SpaceSegment(at, light.direction.scale(-2.0)), true).isHit();
}

View file

@ -0,0 +1,42 @@
#ifndef VEGETATIONMODELRENDERER_H
#define VEGETATIONMODELRENDERER_H
#include "software_global.h"
#include "LightFilter.h"
namespace paysages {
namespace software {
/**
* Renderer of a single instance of vegetation model (e.g a single tree).
*/
class SOFTWARESHARED_EXPORT VegetationModelRenderer : public LightFilter {
public:
VegetationModelRenderer(SoftwareRenderer *parent, const VegetationModelDefinition *model);
virtual ~VegetationModelRenderer();
/**
* Get the final color of this model on a segment.
*
* Coordinates should be expressed as relative to the model origin point.
*
* If only_hit is True, we only look whether there is a hit or not.
*/
VegetationResult getResult(const SpaceSegment &segment, bool only_hit = false) const;
/**
* Internal (relative) light filter.
*
* The 'at' parameter should be expressed as relative to the model origin point.
*/
virtual bool applyLightFilter(LightComponent &light, const Vector3 &at);
private:
SoftwareRenderer *parent;
const VegetationModelDefinition *model;
};
}
}
#endif // VEGETATIONMODELRENDERER_H

View file

@ -0,0 +1,39 @@
#include "VegetationRasterizer.h"
#include <cassert>
#include "CanvasFragment.h"
#include "Color.h"
#include "SpaceSegment.h"
#include "SoftwareRenderer.h"
#include "VegetationRenderer.h"
#include "RayCastingResult.h"
VegetationRasterizer::VegetationRasterizer(SoftwareRenderer *renderer, RenderProgress *progress, int client_id)
: TerrainRasterizer(renderer, progress, client_id) {
setYOffset(0.5);
setColor(Color(0.8, 1.0, 0.8, 0.5));
}
Color VegetationRasterizer::shadeFragment(const CanvasFragment &fragment, const CanvasFragment *previous) const {
assert(previous != NULL);
if (not fragment.isFrontFacing() or previous->getClient() == RASTERIZER_CLIENT_SKY) {
// This is an exit fragment, or the last before sky
return COLOR_TRANSPARENT;
}
// Even if we assert, this may happen in rare circumstances (no opaque background fragment), so don't crash
if (previous == NULL) {
return COLOR_TRANSPARENT;
}
SoftwareRenderer *renderer = getRenderer();
SpaceSegment segment(renderer->unprojectPoint(fragment.getPixel()), renderer->unprojectPoint(previous->getPixel()));
RayCastingResult result = renderer->getVegetationRenderer()->getResult(segment);
if (result.hit) {
return result.hit_color;
} else {
return COLOR_TRANSPARENT;
}
}

View file

@ -0,0 +1,20 @@
#ifndef VEGETATIONRASTERIZER_H
#define VEGETATIONRASTERIZER_H
#include "software_global.h"
#include "TerrainRasterizer.h"
namespace paysages {
namespace software {
class SOFTWARESHARED_EXPORT VegetationRasterizer : public TerrainRasterizer {
public:
VegetationRasterizer(SoftwareRenderer *renderer, RenderProgress *progress, int client_id);
virtual Color shadeFragment(const CanvasFragment &fragment, const CanvasFragment *previous) const override;
};
}
}
#endif // VEGETATIONRASTERIZER_H

View file

@ -0,0 +1,141 @@
#include "VegetationRenderer.h"
#include "VegetationModelRenderer.h"
#include "RayCastingResult.h"
#include "SpaceGridIterator.h"
#include "SpaceSegment.h"
#include "VegetationInstance.h"
#include "SoftwareRenderer.h"
#include "Scenery.h"
#include "TerrainRenderer.h"
#include "VegetationDefinition.h"
#include "VegetationLayerDefinition.h"
#include "VegetationPresenceDefinition.h"
#include "VegetationInstance.h"
#include "VegetationResult.h"
#include "LightComponent.h"
/**
* Grid iterator to collect instances of a layer, in small squares.
*/
class VegetationGridIterator : public SpaceGridIterator {
public:
VegetationGridIterator(const SpaceSegment &segment, VegetationRenderer *renderer, bool only_hit)
: segment(segment), renderer(renderer), only_hit(only_hit) {
}
inline const RayCastingResult &getResult() const {
return result;
}
virtual bool onCell(int x, int, int z) override {
result = renderer->getBoundResult(segment, (double)x, (double)z, only_hit);
return not result.hit;
}
private:
const SpaceSegment &segment;
VegetationRenderer *renderer;
RayCastingResult result;
bool only_hit;
};
VegetationRenderer::VegetationRenderer(SoftwareRenderer *parent) : parent(parent) {
enabled = true;
}
VegetationRenderer::~VegetationRenderer() {
}
void VegetationRenderer::setEnabled(bool enabled) {
this->enabled = enabled;
}
RayCastingResult VegetationRenderer::renderInstance(const SpaceSegment &segment, const VegetationInstance &instance,
bool only_hit, bool displaced) {
if (!displaced) {
// Recursive call on displaced instance
const Vector3 &base = instance.getBase();
TerrainRenderer::TerrainResult terrain = parent->getTerrainRenderer()->getResult(base.x, base.z, true, true);
VegetationInstance displaced_instance = instance.displace(terrain.location, terrain.normal);
return renderInstance(segment, displaced_instance, only_hit, true);
}
RayCastingResult final;
VegetationModelRenderer model_renderer(parent, &instance.getModel());
SpaceSegment scaled_segment(segment.getStart().sub(instance.getBase()).scale(1.0 / instance.getSize()),
segment.getEnd().sub(instance.getBase()).scale(1.0 / instance.getSize()));
VegetationResult result = model_renderer.getResult(scaled_segment, only_hit);
final.hit = result.isHit();
if (final.hit and not only_hit) {
Vector3 location = result.getLocation().scale(instance.getSize()).add(instance.getBase());
final.hit_color = parent->applyLightingToSurface(location, result.getNormal(), result.getMaterial());
final.hit_color = parent->applyMediumTraversal(location, final.hit_color);
final.hit_location = result.getLocation();
}
return final;
}
RayCastingResult VegetationRenderer::getResult(const SpaceSegment &segment, bool only_hit) {
if (enabled) {
// Find instances potentially crossing the segment
VegetationGridIterator it(segment, this, only_hit);
if (not segment.projectedOnYPlane().iterateOnGrid(it)) {
return it.getResult();
}
return RayCastingResult();
} else {
return RayCastingResult();
}
}
RayCastingResult VegetationRenderer::getBoundResult(const SpaceSegment &segment, double x, double z, bool only_hit,
double xsize, double zsize) {
VegetationDefinition *vegetation = parent->getScenery()->getVegetation();
// Early check if we may cross any vegetation
double ymin, ymax;
parent->getTerrainRenderer()->estimateMinMaxHeight(x, z, x + xsize, z + zsize, &ymin, &ymax);
ymax += vegetation->getMaxHeight();
SpaceSegment bbox(Vector3(x, ymin, z), Vector3(x + xsize, ymax, z + zsize));
if (not segment.intersectBoundingBox(bbox)) {
return RayCastingResult();
}
// Iterate all layers and instances
int n = vegetation->getLayerCount();
for (int i = 0; i < n; i++) {
VegetationLayerDefinition *layer = vegetation->getVegetationLayer(i);
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) {
if (enabled) {
// Get segment to iterate
SpaceSegment segment(at, at.add(light.direction.scale(-1.0 * parent->render_quality)));
if (getResult(segment, true).hit) {
light.color = COLOR_BLACK;
return false;
} else {
return true;
}
} else {
return true;
}
}

View file

@ -0,0 +1,56 @@
#ifndef VEGETATIONRENDERER_H
#define VEGETATIONRENDERER_H
#include "software_global.h"
#include "LightFilter.h"
namespace paysages {
namespace software {
class SOFTWARESHARED_EXPORT VegetationRenderer : public LightFilter {
public:
VegetationRenderer(SoftwareRenderer *parent);
virtual ~VegetationRenderer();
/**
* Totally enable or disable the vegetation layers rendering.
*/
void setEnabled(bool enabled);
inline SoftwareRenderer *getParent() const {
return parent;
}
/**
* Perform ray casting on a single instance.
*
* If *only_hit* is true, only care about hitting or not, do not compute the color.
*
* If *displaced* is true, *instance* is considered on already displaced terrain, else, terrain displacement is
*applied.
*/
RayCastingResult renderInstance(const SpaceSegment &segment, const VegetationInstance &instance,
bool only_hit = false, bool displaced = false);
/**
* Perform ray casting on a given segment.
*/
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;
private:
SoftwareRenderer *parent;
bool enabled;
};
}
}
#endif // VEGETATIONRENDERER_H

View file

@ -0,0 +1,9 @@
#include "VegetationResult.h"
VegetationResult::VegetationResult(bool hit) : hit(hit) {
}
VegetationResult::VegetationResult(const Vector3 &location, const Vector3 &normal, const SurfaceMaterial &material)
: location(location), normal(normal), material(material) {
hit = true;
}

View file

@ -0,0 +1,42 @@
#ifndef VEGETATIONRESULT_H
#define VEGETATIONRESULT_H
#include "software_global.h"
#include "Vector3.h"
#include "SurfaceMaterial.h"
namespace paysages {
namespace software {
/**
* Result of a vegetation lookup.
*/
class SOFTWARESHARED_EXPORT VegetationResult {
public:
VegetationResult(bool hit = false);
VegetationResult(const Vector3 &location, const Vector3 &normal, const SurfaceMaterial &material);
inline bool isHit() const {
return hit;
}
inline const Vector3 &getLocation() const {
return location;
}
inline const Vector3 &getNormal() const {
return normal;
}
inline const SurfaceMaterial &getMaterial() const {
return material;
}
private:
bool hit;
Vector3 location;
Vector3 normal;
SurfaceMaterial material;
};
}
}
#endif // VEGETATIONRESULT_H

View file

@ -34,6 +34,7 @@ class TexturesRenderer;
class WaterRenderer; class WaterRenderer;
class Rasterizer; class Rasterizer;
class OverlayRasterizer;
class SkyRasterizer; class SkyRasterizer;
class TerrainRasterizer; class TerrainRasterizer;
@ -52,6 +53,9 @@ class TerrainRayWalker;
class GodRaysSampler; class GodRaysSampler;
class GodRaysResult; class GodRaysResult;
class VegetationResult;
class VegetationRenderer;
class VegetationModelRenderer;
class Canvas; class Canvas;
class CanvasPortion; class CanvasPortion;

View file

@ -3,43 +3,43 @@
#include <cmath> #include <cmath>
#include "CappedCylinder.h" #include "CappedCylinder.h"
TEST(CappedCylinder, checkRayIntersection) { TEST(CappedCylinder, findRayIntersection) {
CappedCylinder cylinder(VECTOR_DOWN, VECTOR_UP, 1.0, 2.0); CappedCylinder cylinder(VECTOR_DOWN, VECTOR_UP, 1.0, 2.0);
int intersect_count; int intersect_count;
Vector3 p1, p2; Vector3 p1, p2;
intersect_count = intersect_count =
cylinder.checkRayIntersection(InfiniteRay(Vector3(1.5, 0.0, 0.0), Vector3(0.0, 0.0, 1.0)), &p1, &p2); cylinder.findRayIntersection(InfiniteRay(Vector3(1.5, 0.0, 0.0), Vector3(0.0, 0.0, 1.0)), &p1, &p2);
EXPECT_EQ(0, intersect_count); EXPECT_EQ(0, intersect_count);
intersect_count = intersect_count =
cylinder.checkRayIntersection(InfiniteRay(Vector3(1.0, 0.0, 0.0), Vector3(0.0, 0.0, 1.0)), &p1, &p2); cylinder.findRayIntersection(InfiniteRay(Vector3(1.0, 0.0, 0.0), Vector3(0.0, 0.0, 1.0)), &p1, &p2);
EXPECT_EQ(1, intersect_count); EXPECT_EQ(1, intersect_count);
EXPECT_VECTOR3_COORDS(p1, 1.0, 0.0, 0.0); EXPECT_VECTOR3_COORDS(p1, 1.0, 0.0, 0.0);
intersect_count = intersect_count =
cylinder.checkRayIntersection(InfiniteRay(Vector3(0.5, 0.0, 0.0), Vector3(0.0, 0.0, 1.0)), &p1, &p2); cylinder.findRayIntersection(InfiniteRay(Vector3(0.5, 0.0, 0.0), Vector3(0.0, 0.0, 1.0)), &p1, &p2);
EXPECT_EQ(2, intersect_count); EXPECT_EQ(2, intersect_count);
EXPECT_VECTOR3_COORDS(p1, 0.5, 0.0, -cos(asin(0.5))); EXPECT_VECTOR3_COORDS(p1, 0.5, 0.0, -cos(asin(0.5)));
EXPECT_VECTOR3_COORDS(p2, 0.5, 0.0, cos(asin(0.5))); EXPECT_VECTOR3_COORDS(p2, 0.5, 0.0, cos(asin(0.5)));
intersect_count = intersect_count =
cylinder.checkRayIntersection(InfiniteRay(Vector3(0.5, -2.1, 0.0), Vector3(0.0, 0.0, 1.0)), &p1, &p2); cylinder.findRayIntersection(InfiniteRay(Vector3(0.5, -2.1, 0.0), Vector3(0.0, 0.0, 1.0)), &p1, &p2);
EXPECT_EQ(0, intersect_count); EXPECT_EQ(0, intersect_count);
intersect_count = intersect_count =
cylinder.checkRayIntersection(InfiniteRay(Vector3(0.5, 2.1, 0.0), Vector3(0.0, 0.0, 1.0)), &p1, &p2); cylinder.findRayIntersection(InfiniteRay(Vector3(0.5, 2.1, 0.0), Vector3(0.0, 0.0, 1.0)), &p1, &p2);
EXPECT_EQ(0, intersect_count); EXPECT_EQ(0, intersect_count);
// diagonal cases (through a cap) // diagonal cases (through a cap)
intersect_count = intersect_count =
cylinder.checkRayIntersection(InfiniteRay(Vector3(-2.0, -1.0, 0.0), Vector3(1.0, 1.0, 0.0)), &p1, &p2); cylinder.findRayIntersection(InfiniteRay(Vector3(-2.0, -1.0, 0.0), Vector3(1.0, 1.0, 0.0)), &p1, &p2);
EXPECT_EQ(1, intersect_count); EXPECT_EQ(1, intersect_count);
EXPECT_VECTOR3_COORDS(p1, -1.0, 0.0, 0.0); EXPECT_VECTOR3_COORDS(p1, -1.0, 0.0, 0.0);
intersect_count = intersect_count =
cylinder.checkRayIntersection(InfiniteRay(Vector3(-2.0, 3.0, 0.0), Vector3(1.0, -1.0, 0.0)), &p1, &p2); cylinder.findRayIntersection(InfiniteRay(Vector3(-2.0, 3.0, 0.0), Vector3(1.0, -1.0, 0.0)), &p1, &p2);
EXPECT_EQ(1, intersect_count); EXPECT_EQ(1, intersect_count);
EXPECT_VECTOR3_COORDS(p1, 1.0, 0.0, 0.0); EXPECT_VECTOR3_COORDS(p1, 1.0, 0.0, 0.0);
} }

View file

@ -4,7 +4,7 @@
#include "InfiniteCylinder.h" #include "InfiniteCylinder.h"
#include <cmath> #include <cmath>
TEST(InfiniteCylinder, checkRayIntersection) { TEST(InfiniteCylinder, findRayIntersection) {
InfiniteRay ray(VECTOR_ZERO, VECTOR_UP); InfiniteRay ray(VECTOR_ZERO, VECTOR_UP);
InfiniteCylinder cylinder(ray, 1.0); InfiniteCylinder cylinder(ray, 1.0);
@ -12,30 +12,30 @@ TEST(InfiniteCylinder, checkRayIntersection) {
Vector3 p1, p2; Vector3 p1, p2;
intersect_count = intersect_count =
cylinder.checkRayIntersection(InfiniteRay(Vector3(1.5, 0.0, 0.0), Vector3(0.0, 0.0, 1.0)), &p1, &p2); cylinder.findRayIntersection(InfiniteRay(Vector3(1.5, 0.0, 0.0), Vector3(0.0, 0.0, 1.0)), &p1, &p2);
EXPECT_EQ(0, intersect_count); EXPECT_EQ(0, intersect_count);
intersect_count = intersect_count =
cylinder.checkRayIntersection(InfiniteRay(Vector3(1.0, 0.0, 0.0), Vector3(0.0, 0.0, 1.0)), &p1, &p2); cylinder.findRayIntersection(InfiniteRay(Vector3(1.0, 0.0, 0.0), Vector3(0.0, 0.0, 1.0)), &p1, &p2);
EXPECT_EQ(1, intersect_count); EXPECT_EQ(1, intersect_count);
EXPECT_VECTOR3_COORDS(p1, 1.0, 0.0, 0.0); EXPECT_VECTOR3_COORDS(p1, 1.0, 0.0, 0.0);
intersect_count = intersect_count =
cylinder.checkRayIntersection(InfiniteRay(Vector3(0.5, 0.0, 0.0), Vector3(0.0, 0.0, 1.0)), &p1, &p2); cylinder.findRayIntersection(InfiniteRay(Vector3(0.5, 0.0, 0.0), Vector3(0.0, 0.0, 1.0)), &p1, &p2);
EXPECT_EQ(2, intersect_count); EXPECT_EQ(2, intersect_count);
EXPECT_VECTOR3_COORDS(p1, 0.5, 0.0, -cos(asin(0.5))); EXPECT_VECTOR3_COORDS(p1, 0.5, 0.0, -cos(asin(0.5)));
EXPECT_VECTOR3_COORDS(p2, 0.5, 0.0, cos(asin(0.5))); EXPECT_VECTOR3_COORDS(p2, 0.5, 0.0, cos(asin(0.5)));
} }
TEST(InfiniteCylinder, checkRayIntersection2) { TEST(InfiniteCylinder, getRayIntersection2) {
InfiniteRay ray(Vector3(-1.4, 1.5, 1.0), Vector3(1.0, 0.0, 0.0)); InfiniteRay ray(Vector3(-1.4, 1.5, 1.0), Vector3(1.0, 0.0, 0.0));
InfiniteCylinder cylinder(ray, 0.5); InfiniteCylinder cylinder(ray, 0.5);
int intersect_count; int intersect_count;
Vector3 p1, p2; Vector3 p1, p2;
intersect_count = cylinder.checkRayIntersection( intersect_count =
InfiniteRay::fromPoints(Vector3(0.0, 1.5, 0.0), Vector3(0.0, 1.5, 2.0)), &p1, &p2); cylinder.findRayIntersection(InfiniteRay::fromPoints(Vector3(0.0, 1.5, 0.0), Vector3(0.0, 1.5, 2.0)), &p1, &p2);
EXPECT_EQ(2, intersect_count); EXPECT_EQ(2, intersect_count);
EXPECT_VECTOR3_COORDS(p1, 0.0, 1.5, 0.5); EXPECT_VECTOR3_COORDS(p1, 0.0, 1.5, 0.5);
EXPECT_VECTOR3_COORDS(p2, 0.0, 1.5, 1.5); EXPECT_VECTOR3_COORDS(p2, 0.0, 1.5, 1.5);

View file

@ -0,0 +1,21 @@
#include "BaseTestCase.h"
#include "OpenGLVegetationImpostor.h"
#include "Vector3.h"
TEST(OpenGLVegetationImpostor, getIndex) {
OpenGLVegetationImpostor impostor;
EXPECT_EQ(0, impostor.getIndex(Vector3(0.0, 1.0, 0.0), VECTOR_ZERO));
EXPECT_EQ(1, impostor.getIndex(Vector3(1.0, 1.0, 0.0), VECTOR_ZERO));
EXPECT_EQ(1, impostor.getIndex(Vector3(1.0, 1.0, 0.1), VECTOR_ZERO));
EXPECT_EQ(1, impostor.getIndex(Vector3(1.0, 1.0, -0.1), VECTOR_ZERO));
EXPECT_EQ(2, impostor.getIndex(Vector3(1.0, 1.0, -1.0), VECTOR_ZERO));
EXPECT_EQ(5, impostor.getIndex(Vector3(1.0, 1.0, 1.0), VECTOR_ZERO));
EXPECT_EQ(6, impostor.getIndex(Vector3(1.0, 0.0, 0.0), VECTOR_ZERO));
EXPECT_EQ(6, impostor.getIndex(Vector3(1.0, 0.0, 0.1), VECTOR_ZERO));
EXPECT_EQ(6, impostor.getIndex(Vector3(1.0, 0.0, -0.1), VECTOR_ZERO));
}

View file

@ -0,0 +1,67 @@
#include "BaseTestCase.h"
#include "OpenGLVegetationLayer.h"
#include "VegetationLayerDefinition.h"
#include "VegetationModelDefinition.h"
#include "VegetationInstance.h"
#include "OpenGLVegetationInstance.h"
#include "CameraDefinition.h"
class FakeLayerRenderer : public OpenGLVegetationLayer {
public:
FakeLayerRenderer(VegetationLayerDefinition *definition) : OpenGLVegetationLayer(NULL, definition, false) {
}
virtual ~FakeLayerRenderer() {
for (auto instance : static_instances) {
delete instance;
}
}
virtual void produceInstancesInArea(double xmin, double xmax, double zmin, double zmax,
vector<OpenGLVegetationInstance *> *instances) const override {
for (auto instance : static_instances) {
Vector3 location = instance->getBase();
if (location.x >= xmin and location.z >= zmin and location.x < xmax and location.z < zmax) {
instances->push_back(instance);
}
}
}
vector<OpenGLVegetationInstance *> static_instances;
};
TEST(OpenGLVegetationLayer, updateInstances) {
CameraDefinition camera;
VegetationLayerDefinition definition(NULL, "test");
FakeLayerRenderer rendering(&definition);
VegetationModelDefinition model(NULL);
EXPECT_EQ(0, rendering.getInstanceCount());
rendering.updateInstances();
EXPECT_EQ(0, rendering.getInstanceCount());
rendering.static_instances.push_back(
new OpenGLVegetationInstance(VegetationInstance(model, Vector3(0.0, 0.0, 0.0))));
rendering.reset();
rendering.updateInstances();
EXPECT_EQ(1, rendering.getInstanceCount());
camera.setLocation(Vector3(-5.0, 0.0, 0.0));
rendering.setCamera(&camera);
rendering.updateInstances();
EXPECT_EQ(1, rendering.getInstanceCount());
camera.setLocation(Vector3(-11.0, 0.0, 0.0));
rendering.setCamera(&camera);
rendering.updateInstances();
EXPECT_EQ(0, rendering.getInstanceCount());
camera.setLocation(Vector3(0.0, 0.0, 5.0));
rendering.setCamera(&camera);
rendering.updateInstances();
EXPECT_EQ(1, rendering.getInstanceCount());
camera.setLocation(Vector3(0.0, 0.0, 15.0));
rendering.setCamera(&camera);
rendering.updateInstances();
EXPECT_EQ(0, rendering.getInstanceCount());
}

View file

@ -0,0 +1,19 @@
#include "BaseTestCase.h"
#include "OpenGLVegetation.h"
#include "Scenery.h"
#include "VegetationDefinition.h"
#include "VegetationLayerDefinition.h"
#include "OpenGLRenderer.h"
TEST(OpenGLVegetation, updateLayers) {
Scenery scenery;
OpenGLRenderer renderer(&scenery);
OpenGLVegetation glvegetation(&renderer);
EXPECT_EQ(0, glvegetation.getLayerCount());
scenery.getVegetation()->addLayer("test");
EXPECT_EQ(1, glvegetation.getLayerCount());
}

View file

@ -0,0 +1,61 @@
#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;
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);
MockOverlayRasterizer rasterizer(&renderer);
renderer.setSize(4, 3);
renderer.setSoloRasterizer(&rasterizer);
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);
}

View file

@ -54,3 +54,17 @@ 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));
}

View file

@ -3,20 +3,26 @@
#include "InfiniteRay.h" #include "InfiniteRay.h"
#include "Sphere.h" #include "Sphere.h"
TEST(Sphere, checkRayIntersection) { TEST(Sphere, findRayIntersection) {
Sphere sphere(Vector3(2.0, 1.0, 1.0), 0.5); Sphere sphere(Vector3(2.0, 1.0, 1.0), 0.5);
int intersect_count; int intersect_count;
Vector3 p1, p2; Vector3 p1, p2;
intersect_count = sphere.checkRayIntersection(InfiniteRay(Vector3(0.0, 0.0, 0.0), VECTOR_SOUTH), &p1, &p2); intersect_count = sphere.checkRayIntersection(InfiniteRay(Vector3(0.0, 0.0, 0.0), VECTOR_SOUTH));
ASSERT_EQ(0, intersect_count); EXPECT_EQ(0, intersect_count);
intersect_count = sphere.findRayIntersection(InfiniteRay(Vector3(0.0, 0.0, 0.0), VECTOR_SOUTH), &p1, &p2);
EXPECT_EQ(0, intersect_count);
intersect_count = sphere.checkRayIntersection(InfiniteRay(Vector3(1.5, 1.0, 0.0), VECTOR_SOUTH), &p1, &p2); intersect_count = sphere.checkRayIntersection(InfiniteRay(Vector3(1.5, 1.0, 0.0), VECTOR_SOUTH));
EXPECT_EQ(1, intersect_count);
intersect_count = sphere.findRayIntersection(InfiniteRay(Vector3(1.5, 1.0, 0.0), VECTOR_SOUTH), &p1, &p2);
ASSERT_EQ(1, intersect_count); ASSERT_EQ(1, intersect_count);
EXPECT_VECTOR3_COORDS(p1, 1.5, 1.0, 1.0); EXPECT_VECTOR3_COORDS(p1, 1.5, 1.0, 1.0);
intersect_count = sphere.checkRayIntersection(InfiniteRay(Vector3(2.0, 1.0, 0.0), VECTOR_SOUTH), &p1, &p2); intersect_count = sphere.checkRayIntersection(InfiniteRay(Vector3(2.0, 1.0, 0.0), VECTOR_SOUTH));
EXPECT_EQ(2, intersect_count);
intersect_count = sphere.findRayIntersection(InfiniteRay(Vector3(2.0, 1.0, 0.0), VECTOR_SOUTH), &p1, &p2);
ASSERT_EQ(2, intersect_count); ASSERT_EQ(2, intersect_count);
EXPECT_VECTOR3_COORDS(p1, 2.0, 1.0, 0.5); EXPECT_VECTOR3_COORDS(p1, 2.0, 1.0, 0.5);
EXPECT_VECTOR3_COORDS(p2, 2.0, 1.0, 1.5); EXPECT_VECTOR3_COORDS(p2, 2.0, 1.0, 1.5);