Add generation of plantation vegetation to TileDetailsCallback.

Plantations are regularly spaced vegetation. This effect is switched
on by the is_plantation material property. Vegetation is laid out
at integer spacings in x and y, with the spacing determined by the
usual coverage properties.
This commit is contained in:
James.Hester
2019-11-16 11:16:09 +11:00
parent 56933067c0
commit 0f9fe8adef
3 changed files with 197 additions and 87 deletions
+103 -49
View File
@@ -285,100 +285,154 @@ public:
} }
} }
} }
void addRandomTreePoints(float wood_coverage, void addRandomTreePoints(float wood_coverage,
osg::Texture2D* object_mask, osg::Texture2D* object_mask,
float vegetation_density, float vegetation_density,
float cos_max_density_angle, float cos_max_density_angle,
float cos_zero_density_angle, float cos_zero_density_angle,
bool is_plantation,
std::vector<SGVec3f>& points, std::vector<SGVec3f>& points,
std::vector<SGVec3f>& normals) std::vector<SGVec3f>& normals)
{ {
if ( !geometries.empty() ) { if ( !geometries.empty() ) {
const osg::Vec3Array* vertices = dynamic_cast<osg::Vec3Array*>(geometries[0]->getVertexArray()); const osg::Vec3Array* vertices = dynamic_cast<osg::Vec3Array*>(geometries[0]->getVertexArray());
const osg::Vec2Array* texcoords = dynamic_cast<osg::Vec2Array*>(geometries[0]->getTexCoordArray(0)); const osg::Vec2Array* texcoords = dynamic_cast<osg::Vec2Array*>(geometries[0]->getTexCoordArray(0));
int numPrimitiveSets = geometries[0]->getNumPrimitiveSets(); int numPrimitiveSets = geometries[0]->getNumPrimitiveSets();
if ( numPrimitiveSets > 0 ) { if ( numPrimitiveSets > 0 ) {
const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0); const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0);
unsigned int numIndices = ps->getNumIndices(); unsigned int numIndices = ps->getNumIndices();
for ( unsigned int i=2; i<numIndices; i+= 3 ) { for ( unsigned int i=2; i<numIndices; i+= 3 ) {
SGVec3f v0 = toSG(vertices->operator[](ps->index(i-2))); SGVec3f v0 = toSG(vertices->operator[](ps->index(i-2)));
SGVec3f v1 = toSG(vertices->operator[](ps->index(i-1))); SGVec3f v1 = toSG(vertices->operator[](ps->index(i-1)));
SGVec3f v2 = toSG(vertices->operator[](ps->index(i-0))); SGVec3f v2 = toSG(vertices->operator[](ps->index(i-0)));
SGVec2f t0 = toSG(texcoords->operator[](ps->index(i-2))); SGVec2f t0 = toSG(texcoords->operator[](ps->index(i-2)));
SGVec2f t1 = toSG(texcoords->operator[](ps->index(i-1))); SGVec2f t1 = toSG(texcoords->operator[](ps->index(i-1)));
SGVec2f t2 = toSG(texcoords->operator[](ps->index(i-0))); SGVec2f t2 = toSG(texcoords->operator[](ps->index(i-0)));
SGVec3f normal = cross(v1 - v0, v2 - v0); SGVec3f normal = cross(v1 - v0, v2 - v0);
// Ensure the slope isn't too steep by checking the // Ensure the slope isn't too steep by checking the
// cos of the angle between the slope normal and the // cos of the angle between the slope normal and the
// vertical (conveniently the z-component of the normalized // vertical (conveniently the z-component of the normalized
// normal) and values passed in. // normal) and values passed in.
float alpha = normalize(normal).z(); float alpha = normalize(normal).z();
float slope_density = 1.0; float slope_density = 1.0;
if (alpha < cos_zero_density_angle) if (alpha < cos_zero_density_angle)
continue; // Too steep for any vegetation continue; // Too steep for any vegetation
if (alpha < cos_max_density_angle) { if (alpha < cos_max_density_angle) {
slope_density = slope_density =
(alpha - cos_zero_density_angle) / (cos_max_density_angle - cos_zero_density_angle); (alpha - cos_zero_density_angle) / (cos_max_density_angle - cos_zero_density_angle);
} }
// Compute the area // Compute the area
float area = 0.5f*length(normal); float area = 0.5f*length(normal);
if (area <= SGLimitsf::min()) if (area <= SGLimitsf::min())
continue; continue;
// Determine the number of trees, taking into account vegetation if (!is_plantation) {
// density (which is linear) and the slope density factor. // Determine the number of trees, taking into account vegetation
// Use a zombie door method to create the proper random chance // density (which is linear) and the slope density factor.
// of a tree being created for partial values. // Use a zombie door method to create the proper random chance
int woodcount = (int) (vegetation_density * vegetation_density * // of a tree being created for partial values.
slope_density * int woodcount = (int) (vegetation_density * vegetation_density *
area / wood_coverage + mt_rand(&seed)); slope_density *
area / wood_coverage + mt_rand(&seed));
for (int j = 0; j < woodcount; j++) { for (int j = 0; j < woodcount; j++) {
float a = mt_rand(&seed); // Use barycentric coordinates
float b = mt_rand(&seed); float a = mt_rand(&seed);
float b = mt_rand(&seed);
if ( a + b > 1.0f ) {
a = 1.0f - a; if ( a + b > 1.0f ) {
b = 1.0f - b; a = 1.0f - a;
} b = 1.0f - b;
}
float c = 1.0f - a - b;
float c = 1.0f - a - b;
SGVec3f randomPoint = a*v0 + b*v1 + c*v2;
SGVec3f randomPoint = a*v0 + b*v1 + c*v2;
if (object_mask != NULL) { if (object_mask != NULL) {
SGVec2f texCoord = a*t0 + b*t1 + c*t2; SGVec2f texCoord = a*t0 + b*t1 + c*t2;
// Check this random point against the object mask // Check this random point against the object mask
// green (for trees) channel. // green (for trees) channel.
osg::Image* img = object_mask->getImage(); osg::Image* img = object_mask->getImage();
unsigned int x = (int) (img->s() * texCoord.x()) % img->s(); unsigned int x = (int) (img->s() * texCoord.x()) % img->s();
unsigned int y = (int) (img->t() * texCoord.y()) % img->t(); unsigned int y = (int) (img->t() * texCoord.y()) % img->t();
if (mt_rand(&seed) < img->getColor(x, y).g()) { if (mt_rand(&seed) < img->getColor(x, y).g()) {
// The red channel contains the rotation for this object // The red channel contains the rotation for this object
points.push_back(randomPoint); points.push_back(randomPoint);
normals.push_back(normalize(normal)); normals.push_back(normalize(normal));
}
} else {
points.push_back(randomPoint);
normals.push_back(normalize(normal));
}
} }
} else { } else { // regularly-spaced vegetation
points.push_back(randomPoint); // separate vegetation in integral 1m units
normals.push_back(normalize(normal)); int separation = (int) ceil(sqrt(wood_coverage));
} float max_x = ceil(max(max(v1.x(),v2.x()),v0.x()));
float min_x = floor(min(min(v1.x(),v2.x()),v0.x()));
float max_y = ceil(max(max(v1.y(),v2.y()),v0.y()));
float min_y = floor(min(min(v1.y(),v2.y()),v0.y()));
/* equation of the plane ax+by+cz+d=0, need d */
float d = -1*(normal.x()*v0.x() + normal.y()*v0.y()+normal.z()*v0.z());
/* Now loop over a grid, skipping points not in the triangle */
int x_steps = (int) (max_x - min_x)/separation;
int y_steps = (int) (max_y - min_y)/separation;
SGVec2f v02d = SGVec2f(v0.x(),v0.y());
SGVec2f v12d = SGVec2f(v1.x(),v1.y());
SGVec2f v22d = SGVec2f(v2.x(),v2.y());
for (int jx = 0; jx < x_steps; jx++) {
float ptx = min_x + jx * separation;
for (int jy = 0; jy < y_steps; jy++) {
float pty = min_y + jy * separation;
SGVec2f newpt = SGVec2f(ptx,pty);
if (!point_in_triangle(newpt,v02d,v12d,v22d))
continue;
// z = (-ax-by-d)/c; c is not zero as
// that would be alpha of 1.0
float ptz = (-normal.x()*ptx - normal.y()*pty-d)/normal.z();
SGVec3f randomPoint = SGVec3f(ptx,pty,ptz);
if (object_mask != NULL) {
// Check this point against the object mask
// green (for trees) channel.
osg::Image* img = object_mask->getImage();
unsigned int x = (int) (img->s() * newpt.x()) % img->s();
unsigned int y = (int) (img->t() * newpt.y()) % img->t();
if (mt_rand(&seed) < img->getColor(x, y).g()) {
// The red channel contains the rotation for this object
points.push_back(randomPoint);
normals.push_back(normalize(normal));
}
} else {
points.push_back(randomPoint);
normals.push_back(normalize(normal));
}
}
}
} }
} }
} }
} }
} }
#if 0 #if 0
// debug : this will save the tile as a shapefile that can be viewed in QGIS. // debug : this will save the tile as a shapefile that can be viewed in QGIS.
// NOTE: this is really slow.... // NOTE: this is really slow....
+93 -38
View File
@@ -216,6 +216,7 @@ public:
float vegetation_density, float vegetation_density,
float cos_max_density_angle, float cos_max_density_angle,
float cos_zero_density_angle, float cos_zero_density_angle,
bool is_plantation,
std::vector<SGVec3f>& points, std::vector<SGVec3f>& points,
std::vector<SGVec3f>& normals) std::vector<SGVec3f>& normals)
{ {
@@ -249,50 +250,104 @@ public:
float area = 0.5f*length(normal); float area = 0.5f*length(normal);
if (area <= SGLimitsf::min()) if (area <= SGLimitsf::min())
continue; continue;
if (!is_plantation) {
// Determine the number of trees, taking into account vegetation // Determine the number of trees, taking into account vegetation
// density (which is linear) and the slope density factor. // density (which is linear) and the slope density factor.
// Use a zombie door method to create the proper random chance // Use a zombie door method to create the proper random chance
// of a tree being created for partial values. // of a tree being created for partial values.
int woodcount = (int) (vegetation_density * vegetation_density * int woodcount = (int) (vegetation_density * vegetation_density *
slope_density * slope_density *
area / wood_coverage + mt_rand(&seed)); area / wood_coverage + mt_rand(&seed));
for (int j = 0; j < woodcount; j++) { for (int j = 0; j < woodcount; j++) {
float a = mt_rand(&seed); float a = mt_rand(&seed);
float b = mt_rand(&seed); float b = mt_rand(&seed);
if ( a + b > 1.0f ) { if ( a + b > 1.0f ) {
a = 1.0f - a; a = 1.0f - a;
b = 1.0f - b; b = 1.0f - b;
}
float c = 1.0f - a - b;
SGVec3f randomPoint = a*v0 + b*v1 + c*v2;
if (object_mask != NULL) {
SGVec2f texCoord = a*t0 + b*t1 + c*t2;
// Check this random point against the object mask
// green (for trees) channel.
osg::Image* img = object_mask->getImage();
unsigned int x = (int) (img->s() * texCoord.x()) % img->s();
unsigned int y = (int) (img->t() * texCoord.y()) % img->t();
if (mt_rand(&seed) < img->getColor(x, y).g()) {
// The red channel contains the rotation for this object
points.push_back(randomPoint);
normals.push_back(normalize(normal));
} }
} else {
points.push_back(randomPoint); float c = 1.0f - a - b;
normals.push_back(normalize(normal));
} SGVec3f randomPoint = a*v0 + b*v1 + c*v2;
if (object_mask != NULL) {
SGVec2f texCoord = a*t0 + b*t1 + c*t2;
// Check this random point against the object mask
// green (for trees) channel.
osg::Image* img = object_mask->getImage();
unsigned int x = (int) (img->s() * texCoord.x()) % img->s();
unsigned int y = (int) (img->t() * texCoord.y()) % img->t();
if (mt_rand(&seed) < img->getColor(x, y).g()) {
// The red channel contains the rotation for this object
points.push_back(randomPoint);
normals.push_back(normalize(normal));
}
} else {
points.push_back(randomPoint);
normals.push_back(normalize(normal));
}
}
} else { // regularly-spaced vegetation
int separation = (int) ceil(sqrt(wood_coverage));
float max_x = ceil(max(max(v1.x(),v2.x()),v0.x()));
float min_x = floor(min(min(v1.x(),v2.x()),v0.x()));
float max_y = ceil(max(max(v1.y(),v2.y()),v0.y()));
float min_y = floor(min(min(v1.y(),v2.y()),v0.y()));
// equation of the plane ax+by+cz+d=0, need d
float d = -1*(normal.x()*v0.x() + normal.y()*v0.y()+normal.z()*v0.z());
// Now loop over a grid, skipping points not in the triangle
int x_steps = (int) (max_x - min_x)/separation;
int y_steps = (int) (max_y - min_y)/separation;
SGVec2f v02d = SGVec2f(v0.x(),v0.y());
SGVec2f v12d = SGVec2f(v1.x(),v1.y());
SGVec2f v22d = SGVec2f(v2.x(),v2.y());
for (int jx = 0; jx < x_steps; jx++) {
float ptx = min_x + jx * separation;
for (int jy = 0; jy < y_steps; jy++) {
float pty = min_y + jy * separation;
SGVec2f newpt = SGVec2f(ptx,pty);
if (!point_in_triangle(newpt,v02d,v12d,v22d))
continue;
// z = (-ax-by-d)/c; c is not zero as
// that would be alpha of 1.0
float ptz = (-normal.x()*ptx - normal.y()*pty-d)/normal.z();
SGVec3f randomPoint = SGVec3f(ptx,pty,ptz);
if (object_mask != NULL) {
// Check this point against the object mask
// green (for trees) channel.
osg::Image* img = object_mask->getImage();
unsigned int x = (int) (img->s() * newpt.x()) % img->s();
unsigned int y = (int) (img->t() * newpt.y()) % img->t();
if (mt_rand(&seed) < img->getColor(x, y).g()) {
// The red channel contains the rotation for this object
points.push_back(randomPoint);
normals.push_back(normalize(normal));
}
} else {
points.push_back(randomPoint);
normals.push_back(normalize(normal));
}
}
}
} }
} }
} }
void addRandomPoints(double coverage, void addRandomPoints(double coverage,
double spacing, double spacing,
osg::Texture2D* object_mask, osg::Texture2D* object_mask,
@@ -753,6 +753,7 @@ public:
vegetation_density, vegetation_density,
mat->get_cos_tree_max_density_slope_angle(), mat->get_cos_tree_max_density_slope_angle(),
mat->get_cos_tree_zero_density_slope_angle(), mat->get_cos_tree_zero_density_slope_angle(),
mat->get_is_plantation(),
randomPoints, randomPoints,
randomPointNormals); randomPointNormals);