WS30: Generic Materials handler and Random tile lights

This commit implements a generic materials handler providing an easy way to
extend the efficient landclass scanning code for new material-specific
processing like trees, random lights, random buildings etc. The existing
vegetation code has been ported to make use of this generic interface,
and random tile lights have been added as well. Detailed commit log can
be found at [1], discussions can be found at [2] and [3].

[1] https://sourceforge.net/u/fahimdalvi/simgear/ci/feat/ws30_random_lights/~/tree/
[2] https://sourceforge.net/p/flightgear/mailman/flightgear-devel/thread/AC818EC5-84A3-425B-98CB-352CCB0B8910%40gmail.com/#msg37356997
[3] https://sourceforge.net/p/flightgear/simgear/merge-requests/99/
This commit is contained in:
Fahim Imaduddin Dalvi 2021-10-21 00:34:32 +03:00
parent 6d71ab75b2
commit 6e7fa6b139
5 changed files with 705 additions and 184 deletions

View File

@ -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

View File

@ -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 <osg/MatrixTransform>
#include <osgTerrain/TerrainTile>
#include <osgUtil/IntersectionVisitor>
#include <osgUtil/LineSegmentIntersector>
#include <simgear/math/sg_random.hxx>
#include <simgear/scene/material/Effect.hxx>
#include <simgear/scene/material/EffectGeode.hxx>
#include <simgear/scene/util/SGNodeMasks.hxx>
#include <simgear/scene/util/SGReaderWriterOptions.hxx>
#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<osg::Group> constraintGroup, osg::Vec3d origin,
osg::Vec3d vertex) {
osg::ref_ptr<osgUtil::LineSegmentIntersector> 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<SGReaderWriterOptions> options,
osg::ref_ptr<TerrainTile> 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<osg::Group> 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<SGReaderWriterOptions> options,
osg::ref_ptr<osg::MatrixTransform> 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<SGReaderWriterOptions> options,
osg::ref_ptr<TerrainTile> 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<osg::Group> 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<SGReaderWriterOptions> options,
osg::ref_ptr<osg::MatrixTransform> 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));
}
}
};

View File

@ -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<osg::Group> 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<SGReaderWriterOptions> options,
osg::ref_ptr<TerrainTile> 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<osg::Group> 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<SGReaderWriterOptions> options,
osg::ref_ptr<osg::MatrixTransform> 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<SGReaderWriterOptions> options,
osg::ref_ptr<TerrainTile> 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<osg::Group> 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<SGReaderWriterOptions> options,
osg::ref_ptr<osg::MatrixTransform> 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<SGReaderWriterOptions> options,
osg::ref_ptr<TerrainTile> 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<osg::Group> 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<SGReaderWriterOptions> options,
osg::ref_ptr<osg::MatrixTransform> transform, const SGGeod loc);
private:
LightBin *bin = NULL;
float lightCoverage = 0.0;
};
}; // namespace simgear
#endif

View File

@ -17,7 +17,7 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include <cmath>
#include <tuple>
#include <osgTerrain/TerrainTile>
#include <osgTerrain/Terrain>
@ -51,7 +51,7 @@
#include <simgear/scene/util/SGSceneFeatures.hxx>
#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<VPBMaterialHandler *> 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<VPBMaterialHandler *> 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<const osg::Vec3*>(vertices->getDataPointer());
const osg::Vec2* texPtr = static_cast<const osg::Vec2*>(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<std::pair<double, double>> 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 <lon, lat, handler*> 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<std::tuple<double, double, VPBMaterialHandler *>>
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<osg::Node> 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<osg::Node> 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<osg::Node> constraint)
void VPBTechnique::removeRandomObjectsConstraint(osg::ref_ptr<osg::Node> 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<osgUtil::LineSegmentIntersector> 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()

View File

@ -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<osg::Node> constraint);
virtual void removeVegetationConstraint(osg::ref_ptr<osg::Node> constraint);
virtual bool checkAgainstVegetationConstraints(osg::Vec3d origin, osg::Vec3d vertex);
virtual void clearVegetationConstraints();
virtual void addRandomObjectsConstraint(osg::ref_ptr<osg::Node> constraint);
virtual void removeRandomObjectsConstraint(osg::ref_ptr<osg::Node> constraint);
virtual void clearRandomObjectsConstraints();
OpenThreads::Mutex _writeBufferMutex;
osg::ref_ptr<BufferData> _currentBufferData;
@ -199,7 +199,7 @@ class VPBTechnique : public TerrainTechnique
osg::Matrix3 _filterMatrix;
osg::ref_ptr<osg::Uniform> _filterMatrixUniform;
osg::ref_ptr<SGReaderWriterOptions> _options;
osg::ref_ptr<osg::Group> _vegetationConstraintGroup;
osg::ref_ptr<osg::Group> _randomObjectsConstraintGroup;
inline static osg::ref_ptr<osg::Group> _elevationConstraintGroup = new osg::Group();
inline static std::mutex _elevationConstraintMutex; // protects the _elevationConstraintGroup;