From 6fb47e8e686bdea1d4997ac3169ebab2ccd519de Mon Sep 17 00:00:00 2001 From: Stuart Buchanan Date: Tue, 9 Feb 2021 17:52:52 +0000 Subject: [PATCH] WS30: Random vegetation from Marc EBERHARD Generate random vegetation of VPB tiles. Currently prototype that generates same vegetation for all landclass types. Needs additional work to a) make the scenegraph more efficient b) support different material definitions of vegetation. --- simgear/scene/tgdb/TreeBin.cxx | 4 +- simgear/scene/tgdb/TreeBin.hxx | 2 +- simgear/scene/tgdb/VPBTechnique.cxx | 228 +++++++++++++++++++++++++++- simgear/scene/tgdb/VPBTechnique.hxx | 7 + 4 files changed, 237 insertions(+), 4 deletions(-) diff --git a/simgear/scene/tgdb/TreeBin.cxx b/simgear/scene/tgdb/TreeBin.cxx index ed74a281..0827923d 100644 --- a/simgear/scene/tgdb/TreeBin.cxx +++ b/simgear/scene/tgdb/TreeBin.cxx @@ -368,7 +368,7 @@ struct QuadTreeCleaner : public osg::NodeVisitor // primitive tree geometry for all the forests of the same type. osg::Group* createForest(SGTreeBinList& forestList, const osg::Matrix& transform, - const SGReaderWriterOptions* options) + const SGReaderWriterOptions* options, int depth) { Matrix transInv = Matrix::inverse(transform); static Matrix ident; @@ -417,7 +417,7 @@ osg::Group* createForest(SGTreeBinList& forestList, const osg::Matrix& transform // Now, create a quadtree for the forest. ShaderGeometryQuadtree quadtree(GetTreeCoord(), AddTreesLeafObject(), - SG_TREE_QUAD_TREE_DEPTH, + depth, MakeTreesLeaf(forest->range, forest->texture_varieties, forest->width, forest->height, effect)); // Transform tree positions from the "geocentric" positions we diff --git a/simgear/scene/tgdb/TreeBin.hxx b/simgear/scene/tgdb/TreeBin.hxx index 9b4b280e..b3bf6072 100644 --- a/simgear/scene/tgdb/TreeBin.hxx +++ b/simgear/scene/tgdb/TreeBin.hxx @@ -84,6 +84,6 @@ void clearSharedTreeGeometry(); typedef std::list SGTreeBinList; osg::Group* createForest(SGTreeBinList& forestList, const osg::Matrix& transform, - const SGReaderWriterOptions* options); + const SGReaderWriterOptions* options, int depth=3); } #endif diff --git a/simgear/scene/tgdb/VPBTechnique.cxx b/simgear/scene/tgdb/VPBTechnique.cxx index 1b7173c6..f53885e7 100644 --- a/simgear/scene/tgdb/VPBTechnique.cxx +++ b/simgear/scene/tgdb/VPBTechnique.cxx @@ -35,14 +35,15 @@ #include #include - #include +#include #include #include #include #include #include "VPBTechnique.hxx" +#include "TreeBin.hxx" using namespace osgTerrain; using namespace simgear; @@ -157,12 +158,14 @@ void VPBTechnique::init(int dirtyMask, bool assumeMultiThreaded) else { applyColorLayers(*buffer, masterLocator); + applyTrees(*buffer, masterLocator); } } else { generateGeometry(*buffer, masterLocator, centerModel); applyColorLayers(*buffer, masterLocator); + applyTrees(*buffer, masterLocator); } if (buffer->_transform.valid()) buffer->_transform->setThreadSafeRefUnref(true); @@ -1512,6 +1515,229 @@ void VPBTechnique::applyColorLayers(BufferData& buffer, Locator* masterLocator) } } +double VPBTechnique::det2(const osg::Vec2d a, const osg::Vec2d b) +{ + return a.x() * b.y() - b.x() * a.y(); +} + +osg::Vec2d* VPBTechnique::_randomOffsets = 0; + +osg::Vec2d VPBTechnique::getRandomOffset(int lon, int lat) +{ + const unsigned initial_seed = 42; + const int N_rnd = 1; // 1 is a uniform random distribution, increase to 5 or + // more for plantation + const int bits = 5; + const int N = 1 << (2 * bits); + const int N_2 = 1 << bits; + const int mask = N_2 - 1; + + if (!_randomOffsets) + { + _randomOffsets = new osg::Vec2d[N]; + mt seed; + mt_init(&seed, initial_seed); + + for (int i = 0; i < N; i++) + { + double x = 0.0; + double y = 0.0; + + for (int j = 0; j < N_rnd; j++) + { + x += mt_rand(&seed); + y += mt_rand(&seed); + } + + x = x / N_rnd - 0.5; + y = y / N_rnd - 0.5; + _randomOffsets[i].set(x, y); + } + + } + + return _randomOffsets[N_2 * (lat & mask) + (lon & mask)]; +} + +void VPBTechnique::applyTrees(BufferData& buffer, Locator* masterLocator) +{ + bool use_random_vegetation = false; + float vegetation_density = 1.0; + int vegetation_lod_range = 6; + float randomness = 1.0; + + SGPropertyNode* propertyNode = _options->getPropertyNode().get(); + + if (propertyNode) { + use_random_vegetation = propertyNode->getBoolValue("/sim/rendering/random-vegetation", use_random_vegetation); + vegetation_density = propertyNode->getFloatValue("/sim/rendering/vegetation-density", vegetation_density); + vegetation_lod_range = propertyNode->getIntValue("/sim/rendering/vegetation-lod", vegetation_lod_range); + } + + // Do not generate vegetation for tiles too far away or if we explicitly don't generate vegetation + if ((!use_random_vegetation) || (_terrainTile->getTileID().level < vegetation_lod_range)) { + return; + } + + SGMaterialLibPtr matlib = _options->getMaterialLib(); + SGMaterial* mat = 0; + osg::Vec3d world; + SGGeod loc; + SGGeoc cloc; + + if (matlib) + { + // Determine the center of the tile, sadly non-trivial. + osg::Vec3d tileloc = computeCenter(buffer, masterLocator); + masterLocator->convertLocalToModel(tileloc, world); + const SGVec3d world2 = SGVec3d(world.x(), world.y(), world.z()); + loc = SGGeod::fromCart(world2); + cloc = SGGeoc::fromCart(world2); + SG_LOG(SG_TERRAIN, SG_DEBUG, "Applying VPB material " << loc); + SGMaterialCache* matcache = _options->getMaterialLib()->generateMatCache(loc, _options); + mat = matcache->find("EvergreenForest"); + delete matcache; + } + + // Should probably be an error if we can't find a material, but we + // return for now. + + if (!mat) + { + return; + } + + TreeBin* bin = new TreeBin(); + bin->texture = mat->get_tree_texture(); + bin->teffect = mat->get_tree_effect(); + bin->range = mat->get_tree_range(); + bin->width = mat->get_tree_width(); + bin->height = mat->get_tree_height(); + bin->texture_varieties = mat->get_tree_varieties(); + + const osg::Matrixd R_vert = osg::Matrixd::rotate( + M_PI / 2.0 - loc.getLatitudeRad(), osg::Vec3d(0.0, 1.0, 0.0), + loc.getLongitudeRad(), osg::Vec3d(0.0, 0.0, 1.0), + 0.0, osg::Vec3d(1.0, 0.0, 0.0)); + + const osg::Array* vertices = buffer._geometry->getVertexArray(); + const osg::Array* texture_coords = buffer._geometry->getTexCoordArray(0); + osgTerrain::Layer* colorLayer = _terrainTile->getColorLayer(0); + osg::Image* image = colorLayer->getImage(); + + // Just want the first primitive set, not the others which will be the "skirts" below. + const osg::PrimitiveSet* primSet = buffer._geometry->getPrimitiveSet(0); + const osg::DrawElements* drawElements = primSet->getDrawElements(); + const unsigned int triangle_count = drawElements->getNumPrimitives(); + + const double lon = loc.getLongitudeRad(); + const double lat = loc.getLatitudeRad(); + const double clon = cloc.getLongitudeRad(); + const double clat = cloc.getLatitudeRad(); + const double r_E_lat = /* 6356752.3 */ 6.375993e+06; + const double r_E_lon = /* 6378137.0 */ 6.389377e+06; + const double C = r_E_lon * cos(lat); + const double one_over_C = (fabs(C) > 1.0e-4) ? (1.0 / C) : 0.0; + const double one_over_r_E = 1.0 / r_E_lat; + const double delta_lat = sqrt(1000.0 / vegetation_density) / r_E_lat; + const double delta_lon = sqrt(1000.0 / vegetation_density) / (r_E_lon * cos(loc.getLatitudeRad())); + + const osg::Matrix rotation_vertices_c = osg::Matrix::rotate( + M_PI / 2 - clat, osg::Vec3d(0.0, 1.0, 0.0), + clon, osg::Vec3d(0.0, 0.0, 1.0), + 0.0, osg::Vec3d(1.0, 0.0, 0.0)); + + const osg::Matrix rotation_vertices_g = osg::Matrix::rotate( + M_PI / 2 - lat, osg::Vec3d(0.0, 1.0, 0.0), + lon, osg::Vec3d(0.0, 0.0, 1.0), + 0.0, osg::Vec3d(1.0, 0.0, 0.0)); + + for (unsigned int i = 0; i < triangle_count; i++) + { + const int i0 = drawElements->index(3 * i); + const int i1 = drawElements->index(3 * i + 1); + const int i2 = drawElements->index(3 * i + 2); + + const osg::Vec3* v0 = (osg::Vec3*) vertices->getDataPointer(i0); + const osg::Vec3* v1 = (osg::Vec3*) vertices->getDataPointer(i1); + const osg::Vec3* v2 = (osg::Vec3*) vertices->getDataPointer(i2); + + const osg::Vec3d v_0 = *v0; + const osg::Vec3d v_x = *v1 - *v0; + const osg::Vec3d v_y = *v2 - *v0; + + osg::Vec3 n = v_x ^ v_y; + n /= n.length(); + + const osg::Vec3d v_0_g = R_vert * *v0; + const osg::Vec3d v_1_g = R_vert * *v1; + const osg::Vec3d v_2_g = R_vert * *v2; + + const osg::Vec2d ll_0 = osg::Vec2d(v_0_g.y() * one_over_C + lon, -v_0_g.x() * one_over_r_E + lat); + const osg::Vec2d ll_1 = osg::Vec2d(v_1_g.y() * one_over_C + lon, -v_1_g.x() * one_over_r_E + lat); + const osg::Vec2d ll_2 = osg::Vec2d(v_2_g.y() * one_over_C + lon, -v_2_g.x() * one_over_r_E + lat); + + const osg::Vec2d ll_O = ll_0; + const osg::Vec2d ll_x = osg::Vec2d((v_1_g.y() - v_0_g.y()) * one_over_C, -(v_1_g.x() - v_0_g.x()) * one_over_r_E); + const osg::Vec2d ll_y = osg::Vec2d((v_2_g.y() - v_0_g.y()) * one_over_C, -(v_2_g.x() - v_0_g.x()) * one_over_r_E); + + const int off_x = ll_O.x() / delta_lon; + const int off_y = ll_O.y() / delta_lat; + const int min_lon = min(min(ll_0.x(), ll_1.x()), ll_2.x()) / delta_lon; + const int max_lon = max(max(ll_0.x(), ll_1.x()), ll_2.x()) / delta_lon; + const int min_lat = min(min(ll_0.y(), ll_1.y()), ll_2.y()) / delta_lat; + const int max_lat = max(max(ll_0.y(), ll_1.y()), ll_2.y()) / delta_lat; + + const osg::Vec2* t0 = (osg::Vec2*) texture_coords->getDataPointer(i0); + const osg::Vec2* t1 = (osg::Vec2*) texture_coords->getDataPointer(i1); + const osg::Vec2* t2 = (osg::Vec2*) texture_coords->getDataPointer(i2); + + const osg::Vec2d t_0 = *t0; + const osg::Vec2d t_x = *t1 - *t0; + const osg::Vec2d t_y = *t2 - *t0; + + const double D = det2(ll_x, ll_y); + + for (int lat_int = min_lat - 1; lat_int <= max_lat + 1; lat_int++) + { + const double lat = (lat_int - off_y) * delta_lat; + + for (int lon_int = min_lon - 1; lon_int <= max_lon + 1; lon_int++) + { + const double lon = (lon_int - off_x) * delta_lon; + const osg::Vec2d ro = getRandomOffset(lon_int, lat_int) * randomness; + const osg::Vec2d p(lon + delta_lon * ro.x(), lat + delta_lat * ro.y()); + const double x = det2(ll_x, p) / D; + const double y = det2(p, ll_y) / D; + + if ((x < 0.0) || (y < 0.0) || (x + y > 1.0)) + { + continue; + } + + const osg::Vec2 tcp = t_x * x + t_y * y + t_0; + const osg::Vec4 tc = image->getColor(tcp); + const int tc_r = floor(tc.r() * 255.0 + 0.5); + + if ((tc_r < 22) || (24 < tc_r)) + { + continue; + } + + const osg::Vec3 vp = v_x * x + v_y * y + v_0; + bin->insert(SGVec3f(vp.x(), vp.y(), vp.z()), SGVec3f(n.x(), n.y(), n.z())); + } + + } + + } + + SGTreeBinList randomForest; + randomForest.push_back(bin); + osg::Group* trees = createForest(randomForest, R_vert,_options, 1); + buffer._transform->addChild(trees); +} + void VPBTechnique::update(osg::NodeVisitor& nv) { if (_terrainTile) _terrainTile->osg::Group::traverse(nv); diff --git a/simgear/scene/tgdb/VPBTechnique.hxx b/simgear/scene/tgdb/VPBTechnique.hxx index c6d2022d..101a5bf5 100644 --- a/simgear/scene/tgdb/VPBTechnique.hxx +++ b/simgear/scene/tgdb/VPBTechnique.hxx @@ -115,6 +115,12 @@ class VPBTechnique : public TerrainTechnique virtual void applyColorLayers(BufferData& buffer, Locator* masterLocator); + virtual double det2(const osg::Vec2d a, const osg::Vec2d b); + + static osg::Vec2d getRandomOffset(int lon, int lat); + + virtual void applyTrees(BufferData& buffer, Locator* masterLocator); + OpenThreads::Mutex _writeBufferMutex; osg::ref_ptr _currentBufferData; osg::ref_ptr _newBufferData; @@ -131,6 +137,7 @@ class VPBTechnique : public TerrainTechnique inline static osg::ref_ptr _constraintGroup = new osg::Group();; inline static std::mutex _constraint_mutex; // protects the _constraintGroup; + static osg::Vec2d* _randomOffsets; }; }