diff --git a/simgear/scene/tgdb/CMakeLists.txt b/simgear/scene/tgdb/CMakeLists.txt index b734b5dd..b03687d2 100644 --- a/simgear/scene/tgdb/CMakeLists.txt +++ b/simgear/scene/tgdb/CMakeLists.txt @@ -24,6 +24,7 @@ set(HEADERS ShaderGeometry.hxx TreeBin.hxx VPBElevationSlice.hxx + VPBMaterialHandler.hxx VPBTileBounds.hxx VPBTechnique.hxx apt_signs.hxx @@ -47,6 +48,7 @@ set(SOURCES ShaderGeometry.cxx TreeBin.cxx VPBElevationSlice.cxx + VPBMaterialHandler.cxx VPBTileBounds.cxx VPBTechnique.cxx apt_signs.cxx diff --git a/simgear/scene/tgdb/VPBMaterialHandler.cxx b/simgear/scene/tgdb/VPBMaterialHandler.cxx new file mode 100644 index 00000000..4543f5d4 --- /dev/null +++ b/simgear/scene/tgdb/VPBMaterialHandler.cxx @@ -0,0 +1,383 @@ +/* -*-c++-*- + * VPBMaterialHandler.cxx -- WS30 material-based generation handlers + * + * Copyright (C) 2021 Fahim Dalvi + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "LightBin.hxx" +#include "TreeBin.hxx" +#include "VPBMaterialHandler.hxx" + +using namespace osgTerrain; + +namespace simgear { +// Common VPBMaterialHandler functions +bool VPBMaterialHandler::checkAgainstObjectMask( + osg::Image *objectMaskImage, ImageChannel channel, double sampleProbability, + double x, double y, float x_scale, float y_scale, const osg::Vec2d t_0, + osg::Vec2d t_x, osg::Vec2d t_y) { + if (objectMaskImage != NULL) { + osg::Vec2 t = osg::Vec2(t_0 + t_x * x + t_y * y); + unsigned int x = + (unsigned int)(objectMaskImage->s() * t.x() * x_scale) % + objectMaskImage->s(); + unsigned int y = + (unsigned int)(objectMaskImage->t() * t.y() * y_scale) % + objectMaskImage->t(); + + float color_value = objectMaskImage->getColor(x, y)[channel]; + return (sampleProbability > color_value); + } + return false; +} + +bool VPBMaterialHandler::checkAgainstConstraints( + osg::ref_ptr constraintGroup, osg::Vec3d origin, + osg::Vec3d vertex) { + osg::ref_ptr intersector; + intersector = new osgUtil::LineSegmentIntersector(origin, vertex); + osgUtil::IntersectionVisitor visitor(intersector.get()); + constraintGroup->accept(visitor); + return intersector->containsIntersections(); +} + +double VPBMaterialHandler::det2(const osg::Vec2d a, const osg::Vec2d b) { + return a.x() * b.y() - b.x() * a.y(); +} + +/** VegetationHandler implementation + * The code has been ported as-is from the following source: + * Simgear commit 6d71ab75: + * simgear/scene/tgdb/VPBTechnique.cxx : applyTrees() + */ +bool VegetationHandler::initialize(osg::ref_ptr options, + osg::ref_ptr terrainTile) { + bool use_random_vegetation = false; + int vegetation_lod_level = 6; + vegetation_density = 1.0; + + // Determine tree spacing, assuming base density of 1 tree per 100m^2, + // though spacing is linear here, as is the + // /sim/rendering/vegetation-density property. + + 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_level = propertyNode->getIntValue( + "/sim/rendering/static-lod/vegetation-lod-level", + vegetation_lod_level); + } + + // 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_level)) { + return false; + } + + bin = NULL; + wood_coverage = 0.0; + + return true; +} + +void VegetationHandler::setLocation(const SGGeod loc, double r_E_lat, + double r_E_lon) { + delta_lat = sqrt(1000.0 / vegetation_density) / r_E_lat; + delta_lon = sqrt(1000.0 / vegetation_density) / + (r_E_lon * cos(loc.getLatitudeRad())); +} + +bool VegetationHandler::handleNewMaterial(SGMaterial *mat) { + if (mat->get_wood_coverage() <= 0) + return false; + + wood_coverage = 2000.0 / mat->get_wood_coverage(); + + bool found = false; + + for (SGTreeBinList::iterator iter = randomForest.begin(); + iter != randomForest.end(); iter++) { + + bin = *iter; + + if ((bin->texture == mat->get_tree_texture() ) && + (bin->teffect == mat->get_tree_effect() ) && + (bin->texture_varieties == mat->get_tree_varieties()) && + (bin->range == mat->get_tree_range() ) && + (bin->width == mat->get_tree_width() ) && + (bin->height == mat->get_tree_height() ) ) { + found = true; + break; + } + } + + if (!found) { + bin = new TreeBin(); + bin->texture = mat->get_tree_texture(); + SG_LOG(SG_TERRAIN, SG_DEBUG, "Tree texture " << bin->texture); + bin->teffect = mat->get_tree_effect(); + SG_LOG(SG_TERRAIN, SG_DEBUG, "Tree effect " << bin->teffect); + 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(); + randomForest.push_back(bin); + } + + return true; +} + +bool VegetationHandler::handleIteration( + SGMaterial *mat, osg::Image *objectMaskImage, + osg::ref_ptr constraintGroup, const double lon, + const double lat, osg::Vec2d p, const double D, const osg::Vec2d ll_O, + const osg::Vec2d ll_x, const osg::Vec2d ll_y, const osg::Vec2d t_0, + osg::Vec2d t_x, osg::Vec2d t_y, const osg::Vec3d v_0, osg::Vec3d v_x, + osg::Vec3d v_y, float x_scale, float y_scale, osg::Vec3 n, osg::Vec3d up) { + const int lat_int = (lat + ll_O.y()) / delta_lat; + const int lon_int = (lon + ll_O.x()) / delta_lon; + + if (mat->get_wood_coverage() <= 0) + return false; + if (pc_map_rand(lon_int, lat_int, 2) > wood_coverage) + return false; + + if (mat->get_is_plantation()) { + p = osg::Vec2d(lon + 0.1 * delta_lon * pc_map_norm(lon_int, lat_int, 0), + lat + 0.1 * delta_lat * pc_map_norm(lon_int, lat_int, 1)); + } else { + p = osg::Vec2d(lon + delta_lon * pc_map_rand(lon_int, lat_int, 0), + lat + delta_lat * pc_map_rand(lon_int, lat_int, 1)); + } + + double x = det2(ll_x, p) / D; + double y = det2(p, ll_y) / D; + + // Check for invalid triangle coordinates. + if ((x < 0.0) || (y < 0.0) || (x + y > 1.0)) return false; + + // Check against any object mask using green (for trees) channel + if (checkAgainstObjectMask(objectMaskImage, Green, + pc_map_rand(lon_int, lat_int, 3), x, y, x_scale, + y_scale, t_0, t_x, t_y)) { + return false; + } + + // Check against constraints to stop trees growing from roads; + const osg::Vec3 vp = v_x * x + v_y * y + v_0; + if (checkAgainstConstraints(constraintGroup, vp - up * 100, vp + up * 100)) + return false; + + bin->insert(SGVec3f(vp.x(), vp.y(), vp.z()), SGVec3f(n.x(), n.y(), n.z())); + + return true; +} + +void VegetationHandler::finish(osg::ref_ptr options, + osg::ref_ptr transform, + const SGGeod loc) { + if (randomForest.size() > 0) { + SG_LOG(SG_TERRAIN, SG_DEBUG, + "Adding Random Forest " << randomForest.size()); + for (auto iter = randomForest.begin(); iter != randomForest.end(); + iter++) { + TreeBin *treeBin = *iter; + SG_LOG(SG_TERRAIN, SG_DEBUG, + " " << treeBin->texture << " " << treeBin->getNumTrees()); + } + + 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)); + + osg::Group *trees = createForest(randomForest, R_vert, options, 1); + trees->setNodeMask(SG_NODEMASK_TERRAIN_BIT); + transform->addChild(trees); + } +} + +/** RandomLightsHandler implementation */ +bool RandomLightsHandler::initialize( + osg::ref_ptr options, + osg::ref_ptr terrainTile) { + SGPropertyNode *propertyNode = options->getPropertyNode().get(); + + int lightLODLevel = 6; + bool useRandomLighting = true; + + if (propertyNode) { + // Turn on random lighting if OSM buildings are turned off + useRandomLighting = !propertyNode->getBoolValue( + "/sim/rendering/osm-buildings", useRandomLighting); + lightLODLevel = propertyNode->getIntValue( + "/sim/rendering/static-lod/light-lod-level", lightLODLevel); + } + + // Do not generate lights for tiles too far away + if ((!useRandomLighting) || + (terrainTile->getTileID().level < lightLODLevel)) { + return false; + } + + lightCoverage = 0.0; + + return true; +} + +void RandomLightsHandler::setLocation(const SGGeod loc, double r_E_lat, + double r_E_lon) { + // Approximately 1m x 1m + // latitudeDelta [degrees] = 360 [degrees] / (2 * PI * polarRadius) + delta_lat = 180.0 / (M_PI * r_E_lat); + + // longitudeDelta [degrees] = 360 [degrees] / (2 * PI * equitorialRadius * + // cos(latitude [radians])) + delta_lon = 180.0 / (M_PI * r_E_lon * cos(loc.getLatitudeRad())); +} + +bool RandomLightsHandler::handleNewMaterial(SGMaterial *mat) { + if (mat->get_light_coverage() <= 0) + return false; + + if (bin == NULL) { + bin = new LightBin(); + } + + lightCoverage = mat->get_light_coverage(); + + return true; +} + +bool RandomLightsHandler::handleIteration( + SGMaterial *mat, osg::Image *objectMaskImage, + osg::ref_ptr constraintGroup, const double lon, + const double lat, osg::Vec2d p, const double D, const osg::Vec2d ll_O, + const osg::Vec2d ll_x, const osg::Vec2d ll_y, const osg::Vec2d t_0, + osg::Vec2d t_x, osg::Vec2d t_y, const osg::Vec3d v_0, osg::Vec3d v_x, + osg::Vec3d v_y, float x_scale, float y_scale, osg::Vec3 n, osg::Vec3d up) { + const int lat_int = (lat + ll_O.y()) / delta_lat; + const int lon_int = (lon + ll_O.x()) / delta_lon; + + if (mat->get_light_coverage() <= 0) + return false; + + // Since we are scanning 1mx1m chunks, 1/lightCoverage gives the probability + // of a particular 1x1 chunk having a light + // e.g. if lightCoverage = 10m^2 (i.e. every light point must cover around + // 10m^2), + // this roughly equates to sqrt(10) * sqrt(10) chunks, and hence we need + // to have a light in only one in 10 chunks (sqrt(10) * sqrt(10)), giving + // us a probability of 1/10. + if (pc_map_rand(lon_int, lat_int, 4) > (1.0 / lightCoverage)) + return false; + + p = osg::Vec2d(lon + delta_lon * pc_map_rand(lon_int, lat_int, 0), + lat + delta_lat * pc_map_rand(lon_int, lat_int, 1)); + + double x = det2(ll_x, p) / D; + double y = det2(p, ll_y) / D; + + // Check for invalid triangle coordinates. + if ((x < 0.0) || (y < 0.0) || (x + y > 1.0)) + return false; + + // Check against any object mask using blue (for lights) channel + if (checkAgainstObjectMask(objectMaskImage, Blue, + pc_map_rand(lon_int, lat_int, 5), x, y, x_scale, + y_scale, t_0, t_x, t_y)) { + return false; + } + + // Check against constraints to stop lights on roads + const osg::Vec3 vp = v_x * x + v_y * y + v_0; + if (checkAgainstConstraints(constraintGroup, vp - up * 100, vp + up * 100)) + return false; + + float zombie = pc_map_rand(lon_int, lat_int, 6); + float factor = pc_map_rand(lon_int, lat_int, 7); + factor *= factor; + + float bright = 1; + SGVec4f color; + if (zombie > 0.5) { + // 50% chance of yellowish + color = SGVec4f(0.9f, 0.9f, 0.3f, bright - factor * 0.2f); + } else if (zombie > 0.15f) { + // 35% chance of whitish + color = SGVec4f(0.9, 0.9f, 0.8f, bright - factor * 0.2f); + } else if (zombie > 0.05f) { + // 10% chance of orangish + color = SGVec4f(0.9f, 0.6f, 0.2f, bright - factor * 0.2f); + } else { + // 5% chance of redish + color = SGVec4f(0.9f, 0.2f, 0.2f, bright - factor * 0.2f); + } + + // Potential enhancment: Randomize light type (directional vs + // omnidirectional, size, intensity) Sizes and Intensity tuning source: + // https://www.scgrp.com/StresscreteGroup/media/images/products/K118-Washington-LED-Spec-Sheet.pdf + // https://www.nationalcityca.gov/home/showpublisheddocument?id=19680 + double size = 30; + double intensity = 500; + double onPeriod = 2; // Turn on randomly around sunset + + // Place lights at 3m above ground + const osg::Vec3 finalPosition = vp + up * 3; + + bin->insert( + SGVec3f(finalPosition.x(), finalPosition.y(), finalPosition.z()), + size, intensity, onPeriod, color); + + return true; +} + +void RandomLightsHandler::finish(osg::ref_ptr options, + osg::ref_ptr transform, + const SGGeod loc) { + if (bin != NULL && bin->getNumLights() > 0) { + SG_LOG(SG_TERRAIN, SG_DEBUG, + "Adding Random Lights " << bin->getNumLights()); + + 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)); + + transform->addChild( + createLights(*bin, osg::Matrix::identity(), options)); + } +} +}; \ No newline at end of file diff --git a/simgear/scene/tgdb/VPBMaterialHandler.hxx b/simgear/scene/tgdb/VPBMaterialHandler.hxx new file mode 100644 index 00000000..689c8037 --- /dev/null +++ b/simgear/scene/tgdb/VPBMaterialHandler.hxx @@ -0,0 +1,173 @@ +/* -*-c++-*- + * VPBMaterialHandler.hxx -- WS30 material-based generation handlers + * + * Copyright (C) 2021 Fahim Dalvi + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + */ + +#ifndef VPBMATERIALHANDLER +#define VPBMATERIALHANDLER 1 + +#include "LightBin.hxx" +#include "TreeBin.hxx" + +using namespace osgTerrain; + +namespace simgear { + +/** Abstract class to represent all material handler classes + * + * Extending and implementing the virtual functions in VPBMaterialHandler + * enables performing arbitrary actions based on landclass materials in + * an efficient manner. One example is generating things based on the + * landclass, such as trees, lights or buildings. + */ +class VPBMaterialHandler { + protected: + // Deltas to define granularity of scanline + double delta_lat; + double delta_lon; + + VPBMaterialHandler() : delta_lat(0.0), delta_lon(0.0) {} + ~VPBMaterialHandler() {} + + enum ImageChannel { Red, Green, Blue, Alpha }; + + // Check against object mask, if any. Returns true if the given point + // should be masked, false otherwise. + bool checkAgainstObjectMask(osg::Image *objectMaskImage, + ImageChannel channel, double sampleProbability, + double x, double y, float x_scale, + float y_scale, const osg::Vec2d t_0, + osg::Vec2d t_x, osg::Vec2d t_y); + + // Check a given vertex against any constraints E.g. to ensure we + // don't get objects like trees sprouting from roads or runways. + bool checkAgainstConstraints(osg::ref_ptr constraintGroup, + osg::Vec3d origin, osg::Vec3d vertex); + + double det2(const osg::Vec2d a, const osg::Vec2d b); + + public: + // Initialize internal state and return true if the handler should be called + // for the current tile. + virtual bool initialize(osg::ref_ptr options, + osg::ref_ptr terrainTile) = 0; + + // Set the internal state (e.g. deltas) based on the current tile's location + virtual void setLocation(const SGGeod loc, double r_E_lat, + double r_E_lon) = 0; + + // Function that is called when a new material/landclass is detected during + // the scanline reading process. Return false if the new material is + // irrelevant to the handler + virtual bool handleNewMaterial(SGMaterial *mat) = 0; + + // Function that is called for each point in the scanline reading process + // Return false if the material is irrelevant to the handler + virtual bool handleIteration(SGMaterial *mat, osg::Image *objectMaskImage, + osg::ref_ptr constraintGroup, + const double lon, const double lat, + osg::Vec2d p, const double D, + const osg::Vec2d ll_O, const osg::Vec2d ll_x, + const osg::Vec2d ll_y, const osg::Vec2d t_0, + osg::Vec2d t_x, osg::Vec2d t_y, + const osg::Vec3d v_0, osg::Vec3d v_x, + osg::Vec3d v_y, float x_scale, float y_scale, + osg::Vec3 n, osg::Vec3d up) = 0; + + // Function that is called after the scanline is complete + virtual void finish(osg::ref_ptr options, + osg::ref_ptr transform, + const SGGeod loc) = 0; + + double get_delta_lat() { return delta_lat; }; + double get_delta_lon() { return delta_lon; }; +}; + +/** Vegetation handler + * + * This handler takes care of generating vegetation and trees. The code has + * been ported as-is from the following source: + * Simgear commit 6d71ab75: + * simgear/scene/tgdb/VPBTechnique.cxx : applyTrees() + */ +class VegetationHandler : public VPBMaterialHandler { + public: + VegetationHandler() {} + ~VegetationHandler() {} + + bool initialize(osg::ref_ptr options, + osg::ref_ptr terrainTile); + void setLocation(const SGGeod loc, double r_E_lat, double r_E_lon); + bool handleNewMaterial(SGMaterial *mat); + bool handleIteration(SGMaterial *mat, osg::Image *objectMaskImage, + osg::ref_ptr constraintGroup, + const double lon, const double lat, osg::Vec2d p, + const double D, const osg::Vec2d ll_O, + const osg::Vec2d ll_x, const osg::Vec2d ll_y, + const osg::Vec2d t_0, osg::Vec2d t_x, osg::Vec2d t_y, + const osg::Vec3d v_0, osg::Vec3d v_x, osg::Vec3d v_y, + float x_scale, float y_scale, osg::Vec3 n, + osg::Vec3d up); + void finish(osg::ref_ptr options, + osg::ref_ptr transform, const SGGeod loc); + + private: + float vegetation_density; + SGTreeBinList randomForest; + + TreeBin *bin = NULL; + float wood_coverage = 0.0; +}; + +/** Random Lighting handler + * + * This handler takes care of generating random tile lighting, taking into + * account light-coverage of various materials contained in the tile. Lights + * are currently generated if OSM Buildings are turned off. + */ +class RandomLightsHandler : public VPBMaterialHandler { + public: + RandomLightsHandler() {} + ~RandomLightsHandler() {} + + bool initialize(osg::ref_ptr options, + osg::ref_ptr terrainTile); + void setLocation(const SGGeod loc, double r_E_lat, double r_E_lon); + bool handleNewMaterial(SGMaterial *mat); + bool handleIteration(SGMaterial *mat, osg::Image *objectMaskImage, + osg::ref_ptr constraintGroup, + const double lon, const double lat, osg::Vec2d p, + const double D, const osg::Vec2d ll_O, + const osg::Vec2d ll_x, const osg::Vec2d ll_y, + const osg::Vec2d t_0, osg::Vec2d t_x, osg::Vec2d t_y, + const osg::Vec3d v_0, osg::Vec3d v_x, osg::Vec3d v_y, + float x_scale, float y_scale, osg::Vec3 n, + osg::Vec3d up); + void finish(osg::ref_ptr options, + osg::ref_ptr transform, const SGGeod loc); + + private: + LightBin *bin = NULL; + float lightCoverage = 0.0; +}; + +}; // namespace simgear + +#endif \ No newline at end of file diff --git a/simgear/scene/tgdb/VPBTechnique.cxx b/simgear/scene/tgdb/VPBTechnique.cxx index de34b43b..ab4ce85a 100644 --- a/simgear/scene/tgdb/VPBTechnique.cxx +++ b/simgear/scene/tgdb/VPBTechnique.cxx @@ -17,7 +17,7 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include - +#include #include #include @@ -51,7 +51,7 @@ #include #include "VPBTechnique.hxx" -#include "TreeBin.hxx" +#include "VPBMaterialHandler.hxx" using namespace osgTerrain; using namespace simgear; @@ -61,7 +61,7 @@ VPBTechnique::VPBTechnique() setFilterBias(0); setFilterWidth(0.1); setFilterMatrixAs(GAUSSIAN); - _vegetationConstraintGroup = new osg::Group(); + _randomObjectsConstraintGroup = new osg::Group(); } VPBTechnique::VPBTechnique(const SGReaderWriterOptions* options) @@ -70,7 +70,7 @@ VPBTechnique::VPBTechnique(const SGReaderWriterOptions* options) setFilterWidth(0.1); setFilterMatrixAs(GAUSSIAN); setOptions(options); - _vegetationConstraintGroup = new osg::Group(); + _randomObjectsConstraintGroup = new osg::Group(); } VPBTechnique::VPBTechnique(const VPBTechnique& gt,const osg::CopyOp& copyop): @@ -80,7 +80,7 @@ VPBTechnique::VPBTechnique(const VPBTechnique& gt,const osg::CopyOp& copyop): setFilterWidth(gt._filterWidth); setFilterMatrix(gt._filterMatrix); setOptions(gt._options); - _vegetationConstraintGroup = new osg::Group(); + _randomObjectsConstraintGroup = new osg::Group(); } VPBTechnique::~VPBTechnique() @@ -172,7 +172,7 @@ void VPBTechnique::init(int dirtyMask, bool assumeMultiThreaded) applyLineFeatures(*buffer, masterLocator); applyAreaFeatures(*buffer, masterLocator); applyCoastline(*buffer, masterLocator); - applyTrees(*buffer, masterLocator); + applyMaterials(*buffer, masterLocator); } } else @@ -183,7 +183,7 @@ void VPBTechnique::init(int dirtyMask, bool assumeMultiThreaded) applyLineFeatures(*buffer, masterLocator); applyAreaFeatures(*buffer, masterLocator); applyCoastline(*buffer, masterLocator); - applyTrees(*buffer, masterLocator); + applyMaterials(*buffer, masterLocator); } if (buffer->_transform.valid()) buffer->_transform->setThreadSafeRefUnref(true); @@ -1332,30 +1332,29 @@ double VPBTechnique::det2(const osg::Vec2d a, const osg::Vec2d b) return a.x() * b.y() - b.x() * a.y(); } -void VPBTechnique::applyTrees(BufferData& buffer, Locator* masterLocator) +void VPBTechnique::applyMaterials(BufferData& buffer, Locator* masterLocator) { pc_init(2718281); - bool use_random_vegetation = false; - float vegetation_density = 1.0; - int vegetation_lod_level = 6; + // Define all possible handlers + VegetationHandler vegetationHandler; + RandomLightsHandler lightsHandler; + std::vector all_handlers{&vegetationHandler, + &lightsHandler}; - 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_level = propertyNode->getIntValue("/sim/rendering/static-lod/vegetation-lod-level", vegetation_lod_level); + // Filter out handlers that do not apply to the current tile + std::vector handlers; + for (const auto handler : all_handlers) { + if (handler->initialize(_options, _terrainTile)) { + handlers.push_back(handler); + } } - // 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_level)) { + // If no handlers are relevant to the current tile, return immediately + if (handlers.size() == 0) { return; } - // Determine tree spacing, assuming base density of 1 tree per 100m^2, though spacing - // is linear here, as is the /sim/rendering/vegetation-density property. - SGMaterialLibPtr matlib = _options->getMaterialLib(); SGMaterial* mat = 0; @@ -1387,8 +1386,6 @@ void VPBTechnique::applyTrees(BufferData& buffer, Locator* masterLocator) return; } - SGTreeBinList randomForest; - const osg::Vec3* vertexPtr = static_cast(vertices->getDataPointer()); const osg::Vec2* texPtr = static_cast(texture_coords->getDataPointer()); @@ -1405,8 +1402,6 @@ void VPBTechnique::applyTrees(BufferData& buffer, Locator* masterLocator) 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), @@ -1418,18 +1413,24 @@ void VPBTechnique::applyTrees(BufferData& buffer, Locator* masterLocator) lon, osg::Vec3d(0.0, 0.0, 1.0), 0.0, osg::Vec3d(1.0, 0.0, 0.0)); - // At the detailed tile level we are generating vegetation, and + // Compute lat/lon deltas for each handler + std::vector> deltas; + for (const auto handler : handlers) { + handler->setLocation(loc, r_E_lat, r_E_lon); + deltas.push_back( + std::make_pair(handler->get_delta_lat(), handler->get_delta_lon())); + } + + // At the detailed tile level we are handling various materials, and // as we walk across the tile in a scanline, the landclass doesn't // change regularly from point to point. Cache the required // material information for the current landclass to reduce the // number of lookups into the material cache. int current_land_class = -1; osg::Texture2D* object_mask = NULL; - osg::Image* img = NULL; + osg::Image* object_mask_image = NULL; float x_scale = 1000.0; float y_scale = 1000.0; - TreeBin* bin = NULL; - float wood_coverage = 0.0; for (unsigned int i = 0; i < triangle_count; i++) { @@ -1460,12 +1461,40 @@ void VPBTechnique::applyTrees(BufferData& buffer, Locator* masterLocator) 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; + // Each handler may have a different delta/granularity in the scanline. + // To take advantage of the material caching, we first collect all the + // scan points from all the handlers for the current tile, and then scan + // them in spatial order, calling the appropriate handler for each + // point. + // + // We will insert elements in a vector, and sort + // all elements in increasing lon followed by increase lat, mimicking a + // scanline reading approach for efficient landclass caching + std::vector> + scan_points; + + for(auto iter=0; iter!=handlers.size(); iter++) { + const double delta_lat = deltas[iter].first; + const double delta_lon = deltas[iter].second; + 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; + + 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; + scan_points.push_back( + std::make_tuple(lon, lat, handlers[iter])); + } + } + } + + std::sort(scan_points.begin(), scan_points.end()); const osg::Vec2 t0 = texPtr[i0]; const osg::Vec2 t1 = texPtr[i1]; @@ -1477,142 +1506,85 @@ void VPBTechnique::applyTrees(BufferData& buffer, Locator* masterLocator) 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(auto const &point : scan_points) { + const double lon = get<0>(point); + const double lat = get<1>(point); + VPBMaterialHandler *handler = get<2>(point); - for (int lon_int = min_lon - 1; lon_int <= max_lon + 1; lon_int++) - { - const double lon = (lon_int - off_x) * delta_lon; - osg::Vec2d p(lon, lat); - double x = det2(ll_x, p) / D; - double y = det2(p, ll_y) / D; + osg::Vec2d p(lon, lat); + double x = det2(ll_x, p) / D; + double y = det2(p, ll_y) / D; - if ((x < 0.0) || (y < 0.0) || (x + y > 1.0)) continue; + if ((x < 0.0) || (y < 0.0) || (x + y > 1.0)) continue; - if (!image) { - SG_LOG(SG_TERRAIN, SG_ALERT, "Image disappeared under my feet."); + if (!image) { + SG_LOG(SG_TERRAIN, SG_ALERT, "Image disappeared under my feet."); + continue; + } + + osg::Vec2 t = osg::Vec2(t_0 + t_x * x + t_y * y); + unsigned int tx = (unsigned int) (image->s() * t.x()) % image->s(); + unsigned int ty = (unsigned int) (image->t() * t.y()) % image->t(); + const osg::Vec4 tc = image->getColor(tx, ty); + const int land_class = int(round(tc.x() * 255.0)); + + if (land_class != current_land_class) { + // Use temporal locality to reduce material lookup by caching + // some elements for future lookups against the same landclass. + mat = matcache->find(land_class); + if (!mat) continue; + + current_land_class = land_class; + + // We need to notify all handlers of material change, but + // only consider the current handler being processed for + // skipping the loop + bool current_handler_result = true; + for (const auto temp_handler : handlers) { + bool result = temp_handler->handleNewMaterial(mat); + + if (temp_handler == handler) { + current_handler_result = result; + } + } + + if (!current_handler_result) { continue; } - osg::Vec2 t = osg::Vec2(t_0 + t_x * x + t_y * y); - unsigned int tx = (unsigned int) (image->s() * t.x()) % image->s(); - unsigned int ty = (unsigned int) (image->t() * t.y()) % image->t(); - const osg::Vec4 tc = image->getColor(tx, ty); - const int land_class = int(round(tc.x() * 255.0)); - - if (land_class != current_land_class) { - // Use temporal locality to reduce material lookup by caching - // some elements for future lookups against the same landclass. - mat = matcache->find(land_class); - if (!mat) continue; - - current_land_class = land_class; - - if (mat->get_wood_coverage() <= 0) continue; - - wood_coverage = 2000.0 / mat->get_wood_coverage(); - object_mask = mat->get_one_object_mask(0); - if (object_mask != NULL) { - img = object_mask->getImage(); - if (!img || ! img->valid()) continue; - - // Texture coordinates run [0..1][0..1] across the entire tile whereas - // the texure itself has defined dimensions in m. - // We therefore need to use the tile width and height to determine the correct - // texture coordinate transformation. - x_scale = buffer._width / 1000.0; - y_scale = buffer._height / 1000.0; - - if (mat->get_xsize() > 0.0) { x_scale = buffer._width / mat->get_xsize(); } - if (mat->get_ysize() > 0.0) { y_scale = buffer._height / mat->get_ysize(); } - } - - bool found = false; - - for (SGTreeBinList::iterator iter = randomForest.begin(); iter != randomForest.end(); iter++) { - - bin = *iter; - - if ((bin->texture == mat->get_tree_texture() ) && - (bin->teffect == mat->get_tree_effect() ) && - (bin->texture_varieties == mat->get_tree_varieties()) && - (bin->range == mat->get_tree_range() ) && - (bin->width == mat->get_tree_width() ) && - (bin->height == mat->get_tree_height() ) ) { - found = true; - break; - } - } - - if (!found) { - bin = new TreeBin(); - bin->texture = mat->get_tree_texture(); - SG_LOG(SG_TERRAIN, SG_DEBUG, "Tree texture " << bin->texture); - bin->teffect = mat->get_tree_effect(); - SG_LOG(SG_TERRAIN, SG_DEBUG, "Tree effect " << bin->teffect); - 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(); - randomForest.push_back(bin); - } - } - - if (!mat) continue; - if (mat->get_wood_coverage() <= 0) continue; - if (pc_map_rand(lon_int, lat_int, 2) > wood_coverage) continue; - - if (mat->get_is_plantation()) { - p = osg::Vec2d(lon + 0.1 * delta_lon * pc_map_norm(lon_int, lat_int, 0), - lat + 0.1 * delta_lat * pc_map_norm(lon_int, lat_int, 1)); - } else { - p = osg::Vec2d(lon + delta_lon * pc_map_rand(lon_int, lat_int, 0), - lat + delta_lat * pc_map_rand(lon_int, lat_int, 1)); - } - - x = det2(ll_x, p) / D; - y = det2(p, ll_y) / D; - - // Check for invalid triangle coordinates. - if ((x < 0.0) || (y < 0.0) || (x + y > 1.0)) continue; - - // Check against any object mask + object_mask = mat->get_one_object_mask(0); + object_mask_image = NULL; if (object_mask != NULL) { - t = osg::Vec2(t_0 + t_x * x + t_y * y); - unsigned int x = (unsigned int) (img->s() * t.x() * x_scale) % img->s(); - unsigned int y = (unsigned int) (img->t() * t.y() * y_scale) % img->t(); + object_mask_image = object_mask->getImage(); + if (!object_mask_image || ! object_mask_image->valid()) { + object_mask_image = NULL; + continue; + } - // green (for trees) channel - if (pc_map_rand(lon_int, lat_int, 3) > img->getColor(x, y).g()) continue; + // Texture coordinates run [0..1][0..1] across the entire tile whereas + // the texure itself has defined dimensions in m. + // We therefore need to use the tile width and height to determine the correct + // texture coordinate transformation. + x_scale = buffer._width / 1000.0; + y_scale = buffer._height / 1000.0; + + if (mat->get_xsize() > 0.0) { x_scale = buffer._width / mat->get_xsize(); } + if (mat->get_ysize() > 0.0) { y_scale = buffer._height / mat->get_ysize(); } } - - // Check against constraints to stop trees growing from roads; - const osg::Vec3 vp = v_x * x + v_y * y + v_0; - if (checkAgainstVegetationConstraints(vp - up*100, vp + up*100)) continue; - - bin->insert(SGVec3f(vp.x(), vp.y(), vp.z()), SGVec3f(n.x(), n.y(), n.z())); } + + if (!mat) continue; + + handler->handleIteration(mat, object_mask_image, + _randomObjectsConstraintGroup, lon, lat, p, + D, ll_O, ll_x, ll_y, t_0, t_x, t_y, v_0, + v_x, v_y, x_scale, y_scale, n, up); } } - if (randomForest.size() > 0) { - SG_LOG(SG_TERRAIN, SG_DEBUG, "Adding Random Forest " << randomForest.size()); - for (auto iter = randomForest.begin(); iter != randomForest.end(); iter++) { - TreeBin* treeBin = *iter; - SG_LOG(SG_TERRAIN, SG_DEBUG, " " << treeBin->texture << " " << treeBin->getNumTrees()); - } - - 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)); - - osg::Group* trees = createForest(randomForest, R_vert, _options, 1); - trees->setNodeMask(SG_NODEMASK_TERRAIN_BIT); - buffer._transform->addChild(trees); + for (const auto handler : handlers) { + handler->finish(_options, buffer._transform, loc); } - } void VPBTechnique::applyLineFeatures(BufferData& buffer, Locator* masterLocator) @@ -1716,7 +1688,7 @@ void VPBTechnique::applyLineFeatures(BufferData& buffer, Locator* masterLocator) geode->setEffect(mat->get_one_effect(0)); geode->setNodeMask(SG_NODEMASK_TERRAIN_BIT); buffer._transform->addChild(geode); - addVegetationConstraint(geode); + addRandomObjectsConstraint(geode); if (lights->size() > 0) { const double size = mat->get_light_edge_size_cm(); @@ -2344,33 +2316,24 @@ osg::Vec3d VPBTechnique::checkAgainstElevationConstraints(osg::Vec3d origin, osg } } -// Add an osg object representing a vegetation contraint on the terrain mesh. The generated terrain mesh will not include any vegetation -// intersection with the constraint model. -void VPBTechnique::addVegetationConstraint(osg::ref_ptr constraint) +// Add an osg object representing a contraint on the terrain mesh. The generated +// terrain mesh will not include any random objects intersecting with the +// constraint model. +void VPBTechnique::addRandomObjectsConstraint(osg::ref_ptr constraint) { - _vegetationConstraintGroup->addChild(constraint.get()); + _randomObjectsConstraintGroup->addChild(constraint.get()); } // Remove a previously added constraint. E.g on model unload. -void VPBTechnique::removeVegetationConstraint(osg::ref_ptr constraint) +void VPBTechnique::removeRandomObjectsConstraint(osg::ref_ptr constraint) { - _vegetationConstraintGroup->removeChild(constraint.get()); + _randomObjectsConstraintGroup->removeChild(constraint.get()); } // Remove all the constraints, which will still be referenced by the terrain tile itself. -void VPBTechnique::clearVegetationConstraints() +void VPBTechnique::clearRandomObjectsConstraints() { - _vegetationConstraintGroup->removeChildren(0, _vegetationConstraintGroup->getNumChildren()); -} - -// Check a given vertex against any vegetation constraints E.g. to ensure we don't get trees sprouting from roads or runways. -bool VPBTechnique::checkAgainstVegetationConstraints(osg::Vec3d origin, osg::Vec3d vertex) -{ - osg::ref_ptr intersector; - intersector = new osgUtil::LineSegmentIntersector(origin, vertex); - osgUtil::IntersectionVisitor visitor(intersector.get()); - _vegetationConstraintGroup->accept(visitor); - return intersector->containsIntersections(); + _randomObjectsConstraintGroup->removeChildren(0, _randomObjectsConstraintGroup->getNumChildren()); } void VPBTechnique::clearConstraints() diff --git a/simgear/scene/tgdb/VPBTechnique.hxx b/simgear/scene/tgdb/VPBTechnique.hxx index e00cb234..18f14f2a 100644 --- a/simgear/scene/tgdb/VPBTechnique.hxx +++ b/simgear/scene/tgdb/VPBTechnique.hxx @@ -137,7 +137,7 @@ class VPBTechnique : public TerrainTechnique virtual double det2(const osg::Vec2d a, const osg::Vec2d b); - virtual void applyTrees(BufferData& buffer, Locator* masterLocator); + virtual void applyMaterials(BufferData& buffer, Locator* masterLocator); virtual void applyLineFeatures(BufferData& buffer, Locator* masterLocator); virtual void generateLineFeature(BufferData& buffer, @@ -181,12 +181,12 @@ class VPBTechnique : public TerrainTechnique virtual osg::Vec3d getMeshIntersection(BufferData& buffer, Locator* masterLocator, osg::Vec3d pt, osg::Vec3d up); - // Vegetation constraints ensure that trees don't grow in roads. Unlike the elevation constraints, + // Random Objects constraints ensure that random objects like trees, + // lights, buildings etc do not appear on roads. Unlike the elevation constraints, // we only use these internally and during generation of this particular tile. - virtual void addVegetationConstraint(osg::ref_ptr constraint); - virtual void removeVegetationConstraint(osg::ref_ptr constraint); - virtual bool checkAgainstVegetationConstraints(osg::Vec3d origin, osg::Vec3d vertex); - virtual void clearVegetationConstraints(); + virtual void addRandomObjectsConstraint(osg::ref_ptr constraint); + virtual void removeRandomObjectsConstraint(osg::ref_ptr constraint); + virtual void clearRandomObjectsConstraints(); OpenThreads::Mutex _writeBufferMutex; osg::ref_ptr _currentBufferData; @@ -199,7 +199,7 @@ class VPBTechnique : public TerrainTechnique osg::Matrix3 _filterMatrix; osg::ref_ptr _filterMatrixUniform; osg::ref_ptr _options; - osg::ref_ptr _vegetationConstraintGroup; + osg::ref_ptr _randomObjectsConstraintGroup; inline static osg::ref_ptr _elevationConstraintGroup = new osg::Group(); inline static std::mutex _elevationConstraintMutex; // protects the _elevationConstraintGroup;