From 3000fdc33c10b5d4336d4b0b245cb5a469bf4226 Mon Sep 17 00:00:00 2001 From: Stuart Buchanan Date: Wed, 12 Sep 2012 22:45:12 +0100 Subject: [PATCH] Improve memory occupancy and load time of random buildings. --- simgear/scene/tgdb/SGBuildingBin.cxx | 1145 +++++++++++++++----------- simgear/scene/tgdb/SGBuildingBin.hxx | 251 +++++- simgear/scene/tgdb/obj.cxx | 623 +++++++------- 3 files changed, 1179 insertions(+), 840 deletions(-) diff --git a/simgear/scene/tgdb/SGBuildingBin.cxx b/simgear/scene/tgdb/SGBuildingBin.cxx index 180dc48d..7d5e4861 100644 --- a/simgear/scene/tgdb/SGBuildingBin.cxx +++ b/simgear/scene/tgdb/SGBuildingBin.cxx @@ -44,23 +44,18 @@ #include #include +#include +#include #include #include #include #include #include #include -#include -#include -#include -#include - #include "ShaderGeometry.hxx" #include "SGBuildingBin.hxx" -#define SG_BUILDING_QUAD_TREE_DEPTH 2 -#define SG_BUILDING_FADE_OUT_LEVELS 4 using namespace osg; @@ -71,247 +66,332 @@ typedef std::map > BuildingStateSe static BuildingStateSetMap statesetmap; static int numBuildings; -void addBuildingToLeafGeode(Geode* geode, const SGBuildingBin::Building& building) +typedef std::map > EffectMap; +static EffectMap buildingEffectMap; + +// Building instance scheme: +// vertex - local position of vertices, with 0,0,0 being the center front. +// fog coord - rotation +// color - xyz of tree quad origin, replicated 4 times. + +struct BuildingBoundingBoxCallback : public Drawable::ComputeBoundingBoxCallback { - // Generate a repeatable random seed - mt seed; - mt_init(&seed, unsigned(building.position.x())); - - // Get or create geometry. - osg::ref_ptr geom; - osg::ref_ptr v; - osg::ref_ptr t; - osg::ref_ptr c; - osg::ref_ptr n; - - if (geode->getNumDrawables() == 0) { - geom = new osg::Geometry; - v = new osg::Vec3Array; - t = new osg::Vec2Array; - c = new osg::Vec4Array; - n = new osg::Vec3Array; + BuildingBoundingBoxCallback() {} + BuildingBoundingBoxCallback(const BuildingBoundingBoxCallback&, const CopyOp&) {} + META_Object(simgear, BuildingBoundingBoxCallback); + virtual BoundingBox computeBound(const Drawable&) const; +}; + +BoundingBox +BuildingBoundingBoxCallback::computeBound(const Drawable& drawable) const +{ + BoundingBox bb; + const Geometry* geom = static_cast(&drawable); + const Vec3Array* v = static_cast(geom->getVertexArray()); + const Vec4Array* pos = static_cast(geom->getColorArray()); + + Geometry::PrimitiveSetList primSets = geom->getPrimitiveSetList(); + for (Geometry::PrimitiveSetList::const_iterator psitr = primSets.begin(), psend = primSets.end(); + psitr != psend; + ++psitr) { + DrawArrays* da = static_cast(psitr->get()); + GLint psFirst = da->getFirst(); + GLint psEndVert = psFirst + da->getCount(); + for (GLint i = psFirst;i < psEndVert; ++i) { + Vec3 pt = (*v)[i]; + Matrixd trnsfrm = Matrixd::rotate(- M_PI * 2 * (*pos)[i].a(), Vec3(0.0f, 0.0f, 1.0f)); + pt = pt * trnsfrm; + pt += Vec3((*pos)[i].x(), (*pos)[i].y(), (*pos)[i].z()); + bb.expandBy(pt); + } + } + return bb; +} + + // Set up the building set based on the material definitions + SGBuildingBin::SGBuildingBin(const SGMaterial *mat) { + + material_name = mat->get_names()[0]; + SG_LOG(SG_TERRAIN, SG_DEBUG, "Building material " << material_name); + texture = mat->get_building_texture(); + lightMap = mat->get_building_lightmap(); + SG_LOG(SG_TERRAIN, SG_DEBUG, "Building texture " << texture); + + // Generate a random seed for the building generation. + mt seed; + mt_init(&seed, unsigned(123)); + + smallSharedGeometry = new osg::Geometry(); + mediumSharedGeometry = new osg::Geometry(); + largeSharedGeometry = new osg::Geometry(); + + smallBuildingMaxRadius = std::max(mat->get_building_small_max_depth() * 0.5, mat->get_building_small_max_width() * 0.5); + mediumBuildingMaxRadius = std::max(mat->get_building_medium_max_depth() * 0.5, mat->get_building_medium_max_width() * 0.5); + largeBuildingMaxRadius = std::max(mat->get_building_large_max_depth() * 0.5, mat->get_building_large_max_width() * 0.5); + + smallBuildingMaxDepth = mat->get_building_small_max_depth(); + mediumBuildingMaxDepth = mat->get_building_medium_max_depth(); + largeBuildingMaxDepth = mat->get_building_large_max_depth(); + + smallBuildingFraction = mat->get_building_small_fraction(); + mediumBuildingFraction = mat->get_building_medium_fraction(); + + SG_LOG(SG_TERRAIN, SG_DEBUG, "Building fractions " << smallBuildingFraction << " " << mediumBuildingFraction); + + + // TODO: Reverse this - otherwise we never get any large buildings! + BuildingType types[] = { SGBuildingBin::SMALL, SGBuildingBin::MEDIUM, SGBuildingBin::LARGE }; + BuildingList lists[] = { SGBuildingBin::smallBuildings, SGBuildingBin::mediumBuildings, SGBuildingBin::largeBuildings }; + ref_ptr geometries[] = { smallSharedGeometry, mediumSharedGeometry, largeSharedGeometry }; + + for (int bt=0; bt < 3; bt++) { + SGBuildingBin::BuildingType buildingtype = types[bt]; + ref_ptr sharedGeometry = geometries[bt]; + BuildingList buildings = lists[bt]; - // Set the color, which is bound overall, and simply white - c->push_back( osg::Vec4( 1, 1, 1, 1) ); - geom->setColorArray(c); - geom->setColorBinding(osg::Geometry::BIND_OVERALL); - - geom->setNormalBinding(osg::Geometry::BIND_PER_VERTEX); - // Temporary primitive set. Will be over-written later. - geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,1)); - geode->addDrawable(geom); - } else { - geom = (osg::Geometry*) geode->getDrawable(0); - v = (osg::Vec3Array*) geom->getVertexArray(); - t = (osg::Vec2Array*) geom->getTexCoordArray(0); - c = (osg::Vec4Array*) geom->getColorArray(); - n = (osg::Vec3Array*) geom->getNormalArray(); - } - - // For the moment we'll create a simple box with 5 sides (no need - // for a base). - int num_quads = 5; + osg::ref_ptr v = new osg::Vec3Array; + osg::ref_ptr t = new osg::Vec2Array; + osg::ref_ptr n = new osg::Vec3Array; - if (building.pitched) { - // If it's a pitched roof, we add another 3 quads (we'll be - // removing the flat top). - num_quads+=3; - } - - // Set up the rotation and translation matrix, which we apply to - // vertices as they are created as we'll be adding buildings later. - osg::Matrix transformMat; - transformMat = osg::Matrix::translate(toOsg(building.position)); - double hdg = - building.rotation * M_PI * 2; - osg::Matrix rotationMat = osg::Matrix::rotate(hdg, - osg::Vec3d(0.0, 0.0, 1.0)); - transformMat.preMult(rotationMat); - - // Create the vertices - float cw = 0.5f * building.width; - float cd = building.depth; - float ch = building.height; + v->reserve(BUILDING_SET_SIZE * VERTICES_PER_BUILDING); + t->reserve(BUILDING_SET_SIZE * VERTICES_PER_BUILDING); + n->reserve(BUILDING_SET_SIZE * VERTICES_PER_BUILDING); - // 0,0,0 is the bottom center of the front - // face, e.g. where the front door would be + sharedGeometry->setNormalBinding(osg::Geometry::BIND_PER_VERTEX); + sharedGeometry->setFogCoordBinding(osg::Geometry::BIND_PER_VERTEX); + sharedGeometry->setComputeBoundingBoxCallback(new BuildingBoundingBoxCallback); + sharedGeometry->setUseDisplayList(false); - // BASEMENT - // This exteds 10m below the main section - // Front face - v->push_back( osg::Vec3( 0, -cw, -10) * transformMat ); // bottom right - v->push_back( osg::Vec3( 0, cw, -10) * transformMat ); // bottom left - v->push_back( osg::Vec3( 0, cw, 0) * transformMat ); // top left - v->push_back( osg::Vec3( 0, -cw, 0) * transformMat ); // top right - - for (int i=0; i<4; ++i) - n->push_back( osg::Vec3(1, 0, 0) * rotationMat ); // normal - - // Left face - v->push_back( osg::Vec3( -cd, -cw, -10) * transformMat ); // bottom right - v->push_back( osg::Vec3( 0, -cw, -10) * transformMat ); // bottom left - v->push_back( osg::Vec3( 0, -cw, 0) * transformMat ); // top left - v->push_back( osg::Vec3( -cd, -cw, 0) * transformMat ); // top right - - for (int i=0; i<4; ++i) - n->push_back( osg::Vec3(0, -1, 0) * rotationMat ); // normal - - // Back face - v->push_back( osg::Vec3( -cd, cw, -10) * transformMat ); // bottom right - v->push_back( osg::Vec3( -cd, -cw, -10) * transformMat ); // bottom left - v->push_back( osg::Vec3( -cd, -cw, 0) * transformMat ); // top left - v->push_back( osg::Vec3( -cd, cw, 0) * transformMat ); // top right - - for (int i=0; i<4; ++i) - n->push_back( osg::Vec3(-1, 0, 0) * rotationMat ); // normal - - // Right face - v->push_back( osg::Vec3( 0, cw, -10) * transformMat ); // bottom right - v->push_back( osg::Vec3( -cd, cw, -10) * transformMat ); // bottom left - v->push_back( osg::Vec3( -cd, cw, 0) * transformMat ); // top left - v->push_back( osg::Vec3( 0, cw, 0) * transformMat ); // top right - - for (int i=0; i<4; ++i) - n->push_back( osg::Vec3(0, 1, 0) * rotationMat ); // normal - - // MAIN BODY - // Front face - v->push_back( osg::Vec3( 0, -cw, 0) * transformMat ); // bottom right - v->push_back( osg::Vec3( 0, cw, 0) * transformMat ); // bottom left - v->push_back( osg::Vec3( 0, cw, ch) * transformMat ); // top left - v->push_back( osg::Vec3( 0, -cw, ch) * transformMat ); // top right - - for (int i=0; i<4; ++i) - n->push_back( osg::Vec3(1, 0, 0) * rotationMat ); // normal - - // Left face - v->push_back( osg::Vec3( -cd, -cw, 0) * transformMat ); // bottom right - v->push_back( osg::Vec3( 0, -cw, 0) * transformMat ); // bottom left - v->push_back( osg::Vec3( 0, -cw, ch) * transformMat ); // top left - v->push_back( osg::Vec3( -cd, -cw, ch) * transformMat ); // top right - - for (int i=0; i<4; ++i) - n->push_back( osg::Vec3(0, -1, 0) * rotationMat ); // normal - - // Back face - v->push_back( osg::Vec3( -cd, cw, 0) * transformMat ); // bottom right - v->push_back( osg::Vec3( -cd, -cw, 0) * transformMat ); // bottom left - v->push_back( osg::Vec3( -cd, -cw, ch) * transformMat ); // top left - v->push_back( osg::Vec3( -cd, cw, ch) * transformMat ); // top right - - for (int i=0; i<4; ++i) - n->push_back( osg::Vec3(-1, 0, 0) * rotationMat ); // normal - - // Right face - v->push_back( osg::Vec3( 0, cw, 0) * transformMat ); // bottom right - v->push_back( osg::Vec3( -cd, cw, 0) * transformMat ); // bottom left - v->push_back( osg::Vec3( -cd, cw, ch) * transformMat ); // top left - v->push_back( osg::Vec3( 0, cw, ch) * transformMat ); // top right - - for (int i=0; i<4; ++i) - n->push_back( osg::Vec3(0, 1, 0) * rotationMat ); // normal - - // ROOF - if (building.pitched) { + for (unsigned int j = 0; j < BUILDING_SET_SIZE; j++) { + float width; + float depth; + int floors; + float height; + bool pitched; + + if (buildingtype == SGBuildingBin::SMALL) { + // Small building + width = mat->get_building_small_min_width() + mt_rand(&seed) * mt_rand(&seed) * (mat->get_building_small_max_width() - mat->get_building_small_min_width()); + depth = mat->get_building_small_min_depth() + mt_rand(&seed) * mt_rand(&seed) * (mat->get_building_small_max_depth() - mat->get_building_small_min_depth()); + floors = SGMisc::round(mat->get_building_small_min_floors() + mt_rand(&seed) * (mat->get_building_small_max_floors() - mat->get_building_small_min_floors())); + height = floors * (2.8 + mt_rand(&seed)); + + // Small buildings are never deeper than they are wide. + if (depth > width) { depth = width; } + + pitched = (mt_rand(&seed) < mat->get_building_small_pitch()); + } else if (buildingtype == SGBuildingBin::MEDIUM) { + width = mat->get_building_medium_min_width() + mt_rand(&seed) * mt_rand(&seed) * (mat->get_building_medium_max_width() - mat->get_building_medium_min_width()); + depth = mat->get_building_medium_min_depth() + mt_rand(&seed) * mt_rand(&seed) * (mat->get_building_medium_max_depth() - mat->get_building_medium_min_depth()); + floors = SGMisc::round(mat->get_building_medium_min_floors() + mt_rand(&seed) * (mat->get_building_medium_max_floors() - mat->get_building_medium_min_floors())); + height = floors * (2.8 + mt_rand(&seed)); + + while ((height > width) && (floors > mat->get_building_medium_min_floors())) { + // Ensure that medium buildings aren't taller than they are wide + floors--; + height = floors * (2.8 + mt_rand(&seed)); + } + + pitched = (mt_rand(&seed) < mat->get_building_medium_pitch()); + } else { + width = mat->get_building_large_min_width() + mt_rand(&seed) * (mat->get_building_large_max_width() - mat->get_building_large_min_width()); + depth = mat->get_building_large_min_depth() + mt_rand(&seed) * (mat->get_building_large_max_depth() - mat->get_building_large_min_depth()); + floors = SGMisc::round(mat->get_building_large_min_floors() + mt_rand(&seed) * (mat->get_building_large_max_floors() - mat->get_building_large_min_floors())); + height = floors * (2.8 + mt_rand(&seed)); + pitched = (mt_rand(&seed) < mat->get_building_large_pitch()); + } - // Front pitched roof - v->push_back( osg::Vec3( 0, -cw, ch) * transformMat ); // bottom right - v->push_back( osg::Vec3( 0, cw, ch) * transformMat ); // bottom left - v->push_back( osg::Vec3(-0.5*cd, cw, ch+3) * transformMat ); // top left - v->push_back( osg::Vec3(-0.5*cd, -cw, ch+3) * transformMat ); // top right + Building building = Building(buildingtype, + width, + depth, + height, + floors, + pitched); - for (int i=0; i<4; ++i) - n->push_back( osg::Vec3(0.707, 0, 0.707) * rotationMat ); // normal + buildings.push_back(building); + + // Now create an OSG Geometry based on the Building + float cw = 0.5f * building.width; + float cd = building.depth; + float ch = building.height; + + // 0,0,0 is the bottom center of the front + // face, e.g. where the front door would be - // Left pitched roof - v->push_back( osg::Vec3( -cd, -cw, ch) * transformMat ); // bottom right - v->push_back( osg::Vec3( 0, -cw, ch) * transformMat ); // bottom left - v->push_back( osg::Vec3(-0.5*cd, -cw, ch+3) * transformMat ); // top left - v->push_back( osg::Vec3(-0.5*cd, -cw, ch+3) * transformMat ); // top right + // BASEMENT + // This exteds 10m below the main section + // Front face + v->push_back( osg::Vec3( 0, -cw, -10) ); // bottom right + v->push_back( osg::Vec3( 0, cw, -10) ); // bottom left + v->push_back( osg::Vec3( 0, cw, 0) ); // top left + v->push_back( osg::Vec3( 0, -cw, 0) ); // top right for (int i=0; i<4; ++i) - n->push_back( osg::Vec3(0, -1, 0) * rotationMat ); // normal - - // Back pitched roof - v->push_back( osg::Vec3( -cd, cw, ch) * transformMat ); // bottom right - v->push_back( osg::Vec3( -cd, -cw, ch) * transformMat ); // bottom left - v->push_back( osg::Vec3(-0.5*cd, -cw, ch+3) * transformMat ); // top left - v->push_back( osg::Vec3(-0.5*cd, cw, ch+3) * transformMat ); // top right + n->push_back( osg::Vec3(1, 0, 0) ); // normal + // Left face + v->push_back( osg::Vec3( -cd, -cw, -10) ); // bottom right + v->push_back( osg::Vec3( 0, -cw, -10) ); // bottom left + v->push_back( osg::Vec3( 0, -cw, 0) ); // top left + v->push_back( osg::Vec3( -cd, -cw, 0) ); // top right + for (int i=0; i<4; ++i) - n->push_back( osg::Vec3(-0.707, 0, 0.707) * rotationMat ); // normal + n->push_back( osg::Vec3(0, -1, 0) ); // normal - // Right pitched roof - v->push_back( osg::Vec3( 0, cw, ch) * transformMat ); // bottom right - v->push_back( osg::Vec3( -cd, cw, ch) * transformMat ); // bottom left - v->push_back( osg::Vec3(-0.5*cd, cw, ch+3) * transformMat ); // top left - v->push_back( osg::Vec3(-0.5*cd, cw, ch+3) * transformMat ); // top right + // Back face + v->push_back( osg::Vec3( -cd, cw, -10) ); // bottom right + v->push_back( osg::Vec3( -cd, -cw, -10) ); // bottom left + v->push_back( osg::Vec3( -cd, -cw, 0) ); // top left + v->push_back( osg::Vec3( -cd, cw, 0) ); // top right for (int i=0; i<4; ++i) - n->push_back( osg::Vec3(0, 1, 0) * rotationMat ); // normal - } else { - // Top face - v->push_back( osg::Vec3( 0, -cw, ch) * transformMat ); // bottom right - v->push_back( osg::Vec3( 0, cw, ch) * transformMat ); // bottom left - v->push_back( osg::Vec3( -cd, cw, ch) * transformMat ); // top left - v->push_back( osg::Vec3( -cd, -cw, ch) * transformMat ); // top right + n->push_back( osg::Vec3(-1, 0, 0) ); // normal + // Right face + v->push_back( osg::Vec3( 0, cw, -10) ); // bottom right + v->push_back( osg::Vec3( -cd, cw, -10) ); // bottom left + v->push_back( osg::Vec3( -cd, cw, 0) ); // top left + v->push_back( osg::Vec3( 0, cw, 0) ); // top right + for (int i=0; i<4; ++i) - n->push_back( osg::Vec3( 0, 0, 1) * rotationMat ); // normal - } - - // The 1024x1024 texture is split into 32x16 blocks. - // For a small building, each block is 6m wide and 3m high. - // For a medium building, each block is 10m wide and 3m high. - // For a large building, each block is 20m wide and 3m high - - if (building.type == SGBuildingBin::SMALL) { - // Small buildings are represented on the bottom 5 rows of 3 floors - int row = ((int) (mt_rand(&seed) * 1000)) % 5; - float base_y = (float) row * 16.0 * 3.0 / 1024.0; - float top_y = base_y + 16.0 * (float) building.floors / 1024.0; - float left_x = 32.0 / 1024.0 * round((float) building.width / 6.0f); - float right_x = 0.0f; - float front_x = 384.0/1024.0; - float back_x = 384.0/1024.0 + 32.0 / 1024.0 * round((float) building.depth/ 6.0f); - - // BASEMENT - uses the baseline texture - for (unsigned int i = 0; i < 16; i++) { - t->push_back( osg::Vec2( left_x, base_y) ); - } + n->push_back( osg::Vec3(0, 1, 0) ); // normal + // MAIN BODY - // Front - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right + // Front face + v->push_back( osg::Vec3( 0, -cw, 0) ); // bottom right + v->push_back( osg::Vec3( 0, cw, 0) ); // bottom left + v->push_back( osg::Vec3( 0, cw, ch) ); // top left + v->push_back( osg::Vec3( 0, -cw, ch) ); // top right - // Left - t->push_back( osg::Vec2( front_x, base_y) ); // bottom right - t->push_back( osg::Vec2( back_x, base_y) ); // bottom left - t->push_back( osg::Vec2( back_x, top_y ) ); // top left - t->push_back( osg::Vec2( front_x, top_y ) ); // top right + for (int i=0; i<4; ++i) + n->push_back( osg::Vec3(1, 0, 0) ); // normal - // Back (same as front for the moment) - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right + // Left face + v->push_back( osg::Vec3( -cd, -cw, 0) ); // bottom right + v->push_back( osg::Vec3( 0, -cw, 0) ); // bottom left + v->push_back( osg::Vec3( 0, -cw, ch) ); // top left + v->push_back( osg::Vec3( -cd, -cw, ch) ); // top right + + for (int i=0; i<4; ++i) + n->push_back( osg::Vec3(0, -1, 0) ); // normal + + // Back face + v->push_back( osg::Vec3( -cd, cw, 0) ); // bottom right + v->push_back( osg::Vec3( -cd, -cw, 0) ); // bottom left + v->push_back( osg::Vec3( -cd, -cw, ch) ); // top left + v->push_back( osg::Vec3( -cd, cw, ch) ); // top right + + for (int i=0; i<4; ++i) + n->push_back( osg::Vec3(-1, 0, 0) ); // normal - // Right (same as left for the moment) - t->push_back( osg::Vec2( front_x, base_y) ); // bottom right - t->push_back( osg::Vec2( back_x, base_y) ); // bottom left - t->push_back( osg::Vec2( back_x, top_y ) ); // top left - t->push_back( osg::Vec2( front_x, top_y ) ); // top right + // Right face + v->push_back( osg::Vec3( 0, cw, 0) ); // bottom right + v->push_back( osg::Vec3( -cd, cw, 0) ); // bottom left + v->push_back( osg::Vec3( -cd, cw, ch) ); // top left + v->push_back( osg::Vec3( 0, cw, ch) ); // top right + for (int i=0; i<4; ++i) + n->push_back( osg::Vec3(0, 1, 0) ); // normal + // ROOF - if (building.pitched) { - // Use the entire height of the roof texture - top_y = base_y + 16.0 * 3.0 / 1024.0; - left_x = 512/1024.0 + 32.0 / 1024.0 * round(building.width / 6.0f); - right_x = 512/1024.0; - front_x = 480.0/1024.0; - back_x = 512.0/1024.0; + if (building.pitched) { + + // Front pitched roof + v->push_back( osg::Vec3( 0, -cw, ch) ); // bottom right + v->push_back( osg::Vec3( 0, cw, ch) ); // bottom left + v->push_back( osg::Vec3(-0.5*cd, cw, ch+3) ); // top left + v->push_back( osg::Vec3(-0.5*cd, -cw, ch+3) ); // top right + + for (int i=0; i<4; ++i) + n->push_back( osg::Vec3(0.707, 0, 0.707) ); // normal + + // Left pitched roof + v->push_back( osg::Vec3( -cd, -cw, ch) ); // bottom right + v->push_back( osg::Vec3( 0, -cw, ch) ); // bottom left + v->push_back( osg::Vec3(-0.5*cd, -cw, ch+3) ); // top left + v->push_back( osg::Vec3(-0.5*cd, -cw, ch+3) ); // top right + + for (int i=0; i<4; ++i) + n->push_back( osg::Vec3(0, -1, 0) ); // normal + + // Back pitched roof + v->push_back( osg::Vec3( -cd, cw, ch) ); // bottom right + v->push_back( osg::Vec3( -cd, -cw, ch) ); // bottom left + v->push_back( osg::Vec3(-0.5*cd, -cw, ch+3) ); // top left + v->push_back( osg::Vec3(-0.5*cd, cw, ch+3) ); // top right + for (int i=0; i<4; ++i) + n->push_back( osg::Vec3(-0.707, 0, 0.707) ); // normal + + // Right pitched roof + v->push_back( osg::Vec3( 0, cw, ch) ); // bottom right + v->push_back( osg::Vec3( -cd, cw, ch) ); // bottom left + v->push_back( osg::Vec3(-0.5*cd, cw, ch+3) ); // top left + v->push_back( osg::Vec3(-0.5*cd, cw, ch+3) ); // top right + + for (int i=0; i<4; ++i) + n->push_back( osg::Vec3(0, 1, 0) ); // normal + } else { + // If the roof isn't pitched, we still generate the + // vertices for simplicity later. + + // Top of the roof + v->push_back( osg::Vec3( 0, -cw, ch) ); // bottom right + v->push_back( osg::Vec3( 0, cw, ch) ); // bottom left + v->push_back( osg::Vec3(-cd, cw, ch) ); // top left + v->push_back( osg::Vec3(-cd, -cw, ch) ); // top right + + for (int i=0; i<4; ++i) + n->push_back( osg::Vec3(0, 0, 1) ); // normal + + // Left non-pitched roof + v->push_back( osg::Vec3( -cd, -cw, ch) ); // bottom right + v->push_back( osg::Vec3( 0, -cw, ch) ); // bottom left + v->push_back( osg::Vec3( 0, -cw, ch) ); // top left + v->push_back( osg::Vec3( -cd, -cw, ch) ); // top right + + for (int i=0; i<4; ++i) + n->push_back( osg::Vec3(0, -1, 0) ); // normal + + // Back pitched roof + v->push_back( osg::Vec3(-cd, cw, ch) ); // bottom right + v->push_back( osg::Vec3(-cd, -cw, ch) ); // bottom left + v->push_back( osg::Vec3(-cd, -cw, ch) ); // top left + v->push_back( osg::Vec3(-cd, cw, ch) ); // top right + + for (int i=0; i<4; ++i) + n->push_back( osg::Vec3(1, 0, 0) ); // normal + + // Right pitched roof + v->push_back( osg::Vec3( 0, cw, ch) ); // bottom right + v->push_back( osg::Vec3(-cd, cw, ch) ); // bottom left + v->push_back( osg::Vec3(-cd, cw, ch) ); // top left + v->push_back( osg::Vec3( 0, cw, ch) ); // top right + + for (int i=0; i<4; ++i) + n->push_back( osg::Vec3(0, 1, 0) ); // normal + } + + // The 1024x1024 texture is split into 32x16 blocks. + // For a small building, each block is 6m wide and 3m high. + // For a medium building, each block is 10m wide and 3m high. + // For a large building, each block is 20m wide and 3m high + + if (building.type == SGBuildingBin::SMALL) { + // Small buildings are represented on the bottom 5 rows of 3 floors + int row = ((int) (mt_rand(&seed) * 1000)) % 5; + float base_y = (float) row * 16.0 * 3.0 / 1024.0; + float top_y = base_y + 16.0 * (float) building.floors / 1024.0; + float left_x = 32.0 / 1024.0 * round((float) building.width / 6.0f); + float right_x = 0.0f; + float front_x = 384.0/1024.0; + float back_x = 384.0/1024.0 + 32.0 / 1024.0 * round((float) building.depth/ 6.0f); + + // BASEMENT - uses the baseline texture + for (unsigned int i = 0; i < 16; i++) { + t->push_back( osg::Vec2( left_x, base_y) ); + } + // MAIN BODY // Front t->push_back( osg::Vec2( right_x, base_y) ); // bottom right t->push_back( osg::Vec2( left_x, base_y) ); // bottom left @@ -335,66 +415,71 @@ void addBuildingToLeafGeode(Geode* geode, const SGBuildingBin::Building& buildin t->push_back( osg::Vec2( back_x, base_y) ); // bottom left t->push_back( osg::Vec2( back_x, top_y ) ); // top left t->push_back( osg::Vec2( front_x, top_y ) ); // top right - } else { - // Flat roof - left_x = 640.0/1024.0; - right_x = 512.0/1024.0; - // Use the entire height of the roof texture - top_y = base_y + 16.0 * 3.0 / 1024.0; + + // ROOF + if (building.pitched) { + // Use the entire height of the roof texture + top_y = base_y + 16.0 * 3.0 / 1024.0; + left_x = 512/1024.0 + 32.0 / 1024.0 * round(building.width / 6.0f); + right_x = 512/1024.0; + front_x = 480.0/1024.0; + back_x = 512.0/1024.0; + + // Front + t->push_back( osg::Vec2( right_x, base_y) ); // bottom right + t->push_back( osg::Vec2( left_x, base_y) ); // bottom left + t->push_back( osg::Vec2( left_x, top_y ) ); // top left + t->push_back( osg::Vec2( right_x, top_y ) ); // top right + + // Left + t->push_back( osg::Vec2( front_x, base_y) ); // bottom right + t->push_back( osg::Vec2( back_x, base_y) ); // bottom left + t->push_back( osg::Vec2( back_x, top_y ) ); // top left + t->push_back( osg::Vec2( front_x, top_y ) ); // top right + + // Back (same as front for the moment) + t->push_back( osg::Vec2( right_x, base_y) ); // bottom right + t->push_back( osg::Vec2( left_x, base_y) ); // bottom left + t->push_back( osg::Vec2( left_x, top_y ) ); // top left + t->push_back( osg::Vec2( right_x, top_y ) ); // top right + + // Right (same as left for the moment) + t->push_back( osg::Vec2( front_x, base_y) ); // bottom right + t->push_back( osg::Vec2( back_x, base_y) ); // bottom left + t->push_back( osg::Vec2( back_x, top_y ) ); // top left + t->push_back( osg::Vec2( front_x, top_y ) ); // top right + } else { + // Flat roof + left_x = 640.0/1024.0; + right_x = 512.0/1024.0; + // Use the entire height of the roof texture + top_y = base_y + 16.0 * 3.0 / 1024.0; + + // Flat roofs still have 4 surfaces, so we need to set the textures + for (int i=0; i<4; ++i) { + t->push_back( osg::Vec2( right_x, base_y) ); // bottom right + t->push_back( osg::Vec2( left_x, base_y) ); // bottom left + t->push_back( osg::Vec2( left_x, top_y ) ); // top left + t->push_back( osg::Vec2( right_x, top_y ) ); // top right + } + } - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right } - } - - if (building.type == SGBuildingBin::MEDIUM) - { - int column = ((int) (mt_rand(&seed) * 1000)) % 5; - float base_y = 288 / 1024.0; - float top_y = base_y + 16.0 * (float) building.floors / 1024.0; - float left_x = column * 192.0 /1024.0 + 32.0 / 1024.0 * round((float) building.width / 10.0f); - float right_x = column * 192.0 /1024.0; - - // BASEMENT - uses the baseline texture - for (unsigned int i = 0; i < 16; i++) { - t->push_back( osg::Vec2( left_x, base_y) ); - } - - // MAIN BODY - // Front - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - - // Left - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - - // Back (same as front for the moment) - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - - // Right (same as left for the moment) - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - - // ROOF - if (building.pitched) { - base_y = 288.0/1024.0; - top_y = 576.0/1024.0; - left_x = 960.0/1024.0; - right_x = 1.0; - + if (building.type == SGBuildingBin::MEDIUM) + { + int column = ((int) (mt_rand(&seed) * 1000)) % 5; + float base_y = 288 / 1024.0; + float top_y = base_y + 16.0 * (float) building.floors / 1024.0; + float left_x = column * 192.0 /1024.0 + 32.0 / 1024.0 * round((float) building.width / 10.0f); + float right_x = column * 192.0 /1024.0; + + // BASEMENT - uses the baseline texture + for (unsigned int i = 0; i < 16; i++) { + t->push_back( osg::Vec2( left_x, base_y) ); + } + + // MAIN BODY // Front t->push_back( osg::Vec2( right_x, base_y) ); // bottom right t->push_back( osg::Vec2( left_x, base_y) ); // bottom left @@ -406,7 +491,7 @@ void addBuildingToLeafGeode(Geode* geode, const SGBuildingBin::Building& buildin t->push_back( osg::Vec2( left_x, base_y) ); // bottom left t->push_back( osg::Vec2( left_x, top_y ) ); // top left t->push_back( osg::Vec2( right_x, top_y ) ); // top right - + // Back (same as front for the moment) t->push_back( osg::Vec2( right_x, base_y) ); // bottom right t->push_back( osg::Vec2( left_x, base_y) ); // bottom left @@ -418,62 +503,68 @@ void addBuildingToLeafGeode(Geode* geode, const SGBuildingBin::Building& buildin t->push_back( osg::Vec2( left_x, base_y) ); // bottom left t->push_back( osg::Vec2( left_x, top_y ) ); // top left t->push_back( osg::Vec2( right_x, top_y ) ); // top right - } else { - // Flat roof - base_y = 416/1024.0; - top_y = 576.0/1024.0; - left_x = column * 192.0 /1024.0; - right_x = (column + 1)* 192.0 /1024.0; - - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - } - } - - if (building.type == SGBuildingBin::LARGE) - { - int column = ((int) (mt_rand(&seed) * 1000)) % 8; - float base_y = 576 / 1024.0; - float top_y = base_y + 16.0 * (float) building.floors / 1024.0; - float left_x = column * 128.0 /1024.0 + 32.0 / 1024.0 * round((float) building.width / 20.0f); - float right_x = column * 128.0 /1024.0; - - // BASEMENT - uses the baseline texture - for (unsigned int i = 0; i < 16; i++) { - t->push_back( osg::Vec2( left_x, base_y) ); - } - // MAIN BODY - // Front - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - - // Left - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - - // Back (same as front for the moment) - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - - // Right (same as left for the moment) - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right + // ROOF + if (building.pitched) { + base_y = 288.0/1024.0; + top_y = 576.0/1024.0; + left_x = 960.0/1024.0; + right_x = 1.0; + + // Front + t->push_back( osg::Vec2( right_x, base_y) ); // bottom right + t->push_back( osg::Vec2( left_x, base_y) ); // bottom left + t->push_back( osg::Vec2( left_x, top_y ) ); // top left + t->push_back( osg::Vec2( right_x, top_y ) ); // top right + + // Left + t->push_back( osg::Vec2( right_x, base_y) ); // bottom right + t->push_back( osg::Vec2( left_x, base_y) ); // bottom left + t->push_back( osg::Vec2( left_x, top_y ) ); // top left + t->push_back( osg::Vec2( right_x, top_y ) ); // top right + + // Back (same as front for the moment) + t->push_back( osg::Vec2( right_x, base_y) ); // bottom right + t->push_back( osg::Vec2( left_x, base_y) ); // bottom left + t->push_back( osg::Vec2( left_x, top_y ) ); // top left + t->push_back( osg::Vec2( right_x, top_y ) ); // top right + + // Right (same as left for the moment) + t->push_back( osg::Vec2( right_x, base_y) ); // bottom right + t->push_back( osg::Vec2( left_x, base_y) ); // bottom left + t->push_back( osg::Vec2( left_x, top_y ) ); // top left + t->push_back( osg::Vec2( right_x, top_y ) ); // top right + } else { + // Flat roof + base_y = 416/1024.0; + top_y = 576.0/1024.0; + left_x = column * 192.0 /1024.0; + right_x = (column + 1)* 192.0 /1024.0; + + // Flat roofs still have 4 surfaces + for (int i=0; i<4; ++i) { + t->push_back( osg::Vec2( right_x, base_y) ); // bottom right + t->push_back( osg::Vec2( left_x, base_y) ); // bottom left + t->push_back( osg::Vec2( left_x, top_y ) ); // top left + t->push_back( osg::Vec2( right_x, top_y ) ); // top right + } + } + } - // ROOF - if (building.pitched) { - base_y = 896/1024.0; - top_y = 1.0; + if (building.type == SGBuildingBin::LARGE) + { + int column = ((int) (mt_rand(&seed) * 1000)) % 8; + float base_y = 576 / 1024.0; + float top_y = base_y + 16.0 * (float) building.floors / 1024.0; + float left_x = column * 128.0 /1024.0 + 32.0 / 1024.0 * round((float) building.width / 20.0f); + float right_x = column * 128.0 /1024.0; + + // BASEMENT - uses the baseline texture + for (unsigned int i = 0; i < 16; i++) { + t->push_back( osg::Vec2( left_x, base_y) ); + } + + // MAIN BODY // Front t->push_back( osg::Vec2( right_x, base_y) ); // bottom right t->push_back( osg::Vec2( left_x, base_y) ); // bottom left @@ -485,7 +576,7 @@ void addBuildingToLeafGeode(Geode* geode, const SGBuildingBin::Building& buildin t->push_back( osg::Vec2( left_x, base_y) ); // bottom left t->push_back( osg::Vec2( left_x, top_y ) ); // top left t->push_back( osg::Vec2( right_x, top_y ) ); // top right - + // Back (same as front for the moment) t->push_back( osg::Vec2( right_x, base_y) ); // bottom right t->push_back( osg::Vec2( left_x, base_y) ); // bottom left @@ -497,163 +588,259 @@ void addBuildingToLeafGeode(Geode* geode, const SGBuildingBin::Building& buildin t->push_back( osg::Vec2( left_x, base_y) ); // bottom left t->push_back( osg::Vec2( left_x, top_y ) ); // top left t->push_back( osg::Vec2( right_x, top_y ) ); // top right - } else { - // Flat roof - base_y = 896/1024.0; - top_y = 1.0; - - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - } + // ROOF + if (building.pitched) { + base_y = 896/1024.0; + top_y = 1.0; + // Front + t->push_back( osg::Vec2( right_x, base_y) ); // bottom right + t->push_back( osg::Vec2( left_x, base_y) ); // bottom left + t->push_back( osg::Vec2( left_x, top_y ) ); // top left + t->push_back( osg::Vec2( right_x, top_y ) ); // top right + + // Left + t->push_back( osg::Vec2( right_x, base_y) ); // bottom right + t->push_back( osg::Vec2( left_x, base_y) ); // bottom left + t->push_back( osg::Vec2( left_x, top_y ) ); // top left + t->push_back( osg::Vec2( right_x, top_y ) ); // top right + + // Back (same as front for the moment) + t->push_back( osg::Vec2( right_x, base_y) ); // bottom right + t->push_back( osg::Vec2( left_x, base_y) ); // bottom left + t->push_back( osg::Vec2( left_x, top_y ) ); // top left + t->push_back( osg::Vec2( right_x, top_y ) ); // top right + + // Right (same as left for the moment) + t->push_back( osg::Vec2( right_x, base_y) ); // bottom right + t->push_back( osg::Vec2( left_x, base_y) ); // bottom left + t->push_back( osg::Vec2( left_x, top_y ) ); // top left + t->push_back( osg::Vec2( right_x, top_y ) ); // top right + } else { + // Flat roof + base_y = 896/1024.0; + top_y = 1.0; + + // Flat roofs still have 4 surfaces + for (int i=0; i<4; ++i) { + t->push_back( osg::Vec2( right_x, base_y) ); // bottom right + t->push_back( osg::Vec2( left_x, base_y) ); // bottom left + t->push_back( osg::Vec2( left_x, top_y ) ); // top left + t->push_back( osg::Vec2( right_x, top_y ) ); // top right + } + } + } } - - // Set the vertex, texture and normals back. - geom->setVertexArray(v); - geom->setTexCoordArray(0, t); - geom->setNormalArray(n); - geom->setPrimitiveSet(0, new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,v->size())); - geode->setDrawable(0, geom); -} - -typedef std::map > EffectMap; + // Set the vertex, texture and normals. Colors will be set per-instance + // later. + sharedGeometry->setVertexArray(v); + sharedGeometry->setTexCoordArray(0, t); + sharedGeometry->setNormalArray(n); + } + } + + void SGBuildingBin::insert(SGVec3f p, float r, BuildingType type) { + + if (type == SGBuildingBin::SMALL) { + smallBuildingLocations.push_back(BuildingInstance(p, r, &smallBuildings, smallSharedGeometry)); + } + + if (type == SGBuildingBin::MEDIUM) { + mediumBuildingLocations.push_back(BuildingInstance(p, r, &mediumBuildings, mediumSharedGeometry)); + } -static EffectMap buildingEffectMap; + if (type == SGBuildingBin::LARGE) { + largeBuildingLocations.push_back(BuildingInstance(p, r, &largeBuildings, largeSharedGeometry)); + } + } -// Helper classes for creating the quad tree -namespace -{ -struct MakeBuildingLeaf -{ - MakeBuildingLeaf(float range, Effect* effect) : - _range(range), _effect(effect) {} + int SGBuildingBin::getNumBuildings() { + return smallBuildingLocations.size() + mediumBuildingLocations.size() + largeBuildingLocations.size(); + } + + bool SGBuildingBin::checkMinDist (SGVec3f p, float radius) { + BuildingInstanceList::iterator iter; - MakeBuildingLeaf(const MakeBuildingLeaf& rhs) : - _range(rhs._range), _effect(rhs._effect) - {} - - LOD* operator() () const - { - LOD* result = new LOD; - - // Create a series of LOD nodes so trees cover decreases slightly - // 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))); - } - return result; + float r = (radius + smallBuildingMaxRadius) * (radius + smallBuildingMaxRadius); + for (iter = smallBuildingLocations.begin(); iter != smallBuildingLocations.end(); ++iter) { + if (iter->getDistSqr(p) < r) { + return false; + } } - float _range; - ref_ptr _effect; -}; - -struct AddBuildingLeafObject -{ - void operator() (LOD* lod, const SGBuildingBin::Building& building) const - { - Geode* geode = static_cast(lod->getChild(int(building.position.x() * 10.0f) % lod->getNumChildren())); - addBuildingToLeafGeode(geode, building); + r = (radius + mediumBuildingMaxRadius) * (radius + mediumBuildingMaxRadius); + for (iter = mediumBuildingLocations.begin(); iter != mediumBuildingLocations.end(); ++iter) { + if (iter->getDistSqr(p) < r) { + return false; + } } -}; - -struct GetBuildingCoord -{ - Vec3 operator() (const SGBuildingBin::Building& building) const - { - return toOsg(building.position); + + r = (radius + largeBuildingMaxRadius) * (radius + largeBuildingMaxRadius); + for (iter = largeBuildingLocations.begin(); iter != largeBuildingLocations.end(); ++iter) { + if (iter->getDistSqr(p) < r) { + return false; + } } -}; + + return true; + } + + SGBuildingBin::BuildingType SGBuildingBin::getBuildingType(float roll) { + + if (roll < smallBuildingFraction) { + return SGBuildingBin::SMALL; + } + + if (roll < (smallBuildingFraction + mediumBuildingFraction)) { + return SGBuildingBin::MEDIUM; + } + + return SGBuildingBin::LARGE; + } -typedef QuadTreeBuilder BuildingGeometryQuadtree; -} + float SGBuildingBin::getBuildingMaxRadius(BuildingType type) { + + if (type == SGBuildingBin::SMALL) return smallBuildingMaxRadius; + if (type == SGBuildingBin::MEDIUM) return mediumBuildingMaxRadius; + if (type == SGBuildingBin::LARGE) return largeBuildingMaxRadius; + + return 0; + } + + float SGBuildingBin::getBuildingMaxDepth(BuildingType type) { + + if (type == SGBuildingBin::SMALL) return smallBuildingMaxDepth; + if (type == SGBuildingBin::MEDIUM) return mediumBuildingMaxDepth; + if (type == SGBuildingBin::LARGE) return largeBuildingMaxDepth; + + return 0; + } + + ref_ptr SGBuildingBin::createBuildingsGroup(Matrix transInv, const SGReaderWriterOptions* options) + { + ref_ptr effect; + EffectMap::iterator iter = buildingEffectMap.find(texture); -struct BuildingTransformer -{ - BuildingTransformer(Matrix& mat_) : mat(mat_) {} - SGBuildingBin::Building operator()(const SGBuildingBin::Building& building) const + if ((iter == buildingEffectMap.end())|| + (!iter->second.lock(effect))) { - Vec3 pos = toOsg(building.position); - return SGBuildingBin::Building(toSG(pos * mat), building); + SGPropertyNode_ptr effectProp = new SGPropertyNode; + makeChild(effectProp, "inherits-from")->setStringValue("Effects/building"); + SGPropertyNode* params = makeChild(effectProp, "parameters"); + // Main texture - n=0 + params->getChild("texture", 0, true)->getChild("image", 0, true) + ->setStringValue(texture); + + // Light map - n=3 + params->getChild("texture", 3, true)->getChild("image", 0, true) + ->setStringValue(lightMap); + + effect = makeEffect(effectProp, true, options); + if (iter == buildingEffectMap.end()) + buildingEffectMap.insert(EffectMap::value_type(texture, effect)); + else + iter->second = effect; // update existing, but empty observer + } + + ref_ptr group = new osg::Group(); + + // Now, create a quadbuilding for the buildings. + + BuildingInstanceList locs[] = { smallBuildingLocations, + SGBuildingBin::mediumBuildingLocations, + SGBuildingBin::largeBuildingLocations }; + + for (int i = 0; i < 3; i++) + { + BuildingGeometryQuadtree + quadbuilding(GetBuildingCoord(), AddBuildingLeafObject(), + SG_BUILDING_QUAD_TREE_DEPTH, + MakeBuildingLeaf(20000.0, effect)); + + // Transform building positions from the "geocentric" positions we + // get from the scenery polys into the local Z-up coordinate + // system. + std::vector rotatedBuildings; + rotatedBuildings.reserve(locs[i].size()); + std::transform(locs[i].begin(), locs[i].end(), + std::back_inserter(rotatedBuildings), + BuildingInstanceTransformer(transInv)); + quadbuilding.buildQuadTree(rotatedBuildings.begin(), rotatedBuildings.end()); + + for (size_t i = 0; i < quadbuilding.getRoot()->getNumChildren(); ++i) + group->addChild(quadbuilding.getRoot()->getChild(i)); } - Matrix mat; -}; - - - -// This actually returns a MatrixTransform node. If we rotate the whole -// forest into the local Z-up coordinate system we can reuse the -// primitive building geometry for all the forests of the same type. -osg::Group* createRandomBuildings(SGBuildingBinList buildings, const osg::Matrix& transform, - const SGReaderWriterOptions* options) -{ - Matrix transInv = Matrix::inverse(transform); - static Matrix ident; - // Set up some shared structures. - MatrixTransform* mt = new MatrixTransform(transform); - - SGBuildingBin* bin = NULL; - - BOOST_FOREACH(bin, buildings) - { - numBuildings = numBuildings + bin->getNumBuildings(); - SG_LOG(SG_TERRAIN, SG_DEBUG, "Total random buildings generated: " << numBuildings); - - ref_ptr effect; - EffectMap::iterator iter = buildingEffectMap.find(bin->texture); - - if ((iter == buildingEffectMap.end())|| - (!iter->second.lock(effect))) - { - SGPropertyNode_ptr effectProp = new SGPropertyNode; - makeChild(effectProp, "inherits-from")->setStringValue("Effects/building"); - SGPropertyNode* params = makeChild(effectProp, "parameters"); - // Main texture - n=0 - params->getChild("texture", 0, true)->getChild("image", 0, true) - ->setStringValue(bin->texture); - - // Light map - n=3 - params->getChild("texture", 3, true)->getChild("image", 0, true) - ->setStringValue(bin->lightMap); - - effect = makeEffect(effectProp, true, options); - if (iter == buildingEffectMap.end()) - buildingEffectMap.insert(EffectMap::value_type(bin->texture, effect)); - else - iter->second = effect; // update existing, but empty observer - } - // Now, create a quadbuilding for the buildings. - BuildingGeometryQuadtree - quadbuilding(GetBuildingCoord(), AddBuildingLeafObject(), - SG_BUILDING_QUAD_TREE_DEPTH, - MakeBuildingLeaf(20000.0f, effect)); // FIXME - tie to property - - // Transform building positions from the "geocentric" positions we - // get from the scenery polys into the local Z-up coordinate - // system. - std::vector rotatedBuildings; - rotatedBuildings.reserve(bin->buildings.size()); - std::transform(bin->buildings.begin(), bin->buildings.end(), - std::back_inserter(rotatedBuildings), - BuildingTransformer(transInv)); - quadbuilding.buildQuadTree(rotatedBuildings.begin(), rotatedBuildings.end()); - - ref_ptr group = quadbuilding.getRoot(); - mt->addChild(group); - delete bin; - } + return group; + } - buildings.clear(); + // We may end up with a quadtree with many empty leaves. One might say + // that we should avoid constructing the leaves in the first place, + // but this node visitor tries to clean up after the fact. + struct QuadTreeCleaner : public osg::NodeVisitor + { + QuadTreeCleaner() : NodeVisitor(NodeVisitor::TRAVERSE_ALL_CHILDREN) + { + } + void apply(LOD& lod) + { + for (int i = lod.getNumChildren() - 1; i >= 0; --i) { + EffectGeode* geode = dynamic_cast(lod.getChild(i)); + if (!geode) + continue; + bool geodeEmpty = true; + if (geode->getNumDrawables() > 1) { + SG_LOG(SG_TERRAIN, SG_DEBUG, "Building LOD Drawables: " << geode->getNumDrawables()); + } + + for (unsigned j = 0; j < geode->getNumDrawables(); ++j) { + const Geometry* geom = dynamic_cast(geode->getDrawable(j)); + if (!geom) { + geodeEmpty = false; + break; + } + for (unsigned k = 0; k < geom->getNumPrimitiveSets(); k++) { + const PrimitiveSet* ps = geom->getPrimitiveSet(k); + if (ps->getNumIndices() > 0) { + geodeEmpty = false; + break; + } + } + } + if (geodeEmpty) + lod.removeChildren(i, 1); + } + } + }; - return mt; -} - + // This actually returns a MatrixTransform node. If we rotate the whole + // forest into the local Z-up coordinate system we can reuse the + // primitive building geometry for all the forests of the same type. + osg::Group* createRandomBuildings(SGBuildingBinList buildings, const osg::Matrix& transform, + const SGReaderWriterOptions* options) + { + Matrix transInv = Matrix::inverse(transform); + static Matrix ident; + // Set up some shared structures. + MatrixTransform* mt = new MatrixTransform(transform); + + SGBuildingBin* bin = NULL; + + BOOST_FOREACH(bin, buildings) + { + numBuildings = numBuildings + bin->getNumBuildings(); + ref_ptr group = bin->createBuildingsGroup(transInv, options); + + for (size_t i = 0; i < group->getNumChildren(); ++i) + mt->addChild(group->getChild(i)); + + delete bin; + } + + buildings.clear(); + QuadTreeCleaner cleaner; + mt->accept(cleaner); + return mt; + } } diff --git a/simgear/scene/tgdb/SGBuildingBin.hxx b/simgear/scene/tgdb/SGBuildingBin.hxx index 1fc64cbd..a6d01f34 100644 --- a/simgear/scene/tgdb/SGBuildingBin.hxx +++ b/simgear/scene/tgdb/SGBuildingBin.hxx @@ -27,53 +27,65 @@ #include #include +#include #include -#include +#include +#include #include +#include +#include +#include + #include +#include + +#include +#include +#include +#include + +#define SG_BUILDING_QUAD_TREE_DEPTH 2 +#define SG_BUILDING_FADE_OUT_LEVELS 4 + +using namespace osg; namespace simgear { 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 }; + +private: struct Building { - Building(BuildingType t, const SGVec3f& p, float w, float d, float h, int f, float rot, bool pitch) : + Building(BuildingType t, float w, float d, float h, int f, bool pitch) : type(t), - position(p), width(w), depth(d), height(h), floors(f), - rotation(rot), pitched(pitch), radius(std::max(d, 0.5f*w)) { } - Building(const SGVec3f& p, Building b) : - type(b.type), - position(p), - width(b.width), - depth(b.depth), - height(b.height), - floors(b.floors), - rotation(b.rotation), - pitched(b.pitched), - radius(std::max(b.depth, 0.5f*b.width)) - { } BuildingType type; - SGVec3f position; float width; float depth; float height; int floors; - float rotation; bool pitched; float radius; @@ -82,33 +94,214 @@ public: } }; + // The set of buildings that are instantiated typedef std::vector BuildingList; - BuildingList buildings; + BuildingList smallBuildings; + BuildingList mediumBuildings; + BuildingList largeBuildings; + std::string material_name; std::string texture; std::string lightMap; + + // Fraction of buildings of this type + float smallBuildingFraction; + float mediumBuildingFraction; - void insert(const Building& model) - { - buildings.push_back(model); - } + // 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; - void insert(BuildingType t, const SGVec3f& p, float w, float d, float h, int f, float rot, bool pitch) - { insert(Building(t, p, w, d, h, f, rot, pitch)); } + // Shared geometries of the building set + ref_ptr smallSharedGeometry; + ref_ptr mediumSharedGeometry; + ref_ptr largeSharedGeometry; + + struct BuildingInstance { + BuildingInstance(SGVec3f p, float r, const BuildingList* bl, ref_ptr 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 sharedGeometry; + + SGVec3f getPosition() { return position; } + float getRotation() { return rotation; } + + float getDistSqr(SGVec3f p) { + return distSqr(p, position); + } - unsigned getNumBuildings() const - { return buildings.size(); } - const Building& getBuilding(unsigned i) const - { return buildings[i]; } + const osg::Vec4f getColorValue() { + return osg::Vec4f(toOsg(position), rotation); + } + }; + + // Information for an instance of a building - position and orientation + typedef std::vector BuildingInstanceList; + BuildingInstanceList smallBuildingLocations; + BuildingInstanceList mediumBuildingLocations; + BuildingInstanceList largeBuildingLocations; + +public: + + SGBuildingBin(const SGMaterial *mat); ~SGBuildingBin() { - buildings.clear(); + smallBuildings.clear(); + mediumBuildings.clear(); + largeBuildings.clear(); + smallBuildingLocations.clear(); + mediumBuildingLocations.clear(); + largeBuildingLocations.clear(); } + + void insert(SGVec3f p, float r, BuildingType type); + int getNumBuildings(); + + bool checkMinDist (SGVec3f p, float radius); + + 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) : + _range(range), _effect(effect) {} + + MakeBuildingLeaf(const MakeBuildingLeaf& rhs) : + _range(rhs._range), _effect(rhs._effect) + {} + + LOD* operator() () const + { + LOD* result = new LOD; + + // Create a series of LOD nodes so trees cover decreases slightly + // 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))); + } + return result; + } + + float _range; + ref_ptr _effect; + }; + + struct AddBuildingLeafObject + { + Geometry* createNewBuildingGeometryInstance(const BuildingInstance& building) const + { + Geometry* geom = simgear::clone(building.sharedGeometry.get(), CopyOp::SHALLOW_COPY); + geom->setColorArray(new Vec4Array); + geom->setColorBinding(Geometry::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(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(geode->getDrawable(numDrawables - 1)); + } + + // Check if this building is too close to any other others. + DrawArrays* primSet = static_cast(geom->getPrimitiveSet(0)); + Vec4Array* posArray = static_cast(geom->getColorArray()); + + // Now check if this geometry is full. + if (posArray->size() >= static_cast(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(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(geom->getPrimitiveSet(0)); + primSet->setCount(numVerts); + } + }; + + struct GetBuildingCoord + { + Vec3 operator() (const BuildingInstance& building) const + { + return toOsg(building.position); + } + }; + + typedef QuadTreeBuilder 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 createBuildingsGroup(Matrix transInv, const SGReaderWriterOptions* options); + }; // List of buildings typedef std::list SGBuildingBinList; + osg::Group* createRandomBuildings(SGBuildingBinList buildinglist, const osg::Matrix& transform, const SGReaderWriterOptions* options); } diff --git a/simgear/scene/tgdb/obj.cxx b/simgear/scene/tgdb/obj.cxx index 3699b556..418ba2c3 100644 --- a/simgear/scene/tgdb/obj.cxx +++ b/simgear/scene/tgdb/obj.cxx @@ -457,7 +457,11 @@ struct SGTileGeometryBin { } } - void computeRandomBuildings(SGMaterialLib* matlib, float building_density) + void computeRandomObjectsAndBuildings( + SGMaterialLib* matlib, + float building_density, + bool use_random_objects, + bool use_random_buildings) { SGMaterialTriangleMap::iterator i; @@ -474,45 +478,34 @@ struct SGTileGeometryBin { osg::Texture2D* object_mask = mat->get_object_mask(triangleBin); - float coverage = mat->get_building_coverage(); + int group_count = mat->get_object_group_count(); + float building_coverage = mat->get_building_coverage(); - // Minimum spacing needs to include the maximum footprint of a building. - // As the 0,0,0 point is the center of the front of the building, we need - // to consider the full depth, but only half the possible width. - float min_spacing = mat->get_building_spacing(); - - if (coverage <= 0) - continue; - bool found = false; SGBuildingBin* bin = NULL; - BOOST_FOREACH(bin, randomBuildings) - { - if (bin->texture == mat->get_building_texture()) { - found = true; - break; + if (building_coverage > 0) { + BOOST_FOREACH(bin, randomBuildings) + { + if (bin->getMaterialName() == mat->get_names()[0]) { + found = true; + break; + } } - } - - if (!found) { - bin = new SGBuildingBin(); - bin->texture = mat->get_building_texture(); - bin->lightMap = mat->get_building_lightmap(); - SG_LOG(SG_INPUT, SG_DEBUG, "Building texture " << bin->texture); - randomBuildings.push_back(bin); - } - - std::vector > randomPoints; + + if (!found) { + bin = new SGBuildingBin(mat); + randomBuildings.push_back(bin); + } + } unsigned num = i->second.getNumTriangles(); - int triangle_dropped = 0; - int building_dropped = 0; int random_dropped = 0; int mask_dropped = 0; + int building_dropped = 0; + int triangle_dropped = 0; for (unsigned i = 0; i < num; ++i) { - SGBuildingBin::BuildingList triangle_buildings; SGTexturedTriangleBin::triangle_ref triangleRef = triangleBin.getTriangleRef(i); SGVec3f vorigin = triangleBin.getVertex(triangleRef[0]).vertex; @@ -523,227 +516,256 @@ struct SGTileGeometryBin { SGVec2f t1 = triangleBin.getVertex(triangleRef[2]).texCoord - torigin; SGVec3f normal = cross(v0, v1); + // Containers to hold the random buildings and objects generated + // for this triangle for collision detection purposes. + std::vector< std::pair< SGVec3f, float> > triangleObjectsList; + std::vector< std::pair< SGVec3f, float> > triangleBuildingList; + // Compute the area float area = 0.5f*length(normal); if (area <= SGLimitsf::min()) continue; - // for partial units of area, use a zombie door method to - // create the proper random chance of an object being created - // for this triangle. - double num = area / coverage + mt_rand(&seed); - if (num < 1.0f) { - continue; - } - - // Apply density, which is linear, while we're dealing in areas - num = num * building_density * building_density; - - // Cosine of the angle between the two vectors. - float cosine = (dot(v0, v1) / (length(v0) * length(v1))); - - // Determine a grid spacing in each vector such that the correct - // coverage will result. - float stepv0 = (sqrtf(coverage) / building_density) / length(v0) / sqrtf(1 - cosine * cosine); - float stepv1 = (sqrtf(coverage) / building_density) / length(v1); - - stepv0 = std::min(stepv0, 1.0f); - stepv1 = std::min(stepv1, 1.0f); - - // Start at a random point. a will be immediately incremented below. - float a = -mt_rand(&seed) * stepv0; - float b = mt_rand(&seed) * stepv1; + // Generate any random objects + if (use_random_objects && (group_count > 0)) + { + for (int j = 0; j < group_count; j++) + { + SGMatModelGroup *object_group = mat->get_object_group(j); + int nObjects = object_group->get_object_count(); + + if (nObjects == 0) continue; + + // For each of the random models in the group, determine an appropriate + // number of random placements and insert them. + for (int k = 0; k < nObjects; k++) { + SGMatModel * object = object_group->get_object(k); + + // Use the zombie door method to determine fractional object placement. + double n = area / object->get_coverage_m2() + mt_rand(&seed); + + // place an object each unit of area + while ( n > 1.0 ) { + float a = mt_rand(&seed); + float b = mt_rand(&seed); + if ( a + b > 1 ) { + a = 1 - a; + b = 1 - b; + } - // Place an object each unit of area - while (num > 1.0) { + SGVec3f randomPoint = vorigin + a*v0 + b*v1; + float rotation = static_cast(mt_rand(&seed)); + + // Check that the point is sufficiently far from + // the edge of the triangle by measuring the distance + // from the three lines that make up the triangle. + float spacing = object->get_spacing_m(); + + SGVec3f p = randomPoint - vorigin; + float edges[] = { length(cross(p , p - v0)) / length(v0), + length(cross(p - v0, p - v1)) / length(v1 - v0), + length(cross(p - v1, p )) / length(v1) }; + float edge_dist = *std::min_element(edges, edges + 3); + + if (edge_dist < spacing) { + n -= 1.0; + continue; + } + + if (object_mask != NULL) { + SGVec2f texCoord = torigin + a*t0 + b*t1; + + // Check this random point against the object mask + // blue (for buildings) channel. + osg::Image* img = object_mask->getImage(); + unsigned int x = (int) (img->s() * texCoord.x()) % img->s(); + unsigned int y = (int) (img->t() * texCoord.y()) % img->t(); + + if (mt_rand(&seed) > img->getColor(x, y).b()) { + // Failed object mask check + n -= 1.0; + continue; + } + + rotation = img->getColor(x,y).r(); + } + + bool close = false; - // Set the next location to place a building - a += stepv0; - - if ((a + b) > 1.0f) { - // Reached the end of the scan-line on v0. Reset and increment - // scan-line on v1 - a = mt_rand(&seed) * stepv0; - b += stepv1; - } - - if (b > 1.0f) { - // In a degenerate case of a single point, we might be outside the - // scanline. Note that we need to still ensure that a+b < 1. - b = mt_rand(&seed) * stepv1 * (1.0f - a); + // Check it isn't too close to any other random objects in the triangle + std::vector >::iterator l; + for (l = triangleObjectsList.begin(); l != triangleObjectsList.end(); ++l) { + float min_dist2 = (l->second + object->get_spacing_m()) * + (l->second + object->get_spacing_m()); + + if (distSqr(l->first, randomPoint) > min_dist2) { + close = true; + continue; + } + } + + if (!close) { + triangleObjectsList.push_back(std::make_pair(randomPoint, object->get_spacing_m())); + randomModels.insert(randomPoint, + object, + (int)object->get_randomized_range_m(&seed), + rotation); + } + + n -= 1.0; + } + } } - - if ((a + b) > 1.0f ) { - // Truly degenerate case - simply choose a random point guaranteed - // to fulfil the constraing of a+b < 1. - a = mt_rand(&seed); - b = mt_rand(&seed) * (1.0f - a); + } + + // Random objects now generated. Now generate the random buildings (if any); + if (use_random_buildings && (building_coverage > 0)) { + // For partial units of area, use a zombie door method to + // create the proper random chance of an object being created + // for this triangle. + double num = area / building_coverage + mt_rand(&seed); + if (num < 1.0f) { + continue; } + + // Apply density, which is linear, while we're dealing in areas + num = num * building_density * building_density; - SGVec3f randomPoint = vorigin + a*v0 + b*v1; - float rotation = mt_rand(&seed); - - if (object_mask != NULL) { - SGVec2f texCoord = torigin + a*t0 + b*t1; - osg::Image* img = object_mask->getImage(); - int x = (int) (img->s() * texCoord.x()) % img->s(); - int y = (int) (img->t() * texCoord.y()) % img->t(); - - // In some degenerate cases x or y can be < 1, in which case the mod operand fails - while (x < 0) x += img->s(); - while (y < 0) y += img->t(); - - if (mt_rand(&seed) < img->getColor(x, y).b()) { - // Object passes mask. Rotation is taken from the red channel - rotation = img->getColor(x,y).r(); - } else { - // Fails mask test - try again. - mask_dropped++; - num -= 1.0; - continue; - } - } + // Cosine of the angle between the two vectors. + float cosine = (dot(v0, v1) / (length(v0) * length(v1))); + + // Determine a grid spacing in each vector such that the correct + // coverage will result. + float stepv0 = (sqrtf(building_coverage) / building_density) / length(v0) / sqrtf(1 - cosine * cosine); + float stepv1 = (sqrtf(building_coverage) / building_density) / length(v1); - // Now create the building, so we have an idea of its footprint - // and therefore appropriate spacing. - SGBuildingBin::BuildingType buildingtype; - float width; - float depth; - int floors; - float height; - bool pitched; - - // Determine the building type, and hence dimensions. - float type = mt_rand(&seed); + stepv0 = std::min(stepv0, 1.0f); + stepv1 = std::min(stepv1, 1.0f); - if (type < mat->get_building_small_fraction()) { - // Small building - buildingtype = SGBuildingBin::SMALL; - width = mat->get_building_small_min_width() + mt_rand(&seed) * mt_rand(&seed) * (mat->get_building_small_max_width() - mat->get_building_small_min_width()); - depth = mat->get_building_small_min_depth() + mt_rand(&seed) * mt_rand(&seed) * (mat->get_building_small_max_depth() - mat->get_building_small_min_depth()); - floors = SGMisc::round(mat->get_building_small_min_floors() + mt_rand(&seed) * (mat->get_building_small_max_floors() - mat->get_building_small_min_floors())); - height = floors * (2.8 + mt_rand(&seed)); + // Start at a random point. a will be immediately incremented below. + float a = -mt_rand(&seed) * stepv0; + float b = mt_rand(&seed) * stepv1; + + // Place an object each unit of area + while (num > 1.0) { + + // Set the next location to place a building + a += stepv0; - // Small buildings are never deeper than they are wide. - if (depth > width) { depth = width; } + if ((a + b) > 1.0f) { + // Reached the end of the scan-line on v0. Reset and increment + // scan-line on v1 + a = mt_rand(&seed) * stepv0; + b += stepv1; + } - pitched = (mt_rand(&seed) < mat->get_building_small_pitch()); - } else if (type < (mat->get_building_small_fraction() + mat->get_building_medium_fraction())) { - buildingtype = SGBuildingBin::MEDIUM; - width = mat->get_building_medium_min_width() + mt_rand(&seed) * mt_rand(&seed) * (mat->get_building_medium_max_width() - mat->get_building_medium_min_width()); - depth = mat->get_building_medium_min_depth() + mt_rand(&seed) * mt_rand(&seed) * (mat->get_building_medium_max_depth() - mat->get_building_medium_min_depth()); - floors = SGMisc::round(mat->get_building_medium_min_floors() + mt_rand(&seed) * (mat->get_building_medium_max_floors() - mat->get_building_medium_min_floors())); - height = floors * (2.8 + mt_rand(&seed)); + if (b > 1.0f) { + // In a degenerate case of a single point, we might be outside the + // scanline. Note that we need to still ensure that a+b < 1. + b = mt_rand(&seed) * stepv1 * (1.0f - a); + } - while ((height > width) && (floors > mat->get_building_medium_min_floors())) { - // Ensure that medium buildings aren't taller than they are wide - floors--; - height = floors * (2.8 + mt_rand(&seed)); + if ((a + b) > 1.0f ) { + // Truly degenerate case - simply choose a random point guaranteed + // to fulfil the constraing of a+b < 1. + a = mt_rand(&seed); + b = mt_rand(&seed) * (1.0f - a); } - pitched = (mt_rand(&seed) < mat->get_building_medium_pitch()); - } else { - buildingtype = SGBuildingBin::LARGE; - width = mat->get_building_large_min_width() + mt_rand(&seed) * (mat->get_building_large_max_width() - mat->get_building_large_min_width()); - depth = mat->get_building_large_min_depth() + mt_rand(&seed) * (mat->get_building_large_max_depth() - mat->get_building_large_min_depth()); - floors = SGMisc::round(mat->get_building_large_min_floors() + mt_rand(&seed) * (mat->get_building_large_max_floors() - mat->get_building_large_min_floors())); - height = floors * (2.8 + mt_rand(&seed)); - pitched = (mt_rand(&seed) < mat->get_building_large_pitch()); - } - - // Determine an appropriate minimum spacing for the object. Note that the - // origin of the building model is the center of the front face, hence we - // consider the full depth. We choose _not_ to use the diagonal distance - // to one of the rear corners, as we assume that terrain masking will - // make the buildings place in some sort of grid. - float radius = std::max(depth, 0.5f*width); - - // Check that the point is sufficiently far from - // the edge of the triangle by measuring the distance - // from the three lines that make up the triangle. - SGVec3f p = randomPoint - vorigin; - - if (((length(cross(p , p - v0)) / length(v0)) < radius) || - ((length(cross(p - v0, p - v1)) / length(v1 - v0)) < radius) || - ((length(cross(p - v1, p )) / length(v1)) < radius) ) - { - triangle_dropped++; - num -= 1.0; - continue; - } + SGVec3f randomPoint = vorigin + a*v0 + b*v1; + float rotation = mt_rand(&seed); + + if (object_mask != NULL) { + SGVec2f texCoord = torigin + a*t0 + b*t1; + osg::Image* img = object_mask->getImage(); + int x = (int) (img->s() * texCoord.x()) % img->s(); + int y = (int) (img->t() * texCoord.y()) % img->t(); + + // In some degenerate cases x or y can be < 1, in which case the mod operand fails + while (x < 0) x += img->s(); + while (y < 0) y += img->t(); + + if (mt_rand(&seed) < img->getColor(x, y).b()) { + // Object passes mask. Rotation is taken from the red channel + rotation = img->getColor(x,y).r(); + } else { + // Fails mask test - try again. + mask_dropped++; + num -= 1.0; + continue; + } + } - // Check against the generic random objects. TODO - make this more efficient by - // masking ahead of time objects outside of the triangle. - bool too_close = false; - for (unsigned int i = 0; i < randomModels.getNumModels(); ++i) { - float min_dist = randomModels.getMatModel(i).model->get_spacing_m() + radius + min_spacing; - min_dist = min_dist * min_dist; + // Check building isn't too close to the triangle edge. + float type_roll = mt_rand(&seed); + SGBuildingBin::BuildingType buildingtype = bin->getBuildingType(type_roll); + float radius = bin->getBuildingMaxRadius(buildingtype); + + // Determine the actual center of the building, by shifting from the + // center of the front face to the true center. + osg::Matrix rotationMat = osg::Matrix::rotate(- rotation * M_PI * 2, + osg::Vec3f(0.0, 0.0, 1.0)); + SGVec3f buildingCenter = randomPoint + toSG(osg::Vec3f(-0.5 * bin->getBuildingMaxDepth(buildingtype), 0.0, 0.0) * rotationMat); + + SGVec3f p = buildingCenter - vorigin; + float edges[] = { length(cross(p , p - v0)) / length(v0), + length(cross(p - v0, p - v1)) / length(v1 - v0), + length(cross(p - v1, p )) / length(v1) }; + float edge_dist = *std::min_element(edges, edges + 3); - if (distSqr(randomModels.getMatModel(i).position, randomPoint) < min_dist) { - too_close = true; - random_dropped++; - continue; - } - } - - if (too_close) { - // Too close to a random model - drop and try again - num -= 1.0; - continue; - } - - SGBuildingBin::BuildingList::iterator l; - - // Check that the building is sufficiently far from any other building within the triangle. - for (l = triangle_buildings.begin(); l != triangle_buildings.end(); ++l) { + if (edge_dist < radius) { + num -= 1.0; + triangle_dropped++; + continue; + } + + // Check building isn't too close to random objects and other buildings. + bool close = false; + std::vector >::iterator iter; - float min_dist = l->radius + radius + min_spacing; - min_dist = min_dist * min_dist; + for (iter = triangleBuildingList.begin(); iter != triangleBuildingList.end(); ++iter) { + float min_dist = iter->second + radius; + if (distSqr(iter->first, buildingCenter) < min_dist * min_dist) { + close = true; + continue; + } + } - if (distSqr(randomPoint, l->position) < min_dist) { + if (close) { + num -= 1.0; building_dropped++; - too_close = true; - continue; + continue; } - } - - if (too_close) { - // Too close to another building - drop and try again + + for (iter = triangleObjectsList.begin(); iter != triangleObjectsList.end(); ++iter) { + float min_dist = iter->second + radius; + if (distSqr(iter->first, buildingCenter) < min_dist * min_dist) { + close = true; + continue; + } + } + + if (close) { + num -= 1.0; + random_dropped++; + continue; + } + + std::pair pt = std::make_pair(buildingCenter, radius); + triangleBuildingList.push_back(pt); + bin->insert(randomPoint, rotation, buildingtype); num -= 1.0; - continue; } - - // If we've passed all of the above tests we have a valid - // building, so create it! - SGBuildingBin::Building building = - SGBuildingBin::Building(buildingtype, - randomPoint, - width, - depth, - height, - floors, - rotation, - pitched); - triangle_buildings.push_back(building); - - num -= 1.0; - } - - // Add the buildings from this triangle to the overall list. - SGBuildingBin::BuildingList::iterator l; - - for (l = triangle_buildings.begin(); l != triangle_buildings.end(); ++l) { - bin->insert(*l); } - triangle_buildings.clear(); + triangleObjectsList.clear(); + triangleBuildingList.clear(); } SG_LOG(SG_TERRAIN, SG_DEBUG, "Random Buildings: " << bin->getNumBuildings()); SG_LOG(SG_TERRAIN, SG_DEBUG, " Dropped due to mask: " << mask_dropped); - SG_LOG(SG_TERRAIN, SG_DEBUG, " Dropped due to triangle edge: " << triangle_dropped); SG_LOG(SG_TERRAIN, SG_DEBUG, " Dropped due to random object: " << random_dropped); - SG_LOG(SG_TERRAIN, SG_DEBUG, " Dropped due to other building: " << building_dropped); + SG_LOG(SG_TERRAIN, SG_DEBUG, " Dropped due to other buildings: " << building_dropped); } } @@ -805,69 +827,6 @@ struct SGTileGeometryBin { } } - void computeRandomObjects(SGMaterialLib* matlib) - { - SGMaterialTriangleMap::iterator i; - - // generate a repeatable random seed - mt seed; - mt_init(&seed, unsigned(123)); - - for (i = materialTriangleMap.begin(); i != materialTriangleMap.end(); ++i) { - SGMaterial *mat = matlib->find(i->first); - if (!mat) - continue; - - int group_count = mat->get_object_group_count(); - - if (group_count > 0) - { - for (int j = 0; j < group_count; j++) - { - SGMatModelGroup *object_group = mat->get_object_group(j); - int nObjects = object_group->get_object_count(); - - if (nObjects > 0) - { - // For each of the random models in the group, determine an appropriate - // number of random placements and insert them. - for (int k = 0; k < nObjects; k++) { - SGMatModel * object = object_group->get_object(k); - - std::vector > randomPoints; - - i->second.addRandomPoints(object->get_coverage_m2(), - object->get_spacing_m(), - mat->get_object_mask(i->second), - randomPoints); - - std::vector >::iterator l; - for (l = randomPoints.begin(); l != randomPoints.end(); ++l) { - // Only add the model if it is sufficiently far from the - // other models - bool close = false; - - for (unsigned i = 0; i < randomModels.getNumModels(); i++) { - float spacing = randomModels.getMatModel(i).model->get_spacing_m() + object->get_spacing_m(); - spacing = spacing * spacing; - - if (distSqr(randomModels.getMatModel(i).position, l->first) < spacing) { - close = true; - continue; - } - } - - if (!close) { - randomModels.insert(l->first, object, (int)object->get_randomized_range_m(&seed), l->second); - } - } - } - } - } - } - } - } - bool insertBinObj(const SGBinObject& obj, SGMaterialLib* matlib) { if (!insertPtGeometry(obj, matlib)) @@ -969,54 +928,63 @@ SGLoadBTG(const std::string& path, const simgear::SGReaderWriterOptions* options osg::Node* node = tileGeometryBin.getSurfaceGeometry(matlib); if (node) terrainGroup->addChild(node); + + if (matlib && (use_random_objects || use_random_buildings)) { + tileGeometryBin.computeRandomObjectsAndBuildings(matlib, + building_density, + use_random_objects, + use_random_buildings); + } - if (use_random_objects && matlib) { - tileGeometryBin.computeRandomObjects(matlib); - - if (tileGeometryBin.randomModels.getNumModels() > 0) { - // Generate a repeatable random seed - mt seed; - mt_init(&seed, unsigned(123)); + if (tileGeometryBin.randomModels.getNumModels() > 0) { + // Generate a repeatable random seed + mt seed; + mt_init(&seed, unsigned(123)); - std::vector models; - for (unsigned int i = 0; - i < tileGeometryBin.randomModels.getNumModels(); i++) { - SGMatModelBin::MatModel obj - = tileGeometryBin.randomModels.getMatModel(i); + std::vector models; + for (unsigned int i = 0; + i < tileGeometryBin.randomModels.getNumModels(); i++) { + SGMatModelBin::MatModel obj + = tileGeometryBin.randomModels.getMatModel(i); - SGPropertyNode* root = options->getPropertyNode()->getRootNode(); - osg::Node* node = obj.model->get_random_model(root, &seed); - - // Create a matrix to place the object in the correct - // location, and then apply the rotation matrix created - // above, with an additional random (or taken from - // the object mask) heading rotation if appropriate. - osg::Matrix transformMat; - transformMat = osg::Matrix::translate(toOsg(obj.position)); - if (obj.model->get_heading_type() == SGMatModel::HEADING_RANDOM) { - // Rotate the object around the z axis. - double hdg = mt_rand(&seed) * M_PI * 2; - transformMat.preMult(osg::Matrix::rotate(hdg, - osg::Vec3d(0.0, 0.0, 1.0))); - } + SGPropertyNode* root = options->getPropertyNode()->getRootNode(); + osg::Node* node = obj.model->get_random_model(root, &seed); + + // Create a matrix to place the object in the correct + // location, and then apply the rotation matrix created + // above, with an additional random (or taken from + // the object mask) heading rotation if appropriate. + osg::Matrix transformMat; + transformMat = osg::Matrix::translate(toOsg(obj.position)); + if (obj.model->get_heading_type() == SGMatModel::HEADING_RANDOM) { + // Rotate the object around the z axis. + double hdg = mt_rand(&seed) * M_PI * 2; + transformMat.preMult(osg::Matrix::rotate(hdg, + osg::Vec3d(0.0, 0.0, 1.0))); + } - if (obj.model->get_heading_type() == SGMatModel::HEADING_MASK) { - // Rotate the object around the z axis. - double hdg = - obj.rotation * M_PI * 2; - transformMat.preMult(osg::Matrix::rotate(hdg, - osg::Vec3d(0.0, 0.0, 1.0))); - } - - osg::MatrixTransform* position = - new osg::MatrixTransform(transformMat); - position->addChild(node); - models.push_back(ModelLOD(position, obj.lod)); + if (obj.model->get_heading_type() == SGMatModel::HEADING_MASK) { + // Rotate the object around the z axis. + double hdg = - obj.rotation * M_PI * 2; + transformMat.preMult(osg::Matrix::rotate(hdg, + osg::Vec3d(0.0, 0.0, 1.0))); } - RandomObjectsQuadtree quadtree((GetModelLODCoord()), (AddModelLOD())); - quadtree.buildQuadTree(models.begin(), models.end()); - randomObjects = quadtree.getRoot(); - randomObjects->setName("random objects"); + + osg::MatrixTransform* position = + new osg::MatrixTransform(transformMat); + position->addChild(node); + models.push_back(ModelLOD(position, obj.lod)); } + RandomObjectsQuadtree quadtree((GetModelLODCoord()), (AddModelLOD())); + quadtree.buildQuadTree(models.begin(), models.end()); + randomObjects = quadtree.getRoot(); + randomObjects->setName("Random objects"); + } + + if (tileGeometryBin.randomBuildings.size() > 0) { + buildingNode = createRandomBuildings(tileGeometryBin.randomBuildings, osg::Matrix::identity(), + options); + buildingNode->setName("Random buildings"); } if (use_random_vegetation && matlib) { @@ -1028,15 +996,6 @@ SGLoadBTG(const std::string& path, const simgear::SGReaderWriterOptions* options options); forestNode->setName("Random trees"); } - } - - if (use_random_buildings && matlib) { - tileGeometryBin.computeRandomBuildings(matlib, building_density); - if (tileGeometryBin.randomBuildings.size() > 0) { - buildingNode = createRandomBuildings(tileGeometryBin.randomBuildings, osg::Matrix::identity(), - options); - buildingNode->setName("Random buildings"); - } } // FIXME: ugly, has a side effect -- 2.39.5