Instance-based random buildings

Replace existing random buildings implementation with one using
proper instancing.  This allows better control of the buildings
themselves and allows BUILDING_LIST STG verb to specify
individual building dimensions.  See README.scenery for details.
This commit is contained in:
Stuart Buchanan
2019-08-20 17:00:17 +01:00
parent 38f6a5a40a
commit 138e28fcbe
3 changed files with 587 additions and 886 deletions

View File

@@ -108,7 +108,7 @@ SGMaterial::SGMaterial( const SGReaderWriterOptions* options,
}
SGMaterial::SGMaterial( const osgDB::Options* options,
const SGPropertyNode *props,
const SGPropertyNode *props,
SGPropertyNode *prop_root,
AreaList *a,
SGSharedPtr<const SGCondition> c)
@@ -210,7 +210,7 @@ SGMaterial::read_properties(const SGReaderWriterOptions* options,
_internal_state st( NULL, tpath.local8BitStr(), true, options );
_status.push_back( st );
}
std::vector<SGPropertyNode_ptr> masks = props->getChildren("object-mask");
for (unsigned int i = 0; i < masks.size(); i++)
{
@@ -243,8 +243,8 @@ SGMaterial::read_properties(const SGReaderWriterOptions* options,
// the object mask, as DDS textures have an origin at the bottom
// left rather than top left. Therefore we flip a copy of the image
// (otherwise a second reference to the object mask would flip it
// back!).
SG_LOG(SG_GENERAL, SG_DEBUG, "Flipping object mask" << omname);
// back!).
SG_LOG(SG_GENERAL, SG_DEBUG, "Flipping object mask" << omname);
image = (osg::Image* ) image->clone(osg::CopyOp::SHALLOW_COPY);
image->flipVertical();
}
@@ -271,30 +271,30 @@ SGMaterial::read_properties(const SGReaderWriterOptions* options,
wrapv = props->getBoolValue("wrapv", true);
mipmap = props->getBoolValue("mipmap", true);
light_coverage = props->getDoubleValue("light-coverage", 0.0);
// Building properties
building_coverage = props->getDoubleValue("building-coverage", 0.0);
building_spacing = props->getDoubleValue("building-spacing-m", 5.0);
std::string bt = props->getStringValue( "building-texture",
"Textures/buildings.png" );
building_texture = SGModelLib::findDataFile(bt, options);
building_texture = SGModelLib::findDataFile(bt, options);
if (building_texture.empty()) {
SG_LOG(SG_GENERAL, SG_ALERT, "Cannot find texture \"" << bt);
}
bt = props->getStringValue("building-lightmap", "Textures/buildings-lightmap.png");
building_lightmap = SGModelLib::findDataFile(bt, options);
building_lightmap = SGModelLib::findDataFile(bt, options);
if (building_lightmap.empty()) {
SG_LOG(SG_GENERAL, SG_ALERT, "Cannot find texture \"" << bt);
}
}
building_small_ratio = props->getDoubleValue("building-small-ratio", 0.8);
building_medium_ratio = props->getDoubleValue("building-medium-ratio", 0.15);
building_large_ratio = props->getDoubleValue("building-large-ratio", 0.05);
building_small_pitch = props->getDoubleValue("building-small-pitch", 0.8);
building_medium_pitch = props->getDoubleValue("building-medium-pitch", 0.2);
building_large_pitch = props->getDoubleValue("building-large-pitch", 0.1);
@@ -305,27 +305,40 @@ SGMaterial::read_properties(const SGReaderWriterOptions* options,
building_medium_max_floors = props->getIntValue("building-medium-max-floors", 8);
building_large_min_floors = props->getIntValue("building-large-min-floors", 5);
building_large_max_floors = props->getIntValue("building-large-max-floors", 20);
building_small_min_width = props->getFloatValue("building-small-min-width-m", 15.0);
building_small_max_width = props->getFloatValue("building-small-max-width-m", 60.0);
building_small_min_depth = props->getFloatValue("building-small-min-depth-m", 10.0);
building_small_max_depth = props->getFloatValue("building-small-max-depth-m", 20.0);
building_medium_min_width = props->getFloatValue("building-medium-min-width-m", 25.0);
building_medium_max_width = props->getFloatValue("building-medium-max-width-m", 50.0);
building_medium_min_depth = props->getFloatValue("building-medium-min-depth-m", 20.0);
building_medium_max_depth = props->getFloatValue("building-medium-max-depth-m", 50.0);
building_large_min_width = props->getFloatValue("building-large-min-width-m", 50.0);
building_large_max_width = props->getFloatValue("building-large-max-width-m", 75.0);
building_large_min_depth = props->getFloatValue("building-large-min-depth-m", 50.0);
building_large_max_depth = props->getFloatValue("building-large-max-depth-m", 75.0);
building_range = props->getDoubleValue("building-range-m", default_object_range);
// There are some constraints on the maximum building size that we can sensibly render.
// Using values outside these ranges will result in the texture being stretched to fit,
// which may not be desireable. We will allow it, but display warnings.
// We do not display warnings for large buildings as we assume the textures are sufficiently
// generic to be stretched without problems.
if (building_small_max_floors > 3) SG_LOG(SG_GENERAL, SG_ALERT, "building-small-max-floors exceeds maximum (3). Texture will be stretched to fit.");
if (building_medium_max_floors > 8) SG_LOG(SG_GENERAL, SG_ALERT, "building-medium-max-floors exceeds maximum (8). Texture will be stretched to fit.");
if (building_large_max_floors > 22) SG_LOG(SG_GENERAL, SG_ALERT, "building-large-max-floors exceeds maximum (22). Texture will be stretched to fit.");
if (building_small_max_width > 192.0) SG_LOG(SG_GENERAL, SG_ALERT, "building-small-max-width-m exceeds maximum (192). Texture will be stretched to fit.");
if (building_small_max_depth > 192.0) SG_LOG(SG_GENERAL, SG_ALERT, "building-small-max-depth-m exceeds maximum (192). Texture will be stretched to fit.");
if (building_medium_max_width > 80.0) SG_LOG(SG_GENERAL, SG_ALERT, "building-medium-max-width-m exceeds maximum (80). Texture will be stretched to fit.");
if (building_medium_max_depth > 80.0) SG_LOG(SG_GENERAL, SG_ALERT, "building-medium-max-depth-m exceeds maximum (80). Texture will be stretched to fit.");
cos_object_max_density_slope_angle = cos(props->getFloatValue("object-max-density-angle-deg", 20.0) * osg::PI/180.0);
cos_object_zero_density_slope_angle = cos(props->getFloatValue("object-zero-density-angle-deg", 30.0) * osg::PI/180.0);
// Random vegetation properties
wood_coverage = props->getDoubleValue("wood-coverage", 0.0);
tree_effect = props->getStringValue("tree-effect", "Effects/tree");
@@ -337,7 +350,7 @@ SGMaterial::read_properties(const SGReaderWriterOptions* options,
cos_tree_zero_density_slope_angle = cos(props->getFloatValue("tree-zero-density-angle-deg", 45.0) * osg::PI/180.0);
const SGPropertyNode* treeTexNode = props->getChild("tree-texture");
if (treeTexNode) {
std::string treeTexPath = props->getStringValue("tree-texture");
@@ -398,7 +411,7 @@ SGMaterial::read_properties(const SGReaderWriterOptions* options,
if (name)
glyphs[name] = new SGMaterialGlyph(glyph_nodes[i]);
}
// Read parameters entry, which is passed into the effect
if (props->hasChild("parameters")) {
parameters = props->getChild("parameters");
@@ -413,7 +426,7 @@ SGMaterial::read_properties(const SGReaderWriterOptions* options,
// Private methods.
////////////////////////////////////////////////////////////////////////
void
void
SGMaterial::init ()
{
_status.clear();
@@ -437,7 +450,7 @@ SGMaterial::init ()
}
Effect* SGMaterial::get_effect(int i)
{
{
if(!_status[i].effect_realized) {
if (!_status[i].effect.valid())
return 0;
@@ -454,7 +467,7 @@ Effect* SGMaterial::get_one_effect(int texIndex)
SG_LOG( SG_GENERAL, SG_WARN, "No effect available.");
return 0;
}
int i = texIndex % _status.size();
return get_effect(i);
}
@@ -472,9 +485,9 @@ osg::Texture2D* SGMaterial::get_one_object_mask(int texIndex)
SG_LOG( SG_GENERAL, SG_WARN, "No mask available.");
return 0;
}
// Note that the object mask is closely linked to the texture/effect
// so we index based on the texture index,
// so we index based on the texture index,
unsigned int i = texIndex % _status.size();
if (i < _masks.size()) {
return _masks[i].get();
@@ -489,10 +502,10 @@ void SGMaterial::buildEffectProperties(const SGReaderWriterOptions* options)
ref_ptr<SGMaterialUserData> user = new SGMaterialUserData(this);
SGPropertyNode_ptr propRoot = new SGPropertyNode();
makeChild(propRoot, "inherits-from")->setStringValue(effect);
SGPropertyNode* paramProp = makeChild(propRoot, "parameters");
copyProperties(parameters, paramProp);
SGPropertyNode* materialProp = makeChild(paramProp, "material");
makeChild(materialProp, "ambient")->setValue(SGVec4d(ambient));
makeChild(materialProp, "diffuse")->setValue(SGVec4d(diffuse));

File diff suppressed because it is too large Load Diff

View File

@@ -35,6 +35,7 @@
#include <osg/ShadeModel>
#include <osg/Material>
#include <osg/CullFace>
#include <osg/VertexAttribDivisor>
#include <simgear/scene/util/OsgMath.hxx>
#include <simgear/scene/material/mat.hxx>
@@ -47,141 +48,120 @@
#include <simgear/scene/util/StateAttributeFactory.hxx>
#include <simgear/structure/OSGUtils.hxx>
#define SG_BUILDING_QUAD_TREE_DEPTH 4
#define SG_BUILDING_QUAD_TREE_DEPTH 2
#define SG_BUILDING_FADE_OUT_LEVELS 4
// these correspond to building.eff
const int BUILDING_POSITION_ATTR = 10; // (x,y,z)
const int BUILDING_SCALE_ATTR = 11; // (width, depth, height)
const int BUILDING_ROT_PITCH_TEX0X_ATTR = 12; // (rotation, pitch height, texture x offset)
const int BUILDING_TEX0Y_TEX1X_TEX1Y_ATTR = 13; // (texture y offset, texture x gain, texture y gain)
using namespace osg;
namespace simgear
{
struct BuildingBoundingBoxCallback : public Drawable::ComputeBoundingBoxCallback
{
BuildingBoundingBoxCallback() {}
BuildingBoundingBoxCallback(const BuildingBoundingBoxCallback&, const CopyOp&) {}
META_Object(simgear, BuildingBoundingBoxCallback);
virtual BoundingBox computeBound(const Drawable& drawable) const
{
BoundingBox bb;
const Geometry* geom = static_cast<const Geometry*>(&drawable);
const Vec3Array* pos = static_cast<const Vec3Array*>(geom->getVertexAttribArray(BUILDING_POSITION_ATTR));
for (unsigned int v=0; v<pos->size(); ++v) {
Vec3 pt = (*pos)[v];
bb.expandBy(pt);
}
return bb;
}
};
class SGBuildingBin {
public:
// Number of buildings to auto-generate. Individual
// building instances are taken from this set.
static const unsigned int BUILDING_SET_SIZE = 200;
static const unsigned int QUADS_PER_BUILDING = 12;
static const unsigned int VERTICES_PER_BUILDING = 4 * QUADS_PER_BUILDING;
static const unsigned int VERTICES_PER_BUILDING_SET = BUILDING_SET_SIZE * VERTICES_PER_BUILDING;
enum BuildingType {
SMALL = 0,
MEDIUM,
LARGE };
struct BuildingInstance {
BuildingInstance(Vec3f p, float w, float d, float h, float ph, float r, Vec2f t0, Vec2f t1) :
position(p),
width(w),
depth(d),
height(h),
pitch_height(ph),
rotation(r),
tex0(t0),
tex1(t1)
{ }
BuildingInstance(Vec3f p, BuildingInstance b) :
position(p),
width(b.width),
depth(b.depth),
height(b.height),
pitch_height(b.pitch_height),
rotation(b.rotation),
tex0(b.tex0),
tex1(b.tex1)
{ }
Vec3f position;
float width;
float depth;
float height;
float pitch_height;
float rotation;
Vec2f tex0;
Vec2f tex1;
// References to allow the QuadTreeBuilder to work
//const BuildingList* buildingList;
//ref_ptr<Geometry> sharedGeometry;
Vec3f getPosition() { return position; }
float getRotation() { return rotation; }
float getDistSqr(Vec3f p) {
return (p - position) * (p - position);
}
};
private:
struct Building {
Building(BuildingType t, float w, float d, float h, int f, bool pitch) :
type(t),
width(w),
depth(d),
height(h),
floors(f),
pitched(pitch),
radius(std::max(d, 0.5f*w))
{ }
BuildingType type;
float width;
float depth;
float height;
int floors;
bool pitched;
float radius;
float getFootprint() {
return radius;
}
};
// The set of buildings that are instantiated
typedef std::vector<Building> BuildingList;
BuildingList smallBuildings;
BuildingList mediumBuildings;
BuildingList largeBuildings;
const SGMaterial *material;
std::string* material_name;
std::string* texture;
std::string* lightMap;
// Fraction of buildings of this type
float smallBuildingFraction;
float mediumBuildingFraction;
// The maximum radius of each building type
float smallBuildingMaxRadius;
float mediumBuildingMaxRadius;
float largeBuildingMaxRadius;
// The maximum depth of each building type
float smallBuildingMaxDepth;
float mediumBuildingMaxDepth;
float largeBuildingMaxDepth;
// Visibility range for buildings
float buildingRange;
// Shared geometries of the building set
ref_ptr<Geometry> smallSharedGeometry;
ref_ptr<Geometry> mediumSharedGeometry;
ref_ptr<Geometry> largeSharedGeometry;
struct BuildingInstance {
BuildingInstance(SGVec3f p, float r, const BuildingList* bl, ref_ptr<Geometry> sg) :
position(p),
rotation(r),
buildingList(bl),
sharedGeometry(sg)
{ }
BuildingInstance(SGVec3f p, BuildingInstance b) :
position(p),
rotation(b.rotation),
buildingList(b.buildingList),
sharedGeometry(b.sharedGeometry)
{ }
SGVec3f position;
float rotation;
// References to allow the QuadTreeBuilder to work
const BuildingList* buildingList;
ref_ptr<Geometry> sharedGeometry;
SGVec3f getPosition() { return position; }
float getRotation() { return rotation; }
float getDistSqr(SGVec3f p) {
return distSqr(p, position);
}
const osg::Vec4f getColorValue() {
return osg::Vec4f(toOsg(position), rotation);
}
};
// Information for an instance of a building - position and orientation
typedef std::vector<BuildingInstance> BuildingInstanceList;
BuildingInstanceList smallBuildingLocations;
BuildingInstanceList mediumBuildingLocations;
BuildingInstanceList largeBuildingLocations;
BuildingInstanceList buildingLocations;
public:
SGBuildingBin(const SGMaterial *mat, bool useVBOs);
SGBuildingBin(const SGPath& absoluteFileName, const SGMaterial *mat, bool useVBOs);
~SGBuildingBin() {
smallBuildings.clear();
mediumBuildings.clear();
largeBuildings.clear();
smallBuildingLocations.clear();
mediumBuildingLocations.clear();
largeBuildingLocations.clear();
}
~SGBuildingBin();
// Generate a building specifying the exact position, dimensions and texture index.
void insert(SGVec3f p, float r, BuildingType buildingtype, float width, float depth, float height, float pitch_height, int floors, int tex_index);
// Generate a building of a given type at a specified position, using the random building material definition to determine the dimensions and texture index.
void insert(SGVec3f p, float r, BuildingType type);
int getNumBuildings();
@@ -190,123 +170,9 @@ public:
std::string* getMaterialName() { return material_name; }
BuildingType getBuildingType(float roll);
float getBuildingMaxRadius(BuildingType);
float getBuildingMaxDepth(BuildingType);
// Helper classes for creating the quad tree
struct MakeBuildingLeaf
{
MakeBuildingLeaf(float range, Effect* effect, bool fade) :
_range(range), _effect(effect), _fade_out(fade) {}
MakeBuildingLeaf(const MakeBuildingLeaf& rhs) :
_range(rhs._range), _effect(rhs._effect), _fade_out(rhs._fade_out)
{}
LOD* operator() () const
{
LOD* result = new LOD;
if (_fade_out) {
// Create a series of LOD nodes so building cover decreases
// gradually with distance from _range to 2*_range
for (float i = 0.0; i < SG_BUILDING_FADE_OUT_LEVELS; i++)
{
EffectGeode* geode = new EffectGeode;
geode->setEffect(_effect.get());
result->addChild(geode, 0, _range * (1.0 + i / (SG_BUILDING_FADE_OUT_LEVELS - 1.0)));
}
} else {
// No fade-out, so all are visible for 2X range
EffectGeode* geode = new EffectGeode;
geode->setEffect(_effect.get());
result->addChild(geode, 0, 2.0 * _range);
}
return result;
}
float _range;
ref_ptr<Effect> _effect;
bool _fade_out;
};
struct AddBuildingLeafObject
{
Geometry* createNewBuildingGeometryInstance(const BuildingInstance& building) const
{
Geometry* geom = simgear::clone(building.sharedGeometry.get(), CopyOp::SHALLOW_COPY);
geom->setColorArray(new Vec4Array, Array::BIND_PER_VERTEX);
geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS));
return geom;
}
void operator() (LOD* lod, const BuildingInstance& building) const
{
Geode* geode = static_cast<Geode*>(lod->getChild(int(building.position.x() * 10.0f) % lod->getNumChildren()));
unsigned int numDrawables = geode->getNumDrawables();
// Get the last geometry of to be added and check if there is space for
// another building instance within it. This is done by checking
// if the number of Color values matches the number of vertices.
// The color array is used to store the position of a particular
// instance.
Geometry* geom;
if (numDrawables == 0) {
// Create a new copy of the shared geometry to instantiate
geom = createNewBuildingGeometryInstance(building);
geode->addDrawable(geom);
} else {
geom = static_cast<Geometry*>(geode->getDrawable(numDrawables - 1));
}
// Check if this building is too close to any other others.
DrawArrays* primSet = static_cast<DrawArrays*>(geom->getPrimitiveSet(0));
Vec4Array* posArray = static_cast<Vec4Array*>(geom->getColorArray());
// Now check if this geometry is full.
if (posArray->size() >= static_cast<Vec3Array*>(geom->getVertexArray())->size()) {
// This particular geometry is full, so we generate another
// by taking a shallow copy of the shared Geomety.
geom = createNewBuildingGeometryInstance(building);
geode->addDrawable(geom);
posArray = static_cast<Vec4Array*>(geom->getColorArray());
SG_LOG(SG_TERRAIN, SG_DEBUG, "Added new geometry to building geod: " << geode->getNumDrawables());
}
// We now have a geometry with space for this new building.
// Set the position and rotation
osg::Vec4f c = osg::Vec4f(toOsg(building.position), building.rotation);
posArray->insert(posArray->end(), VERTICES_PER_BUILDING, c);
size_t numVerts = posArray->size();
primSet = static_cast<DrawArrays*>(geom->getPrimitiveSet(0));
primSet->setCount(numVerts);
}
};
struct GetBuildingCoord
{
Vec3 operator() (const BuildingInstance& building) const
{
return toOsg(building.position);
}
};
typedef QuadTreeBuilder<LOD*, BuildingInstance, MakeBuildingLeaf, AddBuildingLeafObject,
GetBuildingCoord> BuildingGeometryQuadtree;
struct BuildingInstanceTransformer
{
BuildingInstanceTransformer(Matrix& mat_) : mat(mat_) {}
BuildingInstance operator()(const BuildingInstance& buildingInstance) const
{
Vec3 pos = toOsg(buildingInstance.position) * mat;
return BuildingInstance(toSG(pos), buildingInstance);
}
Matrix mat;
};
ref_ptr<Group> createBuildingsGroup(Matrix transInv, const SGReaderWriterOptions* options);
};