diff --git a/simgear/math/SGVec2.hxx b/simgear/math/SGVec2.hxx index 93655cb1..c0c6cb62 100644 --- a/simgear/math/SGVec2.hxx +++ b/simgear/math/SGVec2.hxx @@ -375,6 +375,29 @@ interpolate(T tau, const SGVec2& v1, const SGVec2& v2) return r; } +// Helper function for point_in_triangle +template +inline +T +pt_determine(const SGVec2& pt1, const SGVec2& pt2, const SGVec2& pt3) +{ + return (pt1.x()-pt3.x()) * (pt2.y()-pt3.y()) - (pt2.x() - pt3.x()) * (pt1.y() - pt3.y()); +} + +// Is testpt inside the triangle formed by the other three points? +template +inline +bool +point_in_triangle(const SGVec2& testpt, const SGVec2& pt1, const SGVec2& pt2, const SGVec2& pt3) +{ + T d1 = pt_determine(testpt,pt1,pt2); + T d2 = pt_determine(testpt,pt2,pt3); + T d3 = pt_determine(testpt,pt3,pt1); + bool has_neg = (d1 < 0) || (d2 < 0) || (d3 < 0); + bool has_pos = (d1 > 0) || (d2 > 0) || (d3 > 0); + return !(has_neg && has_pos); +} + #ifndef NDEBUG template inline diff --git a/simgear/scene/material/mat.cxx b/simgear/scene/material/mat.cxx index f62adbef..c8d856d7 100644 --- a/simgear/scene/material/mat.cxx +++ b/simgear/scene/material/mat.cxx @@ -341,6 +341,7 @@ SGMaterial::read_properties(const SGReaderWriterOptions* options, // Random vegetation properties wood_coverage = props->getDoubleValue("wood-coverage", 0.0); + is_plantation = props->getBoolValue("plantation",false); tree_effect = props->getStringValue("tree-effect", "Effects/tree"); tree_height = props->getDoubleValue("tree-height-m", 0.0); tree_width = props->getDoubleValue("tree-width-m", 0.0); diff --git a/simgear/scene/material/mat.hxx b/simgear/scene/material/mat.hxx index ca70bd65..8e4647d2 100644 --- a/simgear/scene/material/mat.hxx +++ b/simgear/scene/material/mat.hxx @@ -244,6 +244,13 @@ public: */ inline double get_wood_coverage () const { return wood_coverage; } + /** + * Get whether or not vegetation is regularly spaced + * + * @return flag: if true, vegetation is regularly spaced. + */ + inline bool get_is_plantation () const { return is_plantation; } + /** * Get the tree height. * @@ -455,6 +462,9 @@ private: // coverage of woods double wood_coverage; + // are trees regularly planted? + bool is_plantation; + // Range at which trees become visible double tree_range; diff --git a/simgear/scene/tgdb/SGNodeTriangles.hxx b/simgear/scene/tgdb/SGNodeTriangles.hxx index 1d3f72d2..c32dfd89 100644 --- a/simgear/scene/tgdb/SGNodeTriangles.hxx +++ b/simgear/scene/tgdb/SGNodeTriangles.hxx @@ -285,100 +285,154 @@ public: } } } - + void addRandomTreePoints(float wood_coverage, osg::Texture2D* object_mask, float vegetation_density, float cos_max_density_angle, float cos_zero_density_angle, + bool is_plantation, std::vector& points, std::vector& normals) { if ( !geometries.empty() ) { const osg::Vec3Array* vertices = dynamic_cast(geometries[0]->getVertexArray()); const osg::Vec2Array* texcoords = dynamic_cast(geometries[0]->getTexCoordArray(0)); - + int numPrimitiveSets = geometries[0]->getNumPrimitiveSets(); if ( numPrimitiveSets > 0 ) { const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0); unsigned int numIndices = ps->getNumIndices(); - + for ( unsigned int i=2; ioperator[](ps->index(i-2))); SGVec3f v1 = toSG(vertices->operator[](ps->index(i-1))); SGVec3f v2 = toSG(vertices->operator[](ps->index(i-0))); - + SGVec2f t0 = toSG(texcoords->operator[](ps->index(i-2))); SGVec2f t1 = toSG(texcoords->operator[](ps->index(i-1))); SGVec2f t2 = toSG(texcoords->operator[](ps->index(i-0))); - + SGVec3f normal = cross(v1 - v0, v2 - v0); - + // Ensure the slope isn't too steep by checking the // cos of the angle between the slope normal and the // vertical (conveniently the z-component of the normalized // normal) and values passed in. float alpha = normalize(normal).z(); float slope_density = 1.0; - + if (alpha < cos_zero_density_angle) continue; // Too steep for any vegetation - + if (alpha < cos_max_density_angle) { slope_density = (alpha - cos_zero_density_angle) / (cos_max_density_angle - cos_zero_density_angle); } - + // Compute the area float area = 0.5f*length(normal); if (area <= SGLimitsf::min()) continue; - - // Determine the number of trees, taking into account vegetation - // density (which is linear) and the slope density factor. - // Use a zombie door method to create the proper random chance - // of a tree being created for partial values. - int woodcount = (int) (vegetation_density * vegetation_density * - slope_density * - area / wood_coverage + mt_rand(&seed)); - - for (int j = 0; j < woodcount; j++) { - float a = mt_rand(&seed); - float b = mt_rand(&seed); - - if ( a + b > 1.0f ) { - a = 1.0f - a; - 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)); + + if (!is_plantation) { + // Determine the number of trees, taking into account vegetation + // density (which is linear) and the slope density factor. + // Use a zombie door method to create the proper random chance + // of a tree being created for partial values. + int woodcount = (int) (vegetation_density * vegetation_density * + slope_density * + area / wood_coverage + mt_rand(&seed)); + for (int j = 0; j < woodcount; j++) { + // Use barycentric coordinates + float a = mt_rand(&seed); + float b = mt_rand(&seed); + + if ( a + b > 1.0f ) { + a = 1.0f - a; + 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); + normals.push_back(normalize(normal)); + + } } - } else { - points.push_back(randomPoint); - normals.push_back(normalize(normal)); - } + } else { // regularly-spaced vegetation + // separate vegetation in integral 1m units + 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 // debug : this will save the tile as a shapefile that can be viewed in QGIS. // NOTE: this is really slow.... diff --git a/simgear/scene/tgdb/SGTexturedTriangleBin.hxx b/simgear/scene/tgdb/SGTexturedTriangleBin.hxx index 47939a75..3c8132e1 100644 --- a/simgear/scene/tgdb/SGTexturedTriangleBin.hxx +++ b/simgear/scene/tgdb/SGTexturedTriangleBin.hxx @@ -216,6 +216,7 @@ public: float vegetation_density, float cos_max_density_angle, float cos_zero_density_angle, + bool is_plantation, std::vector& points, std::vector& normals) { @@ -249,50 +250,104 @@ public: float area = 0.5f*length(normal); if (area <= SGLimitsf::min()) continue; - - // Determine the number of trees, taking into account vegetation - // density (which is linear) and the slope density factor. - // Use a zombie door method to create the proper random chance - // of a tree being created for partial values. - int woodcount = (int) (vegetation_density * vegetation_density * - slope_density * - area / wood_coverage + mt_rand(&seed)); + if (!is_plantation) { + // Determine the number of trees, taking into account vegetation + // density (which is linear) and the slope density factor. + // Use a zombie door method to create the proper random chance + // of a tree being created for partial values. + int woodcount = (int) (vegetation_density * vegetation_density * + slope_density * + area / wood_coverage + mt_rand(&seed)); - for (int j = 0; j < woodcount; j++) { - float a = mt_rand(&seed); - float b = mt_rand(&seed); + for (int j = 0; j < woodcount; j++) { + float a = mt_rand(&seed); + float b = mt_rand(&seed); - if ( a + b > 1.0f ) { - a = 1.0f - a; - 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)); + if ( a + b > 1.0f ) { + a = 1.0f - a; + b = 1.0f - b; } - } else { - points.push_back(randomPoint); - normals.push_back(normalize(normal)); - } + + 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); + 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, double spacing, osg::Texture2D* object_mask, diff --git a/simgear/scene/tgdb/SGTileDetailsCallback.hxx b/simgear/scene/tgdb/SGTileDetailsCallback.hxx index 58792a68..9fc8a36a 100644 --- a/simgear/scene/tgdb/SGTileDetailsCallback.hxx +++ b/simgear/scene/tgdb/SGTileDetailsCallback.hxx @@ -753,6 +753,7 @@ public: vegetation_density, mat->get_cos_tree_max_density_slope_angle(), mat->get_cos_tree_zero_density_slope_angle(), + mat->get_is_plantation(), randomPoints, randomPointNormals);