Merge branch 'vegetation'
This commit is contained in:
commit
5d24edad32
92 changed files with 2591 additions and 214 deletions
|
@ -3,42 +3,45 @@
|
|||
#include "Vector3.h"
|
||||
#include "PackStream.h"
|
||||
|
||||
CappedCylinder::CappedCylinder() {
|
||||
}
|
||||
|
||||
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,
|
||||
Vector3 *second_intersection) const {
|
||||
// TODO Apply the caps
|
||||
int count = InfiniteCylinder::checkRayIntersection(ray, first_intersection, second_intersection);
|
||||
|
||||
if (count == 0) {
|
||||
int CappedCylinder::findRayIntersection(const InfiniteRay &ray, Vector3 *first_intersection,
|
||||
Vector3 *second_intersection) const {
|
||||
if (not container.checkRayIntersection(ray)) {
|
||||
// We don't hit the containing sphere at all
|
||||
return 0;
|
||||
} else if (count == 2) {
|
||||
if (checkPointProjection(first_intersection)) {
|
||||
if (checkPointProjection(second_intersection)) {
|
||||
return 2;
|
||||
} else {
|
||||
// TODO Apply the caps
|
||||
int count = InfiniteCylinder::findRayIntersection(ray, first_intersection, second_intersection);
|
||||
|
||||
if (count == 0) {
|
||||
return 0;
|
||||
} else if (count == 2) {
|
||||
if (checkPointProjection(first_intersection)) {
|
||||
if (checkPointProjection(second_intersection)) {
|
||||
return 2;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
return 1;
|
||||
if (checkPointProjection(second_intersection)) {
|
||||
*first_intersection = *second_intersection;
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (checkPointProjection(second_intersection)) {
|
||||
*first_intersection = *second_intersection;
|
||||
// count == 1
|
||||
if (checkPointProjection(first_intersection)) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
} else // count == 1
|
||||
{
|
||||
if (checkPointProjection(first_intersection)) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "basics_global.h"
|
||||
|
||||
#include "InfiniteCylinder.h"
|
||||
#include "Sphere.h"
|
||||
|
||||
namespace paysages {
|
||||
namespace basics {
|
||||
|
@ -13,7 +14,7 @@ namespace basics {
|
|||
*/
|
||||
class BASICSSHARED_EXPORT CappedCylinder : public InfiniteCylinder {
|
||||
public:
|
||||
CappedCylinder();
|
||||
CappedCylinder() = default;
|
||||
CappedCylinder(const Vector3 &base, const Vector3 &direction, double radius, double length);
|
||||
|
||||
inline double getLength() const {
|
||||
|
@ -22,8 +23,10 @@ class BASICSSHARED_EXPORT CappedCylinder : public InfiniteCylinder {
|
|||
|
||||
/**
|
||||
* 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.
|
||||
|
@ -35,6 +38,7 @@ class BASICSSHARED_EXPORT CappedCylinder : public InfiniteCylinder {
|
|||
|
||||
private:
|
||||
double length;
|
||||
Sphere container;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -132,6 +132,12 @@ double Color::normalize() {
|
|||
return max;*/
|
||||
}
|
||||
|
||||
Color Color::normalized() {
|
||||
Color col = *this;
|
||||
col.normalize();
|
||||
return col;
|
||||
}
|
||||
|
||||
double Color::getValue() const {
|
||||
double max;
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ class BASICSSHARED_EXPORT Color {
|
|||
|
||||
void mask(const Color &mask);
|
||||
double normalize();
|
||||
Color normalized();
|
||||
double getValue() const;
|
||||
double getPower() const;
|
||||
void limitPower(double max_power);
|
||||
|
|
|
@ -2,9 +2,6 @@
|
|||
|
||||
#include "PackStream.h"
|
||||
|
||||
Disk::Disk() {
|
||||
}
|
||||
|
||||
Disk::Disk(const Vector3 &point, const Vector3 &normal, double radius) : InfinitePlane(point, normal), radius(radius) {
|
||||
radius2 = radius * radius;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace basics {
|
|||
*/
|
||||
class BASICSSHARED_EXPORT Disk : public InfinitePlane {
|
||||
public:
|
||||
Disk();
|
||||
Disk() = default;
|
||||
Disk(const Vector3 &point, const Vector3 &normal, double radius);
|
||||
|
||||
inline double getRadius() const {
|
||||
|
|
|
@ -5,23 +5,85 @@
|
|||
|
||||
#define EPS 1E-8
|
||||
|
||||
InfiniteCylinder::InfiniteCylinder() {
|
||||
}
|
||||
|
||||
InfiniteCylinder::InfiniteCylinder(const InfiniteRay &axis, double radius) : axis(axis), radius(radius) {
|
||||
validate();
|
||||
}
|
||||
|
||||
int InfiniteCylinder::checkRayIntersection(const InfiniteRay &ray, Vector3 *first_intersection,
|
||||
Vector3 *second_intersection) const {
|
||||
int InfiniteCylinder::findRayIntersection(const InfiniteRay &ray, Vector3 *first_intersection,
|
||||
Vector3 *second_intersection) const {
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
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 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 */
|
||||
|
||||
|
@ -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][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 */
|
||||
e0 = -2.0 * (R[0][0] * P.x + R[0][1] * P.y + R[0][2] * P.z);
|
||||
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);
|
||||
C = -radius * radius;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace basics {
|
|||
*/
|
||||
class BASICSSHARED_EXPORT InfiniteCylinder {
|
||||
public:
|
||||
InfiniteCylinder();
|
||||
InfiniteCylinder() = default;
|
||||
InfiniteCylinder(const InfiniteRay &axis, double radius);
|
||||
|
||||
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.
|
||||
*/
|
||||
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 load(PackStream *stream);
|
||||
|
||||
private:
|
||||
void validate();
|
||||
|
||||
protected:
|
||||
InfiniteRay axis;
|
||||
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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
#include "InfiniteRay.h"
|
||||
|
||||
InfiniteRay::InfiniteRay() {
|
||||
}
|
||||
|
||||
InfiniteRay::InfiniteRay(const Vector3 &origin, const Vector3 &direction)
|
||||
: origin(origin), direction(direction.normalize()) {
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace basics {
|
|||
*/
|
||||
class BASICSSHARED_EXPORT InfiniteRay {
|
||||
public:
|
||||
InfiniteRay();
|
||||
InfiniteRay() = default;
|
||||
InfiniteRay(const Vector3 &origin, const Vector3 &direction);
|
||||
|
||||
static InfiniteRay fromPoints(const Vector3 &point1, const Vector3 &point2);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <cmath>
|
||||
#include <climits>
|
||||
#include "SpaceGridIterator.h"
|
||||
using namespace std;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 {
|
||||
return SpaceSegment(Vector3(x, start.y, start.z), Vector3(x, end.y, end.z));
|
||||
}
|
||||
|
|
|
@ -47,6 +47,11 @@ class BASICSSHARED_EXPORT SpaceSegment {
|
|||
*/
|
||||
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.
|
||||
*/
|
||||
|
|
|
@ -4,15 +4,27 @@
|
|||
#include "PackStream.h"
|
||||
#include "InfiniteRay.h"
|
||||
|
||||
Sphere::Sphere() {
|
||||
}
|
||||
|
||||
Sphere::Sphere(const Vector3 ¢er, double radius) : center(center), radius(radius) {
|
||||
radius2 = radius * radius;
|
||||
}
|
||||
|
||||
int Sphere::checkRayIntersection(const InfiniteRay &ray, Vector3 *first_intersection,
|
||||
Vector3 *second_intersection) const {
|
||||
int Sphere::checkRayIntersection(const InfiniteRay &ray) 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);
|
||||
double b = 2.0 * ray.getDirection().dotProduct(L);
|
||||
double c = L.dotProduct(L) - radius2;
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace basics {
|
|||
*/
|
||||
class BASICSSHARED_EXPORT Sphere {
|
||||
public:
|
||||
Sphere();
|
||||
Sphere() = default;
|
||||
Sphere(const Vector3 ¢er, double radius);
|
||||
|
||||
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.
|
||||
*/
|
||||
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 load(PackStream *stream);
|
||||
|
|
|
@ -45,7 +45,7 @@ void DefinitionNode::setName(const string &name) {
|
|||
this->name = name;
|
||||
}
|
||||
|
||||
Scenery *DefinitionNode::getScenery() {
|
||||
const Scenery *DefinitionNode::getScenery() const {
|
||||
if (parent) {
|
||||
return parent->getScenery();
|
||||
} else {
|
||||
|
|
|
@ -31,7 +31,7 @@ class DEFINITIONSHARED_EXPORT DefinitionNode {
|
|||
return type_name;
|
||||
}
|
||||
|
||||
virtual Scenery *getScenery();
|
||||
virtual const Scenery *getScenery() const;
|
||||
|
||||
inline const DefinitionNode *getParent() const {
|
||||
return parent;
|
||||
|
|
|
@ -24,7 +24,7 @@ class DEFINITIONSHARED_EXPORT DefinitionWatcher {
|
|||
/**
|
||||
* 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);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "CloudsDefinition.h"
|
||||
#include "TerrainDefinition.h"
|
||||
#include "TexturesDefinition.h"
|
||||
#include "VegetationDefinition.h"
|
||||
#include "WaterDefinition.h"
|
||||
#include "Logs.h"
|
||||
#include "RandomGenerator.h"
|
||||
|
@ -22,6 +23,7 @@ Scenery::Scenery() : DefinitionNode(NULL, "scenery", "scenery") {
|
|||
terrain = new TerrainDefinition(this);
|
||||
textures = new TexturesDefinition(this);
|
||||
water = new WaterDefinition(this);
|
||||
vegetation = new VegetationDefinition(this);
|
||||
}
|
||||
|
||||
void Scenery::validate() {
|
||||
|
@ -89,7 +91,7 @@ Scenery::FileOperationResult Scenery::loadGlobal(const string &filepath) {
|
|||
return FILE_OPERATION_OK;
|
||||
}
|
||||
|
||||
Scenery *Scenery::getScenery() {
|
||||
const Scenery *Scenery::getScenery() const {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -99,6 +101,7 @@ void Scenery::autoPreset(RandomGenerator &random) {
|
|||
atmosphere->applyPreset(AtmosphereDefinition::ATMOSPHERE_PRESET_CLEAR_DAY, random);
|
||||
water->applyPreset(WaterDefinition::WATER_PRESET_LAKE, random);
|
||||
clouds->applyPreset(CloudsDefinition::CLOUDS_PRESET_PARTLY_CLOUDY, random);
|
||||
vegetation->applyPreset(VegetationDefinition::VEGETATION_PRESET_TEMPERATE, random);
|
||||
|
||||
camera->setLocation(VECTOR_ZERO);
|
||||
camera->setTarget(VECTOR_NORTH);
|
||||
|
@ -154,6 +157,14 @@ void Scenery::getTextures(TexturesDefinition *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) {
|
||||
water->copy(this->water);
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ class DEFINITIONSHARED_EXPORT Scenery : public DefinitionNode {
|
|||
FileOperationResult saveGlobal(const string &filepath) const;
|
||||
FileOperationResult loadGlobal(const string &filepath);
|
||||
|
||||
virtual Scenery *getScenery() override;
|
||||
virtual const Scenery *getScenery() const override;
|
||||
|
||||
void autoPreset(RandomGenerator &random = RandomGeneratorDefault);
|
||||
void autoPreset(unsigned int seed);
|
||||
|
@ -67,6 +67,12 @@ class DEFINITIONSHARED_EXPORT Scenery : public DefinitionNode {
|
|||
}
|
||||
void getTextures(TexturesDefinition *textures);
|
||||
|
||||
void setVegetation(VegetationDefinition *Vegetation);
|
||||
inline VegetationDefinition *getVegetation() const {
|
||||
return vegetation;
|
||||
}
|
||||
void getVegetation(VegetationDefinition *Vegetation);
|
||||
|
||||
void setWater(WaterDefinition *water);
|
||||
inline WaterDefinition *getWater() const {
|
||||
return water;
|
||||
|
@ -81,6 +87,7 @@ class DEFINITIONSHARED_EXPORT Scenery : public DefinitionNode {
|
|||
CloudsDefinition *clouds;
|
||||
TerrainDefinition *terrain;
|
||||
TexturesDefinition *textures;
|
||||
VegetationDefinition *vegetation;
|
||||
WaterDefinition *water;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -16,6 +16,10 @@ SurfaceMaterial::SurfaceMaterial(const Color &color) {
|
|||
receive_shadows = 1.0;
|
||||
}
|
||||
|
||||
SurfaceMaterial::SurfaceMaterial(const SurfaceMaterial &other) : SurfaceMaterial(COLOR_BLACK) {
|
||||
other.copy(this);
|
||||
}
|
||||
|
||||
SurfaceMaterial::~SurfaceMaterial() {
|
||||
delete base;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ class DEFINITIONSHARED_EXPORT SurfaceMaterial {
|
|||
public:
|
||||
SurfaceMaterial();
|
||||
SurfaceMaterial(const Color &color);
|
||||
SurfaceMaterial(const SurfaceMaterial &other);
|
||||
~SurfaceMaterial();
|
||||
|
||||
static const SurfaceMaterial &getDefault();
|
||||
|
|
|
@ -46,7 +46,7 @@ void TextureLayerDefinition::validate() {
|
|||
material->validate();
|
||||
|
||||
/* Update zone height range */
|
||||
Scenery *scenery = getScenery();
|
||||
const Scenery *scenery = getScenery();
|
||||
if (scenery) {
|
||||
TerrainDefinition *terrain = scenery->getTerrain();
|
||||
HeightInfo height_info = terrain->getHeightInfo();
|
||||
|
|
37
src/definition/VegetationDefinition.cpp
Normal file
37
src/definition/VegetationDefinition.cpp
Normal 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);
|
||||
}*/
|
||||
}
|
36
src/definition/VegetationDefinition.h
Normal file
36
src/definition/VegetationDefinition.h
Normal 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
|
10
src/definition/VegetationInstance.cpp
Normal file
10
src/definition/VegetationInstance.cpp
Normal 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);
|
||||
}
|
45
src/definition/VegetationInstance.h
Normal file
45
src/definition/VegetationInstance.h
Normal 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
|
18
src/definition/VegetationLayerDefinition.cpp
Normal file
18
src/definition/VegetationLayerDefinition.cpp
Normal 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);
|
||||
}
|
44
src/definition/VegetationLayerDefinition.h
Normal file
44
src/definition/VegetationLayerDefinition.h
Normal 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
|
159
src/definition/VegetationModelDefinition.cpp
Normal file
159
src/definition/VegetationModelDefinition.cpp
Normal 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);
|
||||
}
|
||||
}
|
60
src/definition/VegetationModelDefinition.h
Normal file
60
src/definition/VegetationModelDefinition.h
Normal 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
|
62
src/definition/VegetationPresenceDefinition.cpp
Normal file
62
src/definition/VegetationPresenceDefinition.cpp
Normal 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;
|
||||
}
|
40
src/definition/VegetationPresenceDefinition.h
Normal file
40
src/definition/VegetationPresenceDefinition.h
Normal 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
|
|
@ -35,6 +35,11 @@ class TexturesDefinition;
|
|||
class TextureLayerDefinition;
|
||||
class TerrainDefinition;
|
||||
class TerrainHeightMap;
|
||||
class VegetationDefinition;
|
||||
class VegetationLayerDefinition;
|
||||
class VegetationModelDefinition;
|
||||
class VegetationPresenceDefinition;
|
||||
class VegetationInstance;
|
||||
class PaintedGrid;
|
||||
class PaintedGridData;
|
||||
class PaintedGridBrush;
|
||||
|
|
|
@ -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
|
||||
INCLUDEPATH += $$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
|
||||
|
|
|
@ -17,13 +17,22 @@
|
|||
#include "LightFilter.h"
|
||||
#include "GodRaysSampler.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 <sstream>
|
||||
#include <iostream>
|
||||
|
||||
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;
|
||||
|
||||
stream << "pic_test_" << name;
|
||||
|
@ -35,7 +44,11 @@ static void startTestRender(SoftwareCanvasRenderer *renderer, const string &name
|
|||
}
|
||||
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() {
|
||||
|
@ -99,11 +112,11 @@ static void testCloudQuality() {
|
|||
|
||||
SoftwareCanvasRenderer renderer(&scenery);
|
||||
renderer.setSize(600, 800);
|
||||
SkyRasterizer *rasterizer = new SkyRasterizer(&renderer, renderer.getProgressHelper(), 0);
|
||||
renderer.setSoloRasterizer(rasterizer);
|
||||
SkyRasterizer rasterizer(&renderer, renderer.getProgressHelper(), 0);
|
||||
renderer.setSoloRasterizer(&rasterizer);
|
||||
for (int i = 0; i < 6; i++) {
|
||||
renderer.setQuality((double)i / 5.0);
|
||||
rasterizer->setQuality(0.2);
|
||||
rasterizer.setQuality(0.2);
|
||||
startTestRender(&renderer, "cloud_quality", i);
|
||||
}
|
||||
}
|
||||
|
@ -146,8 +159,8 @@ static void testGodRays() {
|
|||
|
||||
TestRenderer renderer(&scenery);
|
||||
renderer.setSize(500, 300);
|
||||
SkyRasterizer *rasterizer = new SkyRasterizer(&renderer, renderer.getProgressHelper(), 0);
|
||||
renderer.setSoloRasterizer(rasterizer);
|
||||
SkyRasterizer rasterizer(&renderer, renderer.getProgressHelper(), 0);
|
||||
renderer.setSoloRasterizer(&rasterizer);
|
||||
TestLightFilter filter;
|
||||
renderer.getLightingManager()->clearFilters();
|
||||
renderer.getLightingManager()->registerFilter(&filter);
|
||||
|
@ -155,7 +168,7 @@ static void testGodRays() {
|
|||
// quality
|
||||
for (int i = 0; i < 6; i++) {
|
||||
renderer.setQuality((double)i / 5.0);
|
||||
rasterizer->setQuality(0.2);
|
||||
rasterizer.setQuality(0.2);
|
||||
startTestRender(&renderer, "god_rays_quality", i);
|
||||
}
|
||||
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() {
|
||||
testGroundShadowQuality();
|
||||
testRasterizationQuality();
|
||||
|
@ -239,4 +322,6 @@ void runTestSuite() {
|
|||
testNearFrustum();
|
||||
testCloudsNearGround();
|
||||
testSunNearHorizon();
|
||||
testVegetationModels();
|
||||
testOpenGLVegetationImpostor();
|
||||
}
|
||||
|
|
|
@ -63,3 +63,11 @@ void OpenGLPart::updateScenery(bool onlyCommon) {
|
|||
update();
|
||||
}
|
||||
}
|
||||
|
||||
Scenery *OpenGLPart::getScenery() const {
|
||||
return renderer->getScenery();
|
||||
}
|
||||
|
||||
OpenGLFunctions *OpenGLPart::getOpenGlFunctions() const {
|
||||
return renderer->getOpenGlFunctions();
|
||||
}
|
||||
|
|
|
@ -40,6 +40,16 @@ class OPENGLSHARED_EXPORT OpenGLPart {
|
|||
|
||||
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 {
|
||||
return name;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,9 @@
|
|||
#include "OpenGLSkybox.h"
|
||||
#include "OpenGLWater.h"
|
||||
#include "OpenGLTerrain.h"
|
||||
#include "OpenGLVegetation.h"
|
||||
#include "CloudsRenderer.h"
|
||||
#include "VegetationRenderer.h"
|
||||
#include "Color.h"
|
||||
#include "Scenery.h"
|
||||
#include "LightingManager.h"
|
||||
|
@ -41,6 +43,7 @@ OpenGLRenderer::OpenGLRenderer(Scenery *scenery) : SoftwareRenderer(scenery) {
|
|||
parts.push_back(skybox = new OpenGLSkybox(this));
|
||||
parts.push_back(water = new OpenGLWater(this));
|
||||
parts.push_back(terrain = new OpenGLTerrain(this));
|
||||
parts.push_back(vegetation = new OpenGLVegetation(this));
|
||||
}
|
||||
|
||||
OpenGLRenderer::~OpenGLRenderer() {
|
||||
|
@ -59,6 +62,15 @@ OpenGLRenderer::~OpenGLRenderer() {
|
|||
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) {
|
||||
int error_code;
|
||||
while ((error_code = functions->glGetError()) != GL_NO_ERROR) {
|
||||
|
@ -77,9 +89,9 @@ void OpenGLRenderer::destroy() {
|
|||
}
|
||||
|
||||
void OpenGLRenderer::initialize() {
|
||||
ready = functions->initializeOpenGLFunctions();
|
||||
bool init = functions->initializeOpenGLFunctions();
|
||||
|
||||
if (ready) {
|
||||
if (init) {
|
||||
Logs::debug() << "[OpenGL] renderer started (version " << functions->glGetString(GL_VERSION)
|
||||
<< ", glsl version " << functions->glGetString(GL_SHADING_LANGUAGE_VERSION) << ")" << endl;
|
||||
|
||||
|
@ -87,10 +99,6 @@ void OpenGLRenderer::initialize() {
|
|||
|
||||
prepare();
|
||||
|
||||
getCloudsRenderer()->setEnabled(false);
|
||||
getLightingManager()->setSpecularity(false);
|
||||
getGodRaysSampler()->setEnabled(false);
|
||||
|
||||
for (auto part : parts) {
|
||||
part->initialize();
|
||||
part->updateScenery();
|
||||
|
@ -99,6 +107,7 @@ void OpenGLRenderer::initialize() {
|
|||
cameraChangeEvent(render_camera);
|
||||
|
||||
checkForErrors("initialize");
|
||||
ready = true;
|
||||
} else {
|
||||
Logs::error() << "[OpenGL] Failed to initialize api bindings" << endl;
|
||||
}
|
||||
|
@ -111,7 +120,7 @@ void OpenGLRenderer::prepareOpenGLState(bool clear) {
|
|||
functions->glEnable(GL_CULL_FACE);
|
||||
|
||||
functions->glDepthFunc(GL_LESS);
|
||||
functions->glDepthMask(1);
|
||||
functions->glDepthMask(GL_TRUE);
|
||||
functions->glEnable(GL_DEPTH_TEST);
|
||||
|
||||
functions->glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
||||
|
@ -241,6 +250,9 @@ void OpenGLRenderer::cameraChangeEvent(CameraDefinition *camera) {
|
|||
// Set in shaders
|
||||
shared_state->set("cameraLocation", location);
|
||||
shared_state->set("viewMatrix", *view_matrix);
|
||||
|
||||
// Broadcast to parts
|
||||
vegetation->cameraChanged(camera);
|
||||
}
|
||||
|
||||
double OpenGLRenderer::getPrecision(const Vector3 &) {
|
||||
|
|
|
@ -29,6 +29,9 @@ class OPENGLSHARED_EXPORT OpenGLRenderer : public SoftwareRenderer {
|
|||
inline OpenGLTerrain *getTerrain() const {
|
||||
return terrain;
|
||||
}
|
||||
inline OpenGLVegetation *getVegetation() const {
|
||||
return vegetation;
|
||||
}
|
||||
inline bool isDisplayed() const {
|
||||
return displayed;
|
||||
}
|
||||
|
@ -39,6 +42,8 @@ class OPENGLSHARED_EXPORT OpenGLRenderer : public SoftwareRenderer {
|
|||
return stopped;
|
||||
}
|
||||
|
||||
virtual void prepare() override;
|
||||
|
||||
/**
|
||||
* Check for errors in OpenGL context.
|
||||
*
|
||||
|
@ -137,6 +142,7 @@ class OPENGLSHARED_EXPORT OpenGLRenderer : public SoftwareRenderer {
|
|||
OpenGLSkybox *skybox;
|
||||
OpenGLWater *water;
|
||||
OpenGLTerrain *terrain;
|
||||
OpenGLVegetation *vegetation;
|
||||
|
||||
vector<OpenGLPart *> parts;
|
||||
};
|
||||
|
|
|
@ -16,11 +16,13 @@ OpenGLShaderProgram::OpenGLShaderProgram(const string &name, OpenGLRenderer *ren
|
|||
: renderer(renderer), name(name) {
|
||||
program = new QOpenGLShaderProgram();
|
||||
functions = renderer->getOpenGlFunctions();
|
||||
state = new OpenGLSharedState();
|
||||
compiled = false;
|
||||
}
|
||||
|
||||
OpenGLShaderProgram::~OpenGLShaderProgram() {
|
||||
delete program;
|
||||
delete state;
|
||||
}
|
||||
|
||||
void OpenGLShaderProgram::addVertexSource(const string &path) {
|
||||
|
@ -87,9 +89,9 @@ void OpenGLShaderProgram::release() {
|
|||
program->release();
|
||||
}
|
||||
|
||||
void OpenGLShaderProgram::draw(OpenGLVertexArray *vertices, OpenGLSharedState *state) {
|
||||
void OpenGLShaderProgram::draw(OpenGLVertexArray *vertices, OpenGLSharedState *state, int start, int count) {
|
||||
if (bind(state)) {
|
||||
vertices->render(functions);
|
||||
vertices->render(functions, start, count);
|
||||
|
||||
release();
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ class OPENGLSHARED_EXPORT OpenGLShaderProgram {
|
|||
*
|
||||
* *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 {
|
||||
return program;
|
||||
|
@ -42,6 +42,9 @@ class OPENGLSHARED_EXPORT OpenGLShaderProgram {
|
|||
inline OpenGLRenderer *getRenderer() const {
|
||||
return renderer;
|
||||
}
|
||||
inline OpenGLSharedState *getState() const {
|
||||
return state;
|
||||
}
|
||||
|
||||
protected:
|
||||
friend class OpenGLVariable;
|
||||
|
@ -59,6 +62,8 @@ class OPENGLSHARED_EXPORT OpenGLShaderProgram {
|
|||
QOpenGLShaderProgram *program;
|
||||
OpenGLFunctions *functions;
|
||||
|
||||
OpenGLSharedState *state;
|
||||
|
||||
string source_vertex;
|
||||
string source_fragment;
|
||||
};
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
OpenGLSharedState::OpenGLSharedState() {
|
||||
}
|
||||
|
||||
paysages::opengl::OpenGLSharedState::~OpenGLSharedState() {
|
||||
OpenGLSharedState::~OpenGLSharedState() {
|
||||
for (const auto &pair : variables) {
|
||||
delete pair.second;
|
||||
}
|
||||
|
|
|
@ -37,6 +37,12 @@ class OPENGLSHARED_EXPORT OpenGLSharedState {
|
|||
OpenGLVariable *get(const string &name);
|
||||
|
||||
// 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) {
|
||||
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) {
|
||||
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) {
|
||||
get(name)->set(vector);
|
||||
}
|
||||
|
|
|
@ -12,9 +12,9 @@
|
|||
#include "FloatNode.h"
|
||||
#include "Logs.h"
|
||||
|
||||
static const std::string path_daytime = "/atmosphere/daytime";
|
||||
static const std::string path_humidity = "/atmosphere/humidity";
|
||||
static const std::string path_sun_radius = "/atmosphere/sun_radius";
|
||||
static const string path_daytime = "/atmosphere/daytime";
|
||||
static const string path_humidity = "/atmosphere/humidity";
|
||||
static const string path_sun_radius = "/atmosphere/sun_radius";
|
||||
|
||||
OpenGLSkybox::OpenGLSkybox(OpenGLRenderer *renderer) : OpenGLPart(renderer, "skybox") {
|
||||
program = createShader("skybox");
|
||||
|
|
|
@ -49,6 +49,9 @@ void OpenGLVariable::apply(OpenGLShaderProgram *program, int &texture_unit) {
|
|||
}
|
||||
|
||||
switch (type) {
|
||||
case TYPE_INTEGER:
|
||||
pr->setUniformValue(name.c_str(), value_int);
|
||||
break;
|
||||
case TYPE_FLOAT:
|
||||
pr->setUniformValue(name.c_str(), value_float);
|
||||
break;
|
||||
|
@ -217,6 +220,13 @@ void OpenGLVariable::set(const Texture4D *texture, bool repeat, bool 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) {
|
||||
assert(type == TYPE_NONE or type == TYPE_FLOAT);
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ class OpenGLVariable {
|
|||
TYPE_TEXTURE_2D,
|
||||
TYPE_TEXTURE_3D,
|
||||
TYPE_TEXTURE_4D,
|
||||
TYPE_INTEGER,
|
||||
TYPE_FLOAT,
|
||||
TYPE_VECTOR3,
|
||||
TYPE_MATRIX4,
|
||||
|
@ -44,6 +45,7 @@ class OpenGLVariable {
|
|||
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 Texture4D *texture, bool repeat = false, bool color = true);
|
||||
void set(int value);
|
||||
void set(float value);
|
||||
void set(const Vector3 &vector);
|
||||
void set(const QVector3D &vector);
|
||||
|
@ -58,6 +60,7 @@ class OpenGLVariable {
|
|||
string name;
|
||||
OpenGLVariableType type;
|
||||
|
||||
int value_int;
|
||||
float value_float;
|
||||
QColor *value_color;
|
||||
QVector3D *value_vector3;
|
||||
|
|
153
src/render/opengl/OpenGLVegetation.cpp
Normal file
153
src/render/opengl/OpenGLVegetation.cpp
Normal 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();
|
||||
}
|
89
src/render/opengl/OpenGLVegetation.h
Normal file
89
src/render/opengl/OpenGLVegetation.h
Normal 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
|
148
src/render/opengl/OpenGLVegetationImpostor.cpp
Normal file
148
src/render/opengl/OpenGLVegetationImpostor.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
50
src/render/opengl/OpenGLVegetationImpostor.h
Normal file
50
src/render/opengl/OpenGLVegetationImpostor.h
Normal 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
|
10
src/render/opengl/OpenGLVegetationInstance.cpp
Normal file
10
src/render/opengl/OpenGLVegetationInstance.cpp
Normal 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;
|
||||
}
|
43
src/render/opengl/OpenGLVegetationInstance.h
Normal file
43
src/render/opengl/OpenGLVegetationInstance.h
Normal 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
|
147
src/render/opengl/OpenGLVegetationLayer.cpp
Normal file
147
src/render/opengl/OpenGLVegetationLayer.cpp
Normal 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;
|
||||
}
|
||||
}
|
96
src/render/opengl/OpenGLVegetationLayer.h
Normal file
96
src/render/opengl/OpenGLVegetationLayer.h
Normal 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
|
|
@ -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) {
|
||||
changed = false;
|
||||
update(functions);
|
||||
|
@ -53,7 +53,7 @@ void OpenGLVertexArray::render(OpenGLFunctions *functions) {
|
|||
|
||||
if (vertexcount and vao) {
|
||||
functions->glBindVertexArray(vao);
|
||||
functions->glDrawArrays(draw_mode, 0, vertexcount);
|
||||
functions->glDrawArrays(draw_mode, start, count < 0 ? vertexcount : count);
|
||||
functions->glBindVertexArray(0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ class OpenGLVertexArray {
|
|||
*
|
||||
* 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.
|
||||
|
|
|
@ -14,9 +14,9 @@
|
|||
#include "IntNode.h"
|
||||
#include "Logs.h"
|
||||
|
||||
static const std::string path_height = "/terrain/water_height";
|
||||
static const std::string path_reflection = "/water/reflection";
|
||||
static const std::string path_model = "/water/model";
|
||||
static const string path_height = "/terrain/water_height";
|
||||
static const string path_reflection = "/water/reflection";
|
||||
static const string path_model = "/water/model";
|
||||
|
||||
OpenGLWater::OpenGLWater(OpenGLRenderer *renderer) : OpenGLPart(renderer, "water") {
|
||||
enabled = true;
|
||||
|
|
|
@ -55,3 +55,7 @@ RESOURCES += \
|
|||
OTHER_FILES += \
|
||||
shaders/*.frag \
|
||||
shaders/*.vert
|
||||
|
||||
DISTFILES += \
|
||||
shaders/vegetation.frag \
|
||||
shaders/vegetation.vert
|
||||
|
|
|
@ -20,6 +20,10 @@ class OpenGLVertexArray;
|
|||
class OpenGLSkybox;
|
||||
class OpenGLWater;
|
||||
class OpenGLTerrain;
|
||||
class OpenGLVegetation;
|
||||
class OpenGLVegetationLayer;
|
||||
class OpenGLVegetationInstance;
|
||||
class OpenGLVegetationImpostor;
|
||||
class OpenGLTerrainChunk;
|
||||
template <typename Vertex> class VertexArray;
|
||||
}
|
||||
|
|
|
@ -11,5 +11,7 @@
|
|||
<file>fadeout.frag</file>
|
||||
<file>noise.frag</file>
|
||||
<file>ui.frag</file>
|
||||
<file>vegetation.vert</file>
|
||||
<file>vegetation.frag</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
16
src/render/opengl/shaders/vegetation.frag
Normal file
16
src/render/opengl/shaders/vegetation.frag
Normal 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;
|
||||
}
|
16
src/render/opengl/shaders/vegetation.vert
Normal file
16
src/render/opengl/shaders/vegetation.vert
Normal 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);
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
#include "CanvasFragment.h"
|
||||
|
||||
const int MAX_FRAGMENTS_PER_PIXEL = 2;
|
||||
const int MAX_FRAGMENTS_PER_PIXEL = 5;
|
||||
|
||||
namespace paysages {
|
||||
namespace software {
|
||||
|
|
46
src/render/software/OverlayRasterizer.cpp
Normal file
46
src/render/software/OverlayRasterizer.cpp
Normal 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);
|
||||
}
|
33
src/render/software/OverlayRasterizer.h
Normal file
33
src/render/software/OverlayRasterizer.h
Normal 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
|
|
@ -38,6 +38,7 @@ Rasterizer::Rasterizer(SoftwareRenderer *renderer, RenderProgress *progress, int
|
|||
|
||||
interrupted = false;
|
||||
backface_culling = false;
|
||||
perspective_correction = true;
|
||||
triangle_count = 0;
|
||||
auto_cut_limit = 0.01;
|
||||
|
||||
|
@ -52,7 +53,8 @@ void Rasterizer::interrupt() {
|
|||
interrupted = true;
|
||||
}
|
||||
|
||||
void Rasterizer::setQuality(double) {
|
||||
void Rasterizer::setQuality(double quality) {
|
||||
this->perspective_correction = (quality > 0.4);
|
||||
}
|
||||
|
||||
void Rasterizer::setColor(const Color &color) {
|
||||
|
@ -63,6 +65,10 @@ void Rasterizer::setBackFaceCulling(bool cull) {
|
|||
this->backface_culling = cull;
|
||||
}
|
||||
|
||||
void Rasterizer::setPerspectiveCorrection(bool active) {
|
||||
this->perspective_correction = active;
|
||||
}
|
||||
|
||||
void Rasterizer::setAutoCutLimit(double 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,
|
||||
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.y = v1->pixel.y + diff->pixel.y * 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;
|
||||
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;
|
||||
|
||||
if (perspective_correction) {
|
||||
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->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->front_facing = v1->front_facing;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ namespace software {
|
|||
const int RASTERIZER_CLIENT_SKY = 0;
|
||||
const int RASTERIZER_CLIENT_WATER = 1;
|
||||
const int RASTERIZER_CLIENT_TERRAIN = 2;
|
||||
const int RASTERIZER_CLIENT_VEGETATION = 3;
|
||||
|
||||
typedef struct ScanPoint ScanPoint;
|
||||
typedef struct RenderScanlines RenderScanlines;
|
||||
|
@ -60,6 +61,7 @@ class SOFTWARESHARED_EXPORT Rasterizer {
|
|||
|
||||
void setColor(const Color &color);
|
||||
void setBackFaceCulling(bool cull);
|
||||
void setPerspectiveCorrection(bool active);
|
||||
|
||||
/**
|
||||
* Reset the internal triangle counter to 0.
|
||||
|
@ -96,6 +98,7 @@ class SOFTWARESHARED_EXPORT Rasterizer {
|
|||
int triangle_count;
|
||||
double auto_cut_limit;
|
||||
bool backface_culling;
|
||||
bool perspective_correction;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "TerrainRasterizer.h"
|
||||
#include "WaterRasterizer.h"
|
||||
#include "SkyRasterizer.h"
|
||||
#include "VegetationRasterizer.h"
|
||||
#include "CameraDefinition.h"
|
||||
#include "ParallelWork.h"
|
||||
#include "CanvasPortion.h"
|
||||
|
@ -25,9 +26,11 @@ SoftwareCanvasRenderer::SoftwareCanvasRenderer(Scenery *scenery) : SoftwareRende
|
|||
|
||||
postprocess_enabled = true;
|
||||
|
||||
rasterizers.push_back(new SkyRasterizer(this, progress, RASTERIZER_CLIENT_SKY));
|
||||
rasterizers.push_back(new WaterRasterizer(this, progress, RASTERIZER_CLIENT_WATER));
|
||||
rasterizers.push_back(new TerrainRasterizer(this, progress, RASTERIZER_CLIENT_TERRAIN));
|
||||
rasterizers.push_back(rasterizer_sky = new SkyRasterizer(this, progress, RASTERIZER_CLIENT_SKY));
|
||||
rasterizers.push_back(rasterizer_water = new WaterRasterizer(this, progress, RASTERIZER_CLIENT_WATER));
|
||||
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;
|
||||
}
|
||||
|
@ -36,9 +39,10 @@ SoftwareCanvasRenderer::~SoftwareCanvasRenderer() {
|
|||
delete canvas;
|
||||
delete progress;
|
||||
|
||||
for (auto &rasterizer : rasterizers) {
|
||||
delete rasterizer;
|
||||
}
|
||||
delete rasterizer_sky;
|
||||
delete rasterizer_water;
|
||||
delete rasterizer_terrain;
|
||||
delete rasterizer_vegetation;
|
||||
}
|
||||
|
||||
void SoftwareCanvasRenderer::setQuality(double factor) {
|
||||
|
@ -50,9 +54,6 @@ void SoftwareCanvasRenderer::setQuality(double factor) {
|
|||
}
|
||||
|
||||
void SoftwareCanvasRenderer::setSoloRasterizer(Rasterizer *rasterizer) {
|
||||
for (auto &rast : rasterizers) {
|
||||
delete rast;
|
||||
}
|
||||
rasterizers.clear();
|
||||
rasterizers.push_back(rasterizer);
|
||||
}
|
||||
|
|
|
@ -34,21 +34,22 @@ class SOFTWARESHARED_EXPORT SoftwareCanvasRenderer : public SoftwareRenderer {
|
|||
}
|
||||
|
||||
inline Rasterizer *getSkyRasterizer() const {
|
||||
return rasterizers[0];
|
||||
return rasterizer_sky;
|
||||
}
|
||||
inline Rasterizer *getWaterRasterizer() const {
|
||||
return rasterizers[1];
|
||||
return rasterizer_water;
|
||||
}
|
||||
inline Rasterizer *getTerrainRasterizer() const {
|
||||
return rasterizers[2];
|
||||
return rasterizer_terrain;
|
||||
}
|
||||
inline Rasterizer *getVegetationRasterizer() const {
|
||||
return rasterizer_vegetation;
|
||||
}
|
||||
|
||||
virtual void setQuality(double factor) override;
|
||||
|
||||
/**
|
||||
* Clear the rasterizers list, and put a single one.
|
||||
*
|
||||
* The renderer takes ownership of the rasterizer.
|
||||
*/
|
||||
void setSoloRasterizer(Rasterizer *rasterizer);
|
||||
|
||||
|
@ -112,7 +113,13 @@ class SOFTWARESHARED_EXPORT SoftwareCanvasRenderer : public SoftwareRenderer {
|
|||
|
||||
Canvas *canvas;
|
||||
int samples;
|
||||
|
||||
vector<Rasterizer *> rasterizers;
|
||||
Rasterizer *rasterizer_sky;
|
||||
Rasterizer *rasterizer_water;
|
||||
Rasterizer *rasterizer_terrain;
|
||||
Rasterizer *rasterizer_vegetation;
|
||||
|
||||
bool started;
|
||||
bool finished;
|
||||
bool interrupted;
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "CloudsDefinition.h"
|
||||
#include "TerrainRenderer.h"
|
||||
#include "TexturesRenderer.h"
|
||||
#include "VegetationRenderer.h"
|
||||
#include "WaterRenderer.h"
|
||||
#include "SkyRasterizer.h"
|
||||
#include "TerrainRasterizer.h"
|
||||
|
@ -32,6 +33,7 @@ SoftwareRenderer::SoftwareRenderer(Scenery *scenery) : scenery(scenery) {
|
|||
clouds_renderer = new CloudsRenderer(this);
|
||||
terrain_renderer = new TerrainRenderer(this);
|
||||
textures_renderer = new TexturesRenderer(this);
|
||||
vegetation_renderer = new VegetationRenderer(this);
|
||||
water_renderer = new WaterRenderer(this);
|
||||
|
||||
nightsky_renderer = new NightSky(this);
|
||||
|
@ -42,6 +44,7 @@ SoftwareRenderer::SoftwareRenderer(Scenery *scenery) : scenery(scenery) {
|
|||
|
||||
lighting->registerFilter(water_renderer);
|
||||
lighting->registerFilter(terrain_renderer);
|
||||
lighting->registerFilter(vegetation_renderer);
|
||||
lighting->registerFilter(clouds_renderer);
|
||||
lighting->registerSource(atmosphere_renderer);
|
||||
|
||||
|
@ -61,6 +64,7 @@ SoftwareRenderer::~SoftwareRenderer() {
|
|||
delete clouds_renderer;
|
||||
delete terrain_renderer;
|
||||
delete textures_renderer;
|
||||
delete vegetation_renderer;
|
||||
delete water_renderer;
|
||||
}
|
||||
|
||||
|
|
|
@ -61,6 +61,9 @@ class SOFTWARESHARED_EXPORT SoftwareRenderer {
|
|||
inline WaterRenderer *getWaterRenderer() const {
|
||||
return water_renderer;
|
||||
}
|
||||
inline VegetationRenderer *getVegetationRenderer() const {
|
||||
return vegetation_renderer;
|
||||
}
|
||||
|
||||
inline NightSky *getNightSky() const {
|
||||
return nightsky_renderer;
|
||||
|
@ -95,6 +98,7 @@ class SOFTWARESHARED_EXPORT SoftwareRenderer {
|
|||
TexturesRenderer *textures_renderer;
|
||||
WaterRenderer *water_renderer;
|
||||
NightSky *nightsky_renderer;
|
||||
VegetationRenderer *vegetation_renderer;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
|
||||
TerrainRasterizer::TerrainRasterizer(SoftwareRenderer *renderer, RenderProgress *progress, int client_id)
|
||||
: 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) {
|
||||
|
@ -68,18 +70,36 @@ void TerrainRasterizer::renderQuad(CanvasPortion *canvas, double x, double z, do
|
|||
ov4.z = z;
|
||||
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) {
|
||||
pushDisplacedQuad(canvas, dv1, dv2, dv3, dv4, ov1, ov2, ov3, ov4);
|
||||
}
|
||||
}
|
||||
|
||||
void TerrainRasterizer::getChunk(TerrainRasterizer::TerrainChunkInfo *chunk, double x, double z, double size,
|
||||
bool displaced) {
|
||||
void TerrainRasterizer::setYOffset(double offset) {
|
||||
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_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_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;
|
||||
if (displaced) {
|
||||
displacement_power = 0.0;
|
||||
|
@ -136,7 +156,7 @@ int TerrainRasterizer::performTessellation(CanvasPortion *canvas, bool displaced
|
|||
|
||||
while (radius_int < 20000.0) {
|
||||
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) {
|
||||
result += chunk.detail_hint * chunk.detail_hint;
|
||||
if (canvas) {
|
||||
|
@ -147,7 +167,7 @@ int TerrainRasterizer::performTessellation(CanvasPortion *canvas, bool displaced
|
|||
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) {
|
||||
result += chunk.detail_hint * chunk.detail_hint;
|
||||
if (canvas) {
|
||||
|
@ -158,7 +178,7 @@ int TerrainRasterizer::performTessellation(CanvasPortion *canvas, bool displaced
|
|||
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) {
|
||||
result += chunk.detail_hint * chunk.detail_hint;
|
||||
if (canvas) {
|
||||
|
@ -169,7 +189,7 @@ int TerrainRasterizer::performTessellation(CanvasPortion *canvas, bool displaced
|
|||
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) {
|
||||
result += chunk.detail_hint * chunk.detail_hint;
|
||||
if (canvas) {
|
||||
|
|
|
@ -36,6 +36,14 @@ class SOFTWARESHARED_EXPORT TerrainRasterizer : public Rasterizer {
|
|||
virtual void rasterizeToCanvas(CanvasPortion *canvas) 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:
|
||||
/**
|
||||
* 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 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:
|
||||
double yoffset;
|
||||
|
||||
// Quality control
|
||||
double base_chunk_size;
|
||||
double detail_factor;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "TerrainRenderer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include "SoftwareRenderer.h"
|
||||
#include "Scenery.h"
|
||||
#include "TerrainDefinition.h"
|
||||
|
@ -185,3 +186,17 @@ bool TerrainRenderer::applyLightFilter(LightComponent &light, const Vector3 &at)
|
|||
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;
|
||||
}
|
||||
|
|
|
@ -32,6 +32,11 @@ class SOFTWARESHARED_EXPORT TerrainRenderer : public LightFilter {
|
|||
virtual Color getFinalColor(const Vector3 &location, double precision);
|
||||
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:
|
||||
SoftwareRenderer *parent;
|
||||
TerrainRayWalker *walker_ray;
|
||||
|
|
130
src/render/software/VegetationModelRenderer.cpp
Normal file
130
src/render/software/VegetationModelRenderer.cpp
Normal 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();
|
||||
}
|
42
src/render/software/VegetationModelRenderer.h
Normal file
42
src/render/software/VegetationModelRenderer.h
Normal 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
|
39
src/render/software/VegetationRasterizer.cpp
Normal file
39
src/render/software/VegetationRasterizer.cpp
Normal 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;
|
||||
}
|
||||
}
|
20
src/render/software/VegetationRasterizer.h
Normal file
20
src/render/software/VegetationRasterizer.h
Normal 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
|
141
src/render/software/VegetationRenderer.cpp
Normal file
141
src/render/software/VegetationRenderer.cpp
Normal 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;
|
||||
}
|
||||
}
|
56
src/render/software/VegetationRenderer.h
Normal file
56
src/render/software/VegetationRenderer.h
Normal 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
|
9
src/render/software/VegetationResult.cpp
Normal file
9
src/render/software/VegetationResult.cpp
Normal 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;
|
||||
}
|
42
src/render/software/VegetationResult.h
Normal file
42
src/render/software/VegetationResult.h
Normal 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
|
|
@ -34,6 +34,7 @@ class TexturesRenderer;
|
|||
class WaterRenderer;
|
||||
|
||||
class Rasterizer;
|
||||
class OverlayRasterizer;
|
||||
class SkyRasterizer;
|
||||
class TerrainRasterizer;
|
||||
|
||||
|
@ -52,6 +53,9 @@ class TerrainRayWalker;
|
|||
|
||||
class GodRaysSampler;
|
||||
class GodRaysResult;
|
||||
class VegetationResult;
|
||||
class VegetationRenderer;
|
||||
class VegetationModelRenderer;
|
||||
|
||||
class Canvas;
|
||||
class CanvasPortion;
|
||||
|
|
|
@ -3,43 +3,43 @@
|
|||
#include <cmath>
|
||||
#include "CappedCylinder.h"
|
||||
|
||||
TEST(CappedCylinder, checkRayIntersection) {
|
||||
TEST(CappedCylinder, findRayIntersection) {
|
||||
CappedCylinder cylinder(VECTOR_DOWN, VECTOR_UP, 1.0, 2.0);
|
||||
|
||||
int intersect_count;
|
||||
Vector3 p1, p2;
|
||||
|
||||
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);
|
||||
|
||||
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_VECTOR3_COORDS(p1, 1.0, 0.0, 0.0);
|
||||
|
||||
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_VECTOR3_COORDS(p1, 0.5, 0.0, -cos(asin(0.5)));
|
||||
EXPECT_VECTOR3_COORDS(p2, 0.5, 0.0, cos(asin(0.5)));
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
// diagonal cases (through a cap)
|
||||
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_VECTOR3_COORDS(p1, -1.0, 0.0, 0.0);
|
||||
|
||||
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_VECTOR3_COORDS(p1, 1.0, 0.0, 0.0);
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include "InfiniteCylinder.h"
|
||||
#include <cmath>
|
||||
|
||||
TEST(InfiniteCylinder, checkRayIntersection) {
|
||||
TEST(InfiniteCylinder, findRayIntersection) {
|
||||
InfiniteRay ray(VECTOR_ZERO, VECTOR_UP);
|
||||
InfiniteCylinder cylinder(ray, 1.0);
|
||||
|
||||
|
@ -12,30 +12,30 @@ TEST(InfiniteCylinder, checkRayIntersection) {
|
|||
Vector3 p1, p2;
|
||||
|
||||
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);
|
||||
|
||||
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_VECTOR3_COORDS(p1, 1.0, 0.0, 0.0);
|
||||
|
||||
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_VECTOR3_COORDS(p1, 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));
|
||||
InfiniteCylinder cylinder(ray, 0.5);
|
||||
|
||||
int intersect_count;
|
||||
Vector3 p1, p2;
|
||||
|
||||
intersect_count = cylinder.checkRayIntersection(
|
||||
InfiniteRay::fromPoints(Vector3(0.0, 1.5, 0.0), Vector3(0.0, 1.5, 2.0)), &p1, &p2);
|
||||
intersect_count =
|
||||
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_VECTOR3_COORDS(p1, 0.0, 1.5, 0.5);
|
||||
EXPECT_VECTOR3_COORDS(p2, 0.0, 1.5, 1.5);
|
||||
|
|
21
src/tests/OpenGLVegetationImpostor_Test.cpp
Normal file
21
src/tests/OpenGLVegetationImpostor_Test.cpp
Normal 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));
|
||||
}
|
67
src/tests/OpenGLVegetationLayer_Test.cpp
Normal file
67
src/tests/OpenGLVegetationLayer_Test.cpp
Normal 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());
|
||||
}
|
19
src/tests/OpenGLVegetation_Test.cpp
Normal file
19
src/tests/OpenGLVegetation_Test.cpp
Normal 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());
|
||||
}
|
61
src/tests/OverlayRasterizer_Test.cpp
Normal file
61
src/tests/OverlayRasterizer_Test.cpp
Normal 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);
|
||||
}
|
|
@ -54,3 +54,17 @@ TEST(SpaceSegment, iterateOnGrid_Negative) {
|
|||
ASSERT_EQ(1, (int)it.locations.size());
|
||||
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));
|
||||
}
|
||||
|
|
|
@ -3,20 +3,26 @@
|
|||
#include "InfiniteRay.h"
|
||||
#include "Sphere.h"
|
||||
|
||||
TEST(Sphere, checkRayIntersection) {
|
||||
TEST(Sphere, findRayIntersection) {
|
||||
Sphere sphere(Vector3(2.0, 1.0, 1.0), 0.5);
|
||||
|
||||
int intersect_count;
|
||||
Vector3 p1, p2;
|
||||
|
||||
intersect_count = sphere.checkRayIntersection(InfiniteRay(Vector3(0.0, 0.0, 0.0), VECTOR_SOUTH), &p1, &p2);
|
||||
ASSERT_EQ(0, intersect_count);
|
||||
intersect_count = sphere.checkRayIntersection(InfiniteRay(Vector3(0.0, 0.0, 0.0), VECTOR_SOUTH));
|
||||
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);
|
||||
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);
|
||||
EXPECT_VECTOR3_COORDS(p1, 2.0, 1.0, 0.5);
|
||||
EXPECT_VECTOR3_COORDS(p2, 2.0, 1.0, 1.5);
|
||||
|
|
Loading…
Reference in a new issue