1 // obj.cxx -- routines to handle loading scenery and building the plib 2 // scene graph. 3 // 4 // Written by Curtis Olson, started October 1997. 5 // 6 // Copyright (C) 1997 Curtis L. Olson - http://www.flightgear.org/~curt 7 // 8 // This program is free software; you can redistribute it and/or 9 // modify it under the terms of the GNU General Public License as 10 // published by the Free Software Foundation; either version 2 of the 11 // License, or (at your option) any later version. 12 // 13 // This program is distributed in the hope that it will be useful, but 14 // WITHOUT ANY WARRANTY; without even the implied warranty of 15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 // General Public License for more details. 17 // 18 // You should have received a copy of the GNU General Public License 19 // along with this program; if not, write to the Free Software 20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 // 22 // $Id$ 23 24 25 #ifdef HAVE_CONFIG_H 26 # include <simgear_config.h> 27 #endif 28 29 #include <osg/LOD> 30 #include <osgUtil/Simplifier> 31 32 #include <boost/foreach.hpp> 33 34 #include <simgear/scene/material/matmodel.hxx> 35 #include <simgear/scene/model/SGOffsetTransform.hxx> 36 #include <simgear/scene/util/QuadTreeBuilder.hxx> 37 #include <simgear/scene/util/SGReaderWriterOptions.hxx> 38 #include <simgear/scene/util/OptionsReadFileCallback.hxx> 39 #include <simgear/scene/util/SGNodeMasks.hxx> 40 #include <simgear/debug/ErrorReportingCallback.hxx> 41 42 #include "SGNodeTriangles.hxx" 43 #include "GroundLightManager.hxx" 44 #include "SGLightBin.hxx" 45 #include "SGDirectionalLightBin.hxx" 46 #include "SGModelBin.hxx" 47 #include "SGBuildingBin.hxx" 48 #include "TreeBin.hxx" 49 50 #include "pt_lights.hxx" 51 52 53 typedef std::list<SGLightBin> SGLightListBin; 54 typedef std::list<SGDirectionalLightBin> SGDirectionalLightListBin; 55 56 #define SG_SIMPLIFIER_RATIO (0.001) 57 #define SG_SIMPLIFIER_MAX_LENGTH (1000.0) 58 #define SG_SIMPLIFIER_MAX_ERROR (2000.0) 59 60 using namespace simgear; 61 62 using ReadResult = osgDB::ReaderWriter::ReadResult; 63 64 // QuadTreeBuilder is used by Random Objects Generator 65 typedef std::pair<osg::Node*, int> ModelLOD; 66 struct MakeQuadLeaf { operator ()MakeQuadLeaf67 osg::LOD* operator() () const { return new osg::LOD; } 68 }; 69 struct AddModelLOD { operator ()AddModelLOD70 void operator() (osg::LOD* leaf, ModelLOD& mlod) const 71 { 72 leaf->addChild(mlod.first, 0, mlod.second); 73 } 74 }; 75 struct GetModelLODCoord { GetModelLODCoordGetModelLODCoord76 GetModelLODCoord() {} GetModelLODCoordGetModelLODCoord77 GetModelLODCoord(const GetModelLODCoord& rhs) 78 {} operator ()GetModelLODCoord79 osg::Vec3 operator() (const ModelLOD& mlod) const 80 { 81 return mlod.first->getBound().center(); 82 } 83 }; 84 typedef QuadTreeBuilder<osg::LOD*, ModelLOD, MakeQuadLeaf, AddModelLOD, 85 GetModelLODCoord> RandomObjectsQuadtree; 86 87 88 // needs constructor 89 static unsigned int num_tdcb = 0; 90 class SGTileDetailsCallback : public OptionsReadFileCallback { 91 public: SGTileDetailsCallback()92 SGTileDetailsCallback() 93 { 94 num_tdcb++; 95 } 96 ~SGTileDetailsCallback()97 virtual ~SGTileDetailsCallback() 98 { 99 num_tdcb--; 100 SG_LOG( SG_TERRAIN, SG_DEBUG, "SGTileDetailsCallback::~SGTileDetailsCallback() num cbs left " << num_tdcb ); 101 } 102 readNode(const std::string &,const osgDB::Options *)103 ReadResult readNode(const std::string&, const osgDB::Options*) override 104 { 105 osg::ref_ptr<osg::Group> group; 106 simgear::ErrorReportContext ec{"btg", _path}; 107 108 try { 109 group = new osg::Group; 110 SGMaterialLibPtr matlib; 111 osg::ref_ptr<SGMaterialCache> matcache; 112 group->setDataVariance(osg::Object::STATIC); 113 114 // generate textured triangle list 115 std::vector<SGTriangleInfo> matTris; 116 GetNodeTriangles nodeTris(_gbs_center, &matTris); 117 _rootNode->accept( nodeTris ); 118 119 // build matcache 120 matlib = _options->getMaterialLib(); 121 if (matlib) { 122 SGGeod geodPos = SGGeod::fromCart(_gbs_center); 123 matcache = matlib->generateMatCache(geodPos); 124 } 125 126 #if 0 127 // TEST : See if we can regenerate landclass shapes from node 128 for ( unsigned int i=0; i<matTris.size(); i++ ) { 129 matTris[i].dumpBorder(_gbs_center); 130 } 131 #endif 132 133 osg::Node* node = loadTerrain(); 134 if (node) { 135 group->addChild(node); 136 } 137 138 osg::LOD* lightLOD = generateLightingTileObjects(matTris, matcache); 139 if (lightLOD) { 140 group->addChild(lightLOD); 141 } 142 143 osg::LOD* objectLOD = generateRandomTileObjects(matTris, matcache); 144 if (objectLOD) { 145 group->addChild(objectLOD); 146 } 147 } catch (sg_exception& sge) { 148 simgear::reportFailure(simgear::LoadFailure::BadData, simgear::ErrorCode::BTGLoad, 149 "Failed to load BTG file:" + sge.getFormattedMessage(), 150 sge.getLocation()); 151 return ReadResult::ERROR_IN_READING_FILE; 152 } catch (std::bad_alloc &e) { 153 simgear::reportFailure(simgear::LoadFailure::OutOfMemory, simgear::ErrorCode::BTGLoad, 154 "Out of memory loading tile details", sg_location{_path}); 155 return ReadResult::INSUFFICIENT_MEMORY_TO_LOAD; 156 } 157 158 return group.release(); 159 } 160 getMaterialLightColor(const SGMaterial * material)161 static SGVec4f getMaterialLightColor(const SGMaterial* material) 162 { 163 if (!material) { 164 return SGVec4f(1, 1, 1, 0.8); 165 } 166 167 return material->get_light_color(); 168 } 169 170 static void addPointGeometry(SGLightBin & lights,const std::vector<SGVec3d> & vertices,const SGVec4f & color,const int_list & pts_v)171 addPointGeometry(SGLightBin& lights, 172 const std::vector<SGVec3d>& vertices, 173 const SGVec4f& color, 174 const int_list& pts_v) 175 { 176 for (unsigned i = 0; i < pts_v.size(); ++i) 177 lights.insert(toVec3f(vertices[pts_v[i]]), color); 178 } 179 180 static void addPointGeometry(SGDirectionalLightBin & lights,const std::vector<SGVec3d> & vertices,const std::vector<SGVec3f> & normals,const SGVec4f & color,const int_list & pts_v,const int_list & pts_n)181 addPointGeometry(SGDirectionalLightBin& lights, 182 const std::vector<SGVec3d>& vertices, 183 const std::vector<SGVec3f>& normals, 184 const SGVec4f& color, 185 const int_list& pts_v, 186 const int_list& pts_n) 187 { 188 // If the normal indices match the vertex indices, use seperate 189 // normal indices. Else reuse the vertex indices for the normals. 190 if (pts_v.size() == pts_n.size()) { 191 for (unsigned i = 0; i < pts_v.size(); ++i) 192 lights.insert(toVec3f(vertices[pts_v[i]]), normals[pts_n[i]], color); 193 } else { 194 for (unsigned i = 0; i < pts_v.size(); ++i) 195 lights.insert(toVec3f(vertices[pts_v[i]]), normals[pts_v[i]], color); 196 } 197 } 198 insertPtGeometry(const SGBinObject & obj,SGMaterialCache * matcache)199 bool insertPtGeometry(const SGBinObject& obj, SGMaterialCache* matcache) 200 { 201 if (obj.get_pts_v().size() != obj.get_pts_n().size()) { 202 SG_LOG(SG_TERRAIN, SG_ALERT, 203 "Group list sizes for points do not match!"); 204 return false; 205 } 206 207 for (unsigned grp = 0; grp < obj.get_pts_v().size(); ++grp) { 208 std::string materialName = obj.get_pt_materials()[grp]; 209 SGMaterial* material = matcache->find(materialName); 210 SGVec4f color = getMaterialLightColor(material); 211 212 if (3 <= materialName.size() && materialName.substr(0, 3) != "RWY") { 213 // Just plain lights. Not something for the runway. 214 addPointGeometry(tileLights, obj.get_wgs84_nodes(), color, 215 obj.get_pts_v()[grp]); 216 } else if (materialName == "RWY_BLUE_TAXIWAY_LIGHTS" 217 || materialName == "RWY_GREEN_TAXIWAY_LIGHTS") { 218 addPointGeometry(taxiLights, obj.get_wgs84_nodes(), obj.get_normals(), 219 color, obj.get_pts_v()[grp], obj.get_pts_n()[grp]); 220 } else if (materialName == "RWY_VASI_LIGHTS") { 221 vasiLights.push_back(SGDirectionalLightBin()); 222 addPointGeometry(vasiLights.back(), obj.get_wgs84_nodes(), 223 obj.get_normals(), color, obj.get_pts_v()[grp], 224 obj.get_pts_n()[grp]); 225 } else if (materialName == "RWY_SEQUENCED_LIGHTS") { 226 rabitLights.push_back(SGDirectionalLightBin()); 227 addPointGeometry(rabitLights.back(), obj.get_wgs84_nodes(), 228 obj.get_normals(), color, obj.get_pts_v()[grp], 229 obj.get_pts_n()[grp]); 230 } else if (materialName == "RWY_ODALS_LIGHTS") { 231 odalLights.push_back(SGLightBin()); 232 addPointGeometry(odalLights.back(), obj.get_wgs84_nodes(), 233 color, obj.get_pts_v()[grp]); 234 } else if (materialName == "RWY_YELLOW_PULSE_LIGHTS") { 235 holdshortLights.push_back(SGDirectionalLightBin()); 236 addPointGeometry(holdshortLights.back(), obj.get_wgs84_nodes(), 237 obj.get_normals(), color, obj.get_pts_v()[grp], 238 obj.get_pts_n()[grp]); 239 } else if (materialName == "RWY_GUARD_LIGHTS") { 240 guardLights.push_back(SGDirectionalLightBin()); 241 addPointGeometry(guardLights.back(), obj.get_wgs84_nodes(), 242 obj.get_normals(), color, obj.get_pts_v()[grp], 243 obj.get_pts_n()[grp]); 244 } else if (materialName == "RWY_REIL_LIGHTS") { 245 reilLights.push_back(SGDirectionalLightBin()); 246 addPointGeometry(reilLights.back(), obj.get_wgs84_nodes(), 247 obj.get_normals(), color, obj.get_pts_v()[grp], 248 obj.get_pts_n()[grp]); 249 } else { 250 // what is left must be runway lights 251 addPointGeometry(runwayLights, obj.get_wgs84_nodes(), 252 obj.get_normals(), color, obj.get_pts_v()[grp], 253 obj.get_pts_n()[grp]); 254 } 255 } 256 257 return true; 258 } 259 260 261 262 // Load terrain if required 263 // todo - this is the same code as when we load a btg from the .STG - can we combine? loadTerrain()264 osg::Node* loadTerrain() 265 { 266 if (! _loadterrain) 267 return NULL; 268 269 SGBinObject tile; 270 if (!tile.read_bin(_path)) 271 return NULL; 272 273 SGMaterialLibPtr matlib; 274 SGMaterialCache* matcache = 0; 275 bool useVBOs = false; 276 bool simplifyNear = false; 277 double ratio = SG_SIMPLIFIER_RATIO; 278 double maxLength = SG_SIMPLIFIER_MAX_LENGTH; 279 double maxError = SG_SIMPLIFIER_MAX_ERROR; 280 281 if (_options) { 282 matlib = _options->getMaterialLib(); 283 useVBOs = (_options->getPluginStringData("SimGear::USE_VBOS") == "ON"); 284 SGPropertyNode* propertyNode = _options->getPropertyNode().get(); 285 simplifyNear = propertyNode->getBoolValue("/sim/rendering/terrain/simplifier/enabled-near", simplifyNear); 286 ratio = propertyNode->getDoubleValue("/sim/rendering/terrain/simplifier/ratio", ratio); 287 maxLength = propertyNode->getDoubleValue("/sim/rendering/terrain/simplifier/max-length", maxLength); 288 maxError = propertyNode->getDoubleValue("/sim/rendering/terrain/simplifier/max-error", maxError); 289 } 290 291 // PSADRO TODO : we can do this in terragear 292 // - why not add a bitmask of flags to the btg so we can precompute this? 293 // and only do it if it hasn't been done already 294 SGVec3d center = tile.get_gbs_center(); 295 SGGeod geodPos = SGGeod::fromCart(center); 296 SGQuatd hlOr = SGQuatd::fromLonLat(geodPos)*SGQuatd::fromEulerDeg(0, 0, 180); 297 298 // Generate a materials cache 299 if (matlib) { 300 matcache = matlib->generateMatCache(geodPos); 301 } 302 303 // rotate the tiles so that the bounding boxes get nearly axis aligned. 304 // this will help the collision tree's bounding boxes a bit ... 305 std::vector<SGVec3d> nodes = tile.get_wgs84_nodes(); 306 for (unsigned i = 0; i < nodes.size(); ++i) { 307 nodes[i] = hlOr.transform(nodes[i]); 308 } 309 tile.set_wgs84_nodes(nodes); 310 311 SGQuatf hlOrf(hlOr[0], hlOr[1], hlOr[2], hlOr[3]); 312 std::vector<SGVec3f> normals = tile.get_normals(); 313 for (unsigned i = 0; i < normals.size(); ++i) { 314 normals[i] = hlOrf.transform(normals[i]); 315 } 316 tile.set_normals(normals); 317 318 osg::ref_ptr<SGTileGeometryBin> tileGeometryBin = new SGTileGeometryBin; 319 320 if (!tileGeometryBin->insertSurfaceGeometry(tile, matcache)) { 321 return NULL; 322 } 323 324 osg::Node* node = tileGeometryBin->getSurfaceGeometry(matcache, useVBOs); 325 if (node && simplifyNear) { 326 osgUtil::Simplifier simplifier(ratio, maxError, maxLength); 327 node->accept(simplifier); 328 } 329 330 return node; 331 } 332 min_dist_to_seg_squared(const SGVec3f p,const SGVec3d & a,const SGVec3d & b)333 float min_dist_to_seg_squared( const SGVec3f p, const SGVec3d& a, const SGVec3d& b ) 334 { 335 const float l2 = distSqr(a, b); 336 SGVec3d pd = toVec3d( p ); 337 if (l2 == 0.0) { 338 return distSqr(pd, a); // if a == b, just return distance to A 339 } 340 341 // Consider the line extending the segment, parameterized as a + t (b - a). 342 // We find projection of pt onto the line. 343 // It falls where t = [(p-a) . (b-a)] / |b-a|^2 344 const float t = dot(pd-a, b-a) / l2; 345 346 if (t < 0.0) { 347 return distSqr(pd, a); 348 } else if (t > 1.0) { 349 return distSqr(pd, b); 350 } else { 351 const SGVec3d proj = a + t * (b-a); 352 return distSqr(pd, proj); 353 } 354 } 355 min_dist_from_borders(SGVec3f p,const std::vector<SGBorderContour> & bsegs)356 float min_dist_from_borders( SGVec3f p, const std::vector<SGBorderContour>& bsegs ) 357 { 358 // calc min dist to each line 359 // calc distance squared to keep this as fast as we can 360 // first, we must be able to project the point onto the segment 361 std::vector<float> distances; 362 for ( unsigned int b=0; b<bsegs.size(); b++ ) 363 { 364 distances.push_back( min_dist_to_seg_squared( p, bsegs[b].start, bsegs[b].end ) ); 365 } 366 367 float min_dist_sq = *std::min_element( distances.begin(), distances.end() ); 368 return sqrt( min_dist_sq ); 369 } 370 371 // let's break random objects from randomBuildings computeRandomObjectsAndBuildings(std::vector<SGTriangleInfo> & matTris,float building_density,bool use_random_objects,bool use_random_buildings,bool useVBOs,SGMatModelBin & randomModels,SGBuildingBinList & randomBuildings)372 void computeRandomObjectsAndBuildings( 373 std::vector<SGTriangleInfo>& matTris, 374 float building_density, 375 bool use_random_objects, 376 bool use_random_buildings, 377 bool useVBOs, 378 SGMatModelBin& randomModels, 379 SGBuildingBinList& randomBuildings ) 380 { 381 unsigned int m; 382 383 // Only compute the random objects if we haven't already done so 384 if (_tileRandomObjectsComputed) { 385 return; 386 } 387 _tileRandomObjectsComputed = true; 388 389 // generate a repeatable random seed 390 mt seed; 391 mt_init(&seed, unsigned(123)); 392 393 for ( m=0; m<matTris.size(); m++ ) { 394 SGMaterial *mat = matTris[m].getMaterial(); 395 if (!mat) 396 continue; 397 398 osg::Texture2D* object_mask = mat->get_one_object_mask(matTris[m].getTextureIndex()); 399 400 int group_count = mat->get_object_group_count(); 401 float building_coverage = mat->get_building_coverage(); 402 float cos_zero_density_angle = mat->get_cos_object_zero_density_slope_angle(); 403 float cos_max_density_angle = mat->get_cos_object_max_density_slope_angle(); 404 405 if ((building_coverage == 0) && (group_count ==0)) 406 continue; 407 408 SGBuildingBin* bin = NULL; 409 410 if (building_coverage > 0) { 411 bin = new SGBuildingBin(mat, useVBOs); 412 randomBuildings.push_back(bin); 413 } 414 415 unsigned num = matTris[m].getNumTriangles(); 416 int random_dropped = 0; 417 int mask_dropped = 0; 418 int building_dropped = 0; 419 int triangle_dropped = 0; 420 421 // get the polygon border segments 422 // std::vector<SGBorderContour> borderSegs; 423 // matTris[m].getBorderContours( borderSegs ); 424 425 for (unsigned i = 0; i < num; ++i) { 426 std::vector<SGVec3f> triVerts; 427 std::vector<SGVec2f> triTCs; 428 matTris[m].getTriangle(i, triVerts, triTCs); 429 430 SGVec3f vorigin = triVerts[0]; 431 SGVec3f v0 = triVerts[1] - vorigin; 432 SGVec3f v1 = triVerts[2] - vorigin; 433 SGVec2f torigin = triTCs[0]; 434 SGVec2f t0 = triTCs[1] - torigin; 435 SGVec2f t1 = triTCs[2] - torigin; 436 SGVec3f normal = cross(v0, v1); 437 438 // Ensure the slope isn't too steep by checking the 439 // cos of the angle between the slope normal and the 440 // vertical (conveniently the z-component of the normalized 441 // normal) and values passed in. 442 float cos = normalize(normal).z(); 443 float slope_density = 1.0; 444 if (cos < cos_zero_density_angle) continue; // Too steep for any objects 445 if (cos < cos_max_density_angle) { 446 slope_density = 447 (cos - cos_zero_density_angle) / 448 (cos_max_density_angle - cos_zero_density_angle); 449 } 450 451 // Containers to hold the random buildings and objects generated 452 // for this triangle for collision detection purposes. 453 std::vector< std::pair< SGVec3f, float> > triangleObjectsList; 454 std::vector< std::pair< SGVec3f, float> > triangleBuildingList; 455 456 // Compute the area : todo - we only want to stop if the area of the POLY 457 // is too small 458 // so we need to know area of each poly.... 459 float area = 0.5f*length(normal); 460 if (area <= SGLimitsf::min()) 461 continue; 462 463 // Generate any random objects 464 if (use_random_objects && (group_count > 0)) 465 { 466 for (int j = 0; j < group_count; j++) 467 { 468 SGMatModelGroup *object_group = mat->get_object_group(j); 469 int nObjects = object_group->get_object_count(); 470 471 if (nObjects == 0) continue; 472 473 // For each of the random models in the group, determine an appropriate 474 // number of random placements and insert them. 475 for (int k = 0; k < nObjects; k++) { 476 SGMatModel * object = object_group->get_object(k); 477 478 // Determine the number of objecst to place, taking into account 479 // the slope density factor. 480 double n = slope_density * area / object->get_coverage_m2(); 481 482 // Use the zombie door method to determine fractional object placement. 483 n = n + mt_rand(&seed); 484 485 // place an object each unit of area 486 while ( n > 1.0 ) { 487 n -= 1.0; 488 489 float a = mt_rand(&seed); 490 float b = mt_rand(&seed); 491 if ( a + b > 1 ) { 492 a = 1 - a; 493 b = 1 - b; 494 } 495 496 SGVec3f randomPoint = vorigin + a*v0 + b*v1; 497 float rotation = static_cast<float>(mt_rand(&seed)); 498 499 // Check that the point is sufficiently far from 500 // the edge of the triangle by measuring the distance 501 // from the three lines that make up the triangle. 502 float spacing = object->get_spacing_m(); 503 504 SGVec3f p = randomPoint - vorigin; 505 #if 1 506 float edges[] = { 507 length(cross(p , p - v0)) / length(v0), 508 length(cross(p - v0, p - v1)) / length(v1 - v0), 509 length(cross(p - v1, p )) / length(v1) }; 510 float edge_dist = *std::min_element(edges, edges + 3); 511 #else 512 float edge_dist = min_dist_from_borders( randomPoint, borderSegs ); 513 #endif 514 if (edge_dist < spacing) { 515 continue; 516 } 517 518 if (object_mask != NULL) { 519 SGVec2f texCoord = torigin + a*t0 + b*t1; 520 521 // Check this random point against the object mask 522 // blue (for buildings) channel. 523 osg::Image* img = object_mask->getImage(); 524 unsigned int x = (int) (img->s() * texCoord.x()) % img->s(); 525 unsigned int y = (int) (img->t() * texCoord.y()) % img->t(); 526 527 if (mt_rand(&seed) > img->getColor(x, y).b()) { 528 // Failed object mask check 529 continue; 530 } 531 532 rotation = img->getColor(x,y).r(); 533 } 534 535 bool close = false; 536 537 // Check it isn't too close to any other random objects in the triangle 538 std::vector<std::pair<SGVec3f, float> >::iterator l; 539 for (l = triangleObjectsList.begin(); l != triangleObjectsList.end(); ++l) { 540 float min_dist2 = (l->second + object->get_spacing_m()) * 541 (l->second + object->get_spacing_m()); 542 543 if (distSqr(l->first, randomPoint) < min_dist2) { 544 close = true; 545 continue; 546 } 547 } 548 549 if (!close) { 550 triangleObjectsList.push_back(std::make_pair(randomPoint, object->get_spacing_m())); 551 randomModels.insert(randomPoint, 552 object, 553 (int)object->get_randomized_range_m(&seed), 554 rotation); 555 } 556 } 557 } 558 } 559 } 560 561 // Random objects now generated. Now generate the random buildings (if any); 562 if (use_random_buildings && (building_coverage > 0) && (building_density > 0)) { 563 564 // Calculate the number of buildings, taking into account building density (which is linear) 565 // and the slope density factor. 566 double num = building_density * building_density * slope_density * area / building_coverage; 567 568 // For partial units of area, use a zombie door method to 569 // create the proper random chance of an object being created 570 // for this triangle. 571 num = num + mt_rand(&seed); 572 573 if (num < 1.0f) { 574 continue; 575 } 576 577 // Cosine of the angle between the two vectors. 578 float cosine = (dot(v0, v1) / (length(v0) * length(v1))); 579 580 // Determine a grid spacing in each vector such that the correct 581 // coverage will result. 582 float stepv0 = (sqrtf(building_coverage) / building_density) / length(v0) / sqrtf(1 - cosine * cosine); 583 float stepv1 = (sqrtf(building_coverage) / building_density) / length(v1); 584 585 stepv0 = std::min(stepv0, 1.0f); 586 stepv1 = std::min(stepv1, 1.0f); 587 588 // Start at a random point. a will be immediately incremented below. 589 float a = -mt_rand(&seed) * stepv0; 590 float b = mt_rand(&seed) * stepv1; 591 592 // Place an object each unit of area 593 while (num > 1.0) { 594 num -= 1.0; 595 596 // Set the next location to place a building 597 a += stepv0; 598 599 if ((a + b) > 1.0f) { 600 // Reached the end of the scan-line on v0. Reset and increment 601 // scan-line on v1 602 a = mt_rand(&seed) * stepv0; 603 b += stepv1; 604 } 605 606 if (b > 1.0f) { 607 // In a degenerate case of a single point, we might be outside the 608 // scanline. Note that we need to still ensure that a+b < 1. 609 b = mt_rand(&seed) * stepv1 * (1.0f - a); 610 } 611 612 if ((a + b) > 1.0f ) { 613 // Truly degenerate case - simply choose a random point guaranteed 614 // to fulfil the constraing of a+b < 1. 615 a = mt_rand(&seed); 616 b = mt_rand(&seed) * (1.0f - a); 617 } 618 619 SGVec3f randomPoint = vorigin + a*v0 + b*v1; 620 float rotation = mt_rand(&seed); 621 622 if (object_mask != NULL) { 623 SGVec2f texCoord = torigin + a*t0 + b*t1; 624 osg::Image* img = object_mask->getImage(); 625 int x = (int) (img->s() * texCoord.x()) % img->s(); 626 int y = (int) (img->t() * texCoord.y()) % img->t(); 627 628 // In some degenerate cases x or y can be < 1, in which case the mod operand fails 629 while (x < 0) x += img->s(); 630 while (y < 0) y += img->t(); 631 632 if (mt_rand(&seed) < img->getColor(x, y).b()) { 633 // Object passes mask. Rotation is taken from the red channel 634 rotation = img->getColor(x,y).r(); 635 } else { 636 // Fails mask test - try again. 637 mask_dropped++; 638 continue; 639 } 640 } 641 642 // Check building isn't too close to the triangle edge. 643 float type_roll = mt_rand(&seed); 644 SGBuildingBin::BuildingType buildingtype = bin->getBuildingType(type_roll); 645 float radius = bin->getBuildingMaxRadius(buildingtype); 646 647 // Determine the actual center of the building, by shifting from the 648 // center of the front face to the true center. 649 osg::Matrix rotationMat = osg::Matrix::rotate(- rotation * M_PI * 2, 650 osg::Vec3f(0.0, 0.0, 1.0)); 651 SGVec3f buildingCenter = randomPoint + toSG(osg::Vec3f(-0.5 * bin->getBuildingMaxDepth(buildingtype), 0.0, 0.0) * rotationMat); 652 653 SGVec3f p = buildingCenter - vorigin; 654 #if 1 655 float edges[] = { length(cross(p , p - v0)) / length(v0), 656 length(cross(p - v0, p - v1)) / length(v1 - v0), 657 length(cross(p - v1, p )) / length(v1) }; 658 float edge_dist = *std::min_element(edges, edges + 3); 659 #else 660 float edge_dist = min_dist_from_borders(randomPoint, borderSegs); 661 #endif 662 if (edge_dist < radius) { 663 triangle_dropped++; 664 continue; 665 } 666 667 // Check building isn't too close to random objects and other buildings. 668 bool close = false; 669 std::vector<std::pair<SGVec3f, float> >::iterator iter; 670 671 for (iter = triangleBuildingList.begin(); iter != triangleBuildingList.end(); ++iter) { 672 float min_dist = iter->second + radius; 673 if (distSqr(iter->first, buildingCenter) < min_dist * min_dist) { 674 close = true; 675 continue; 676 } 677 } 678 679 if (close) { 680 building_dropped++; 681 continue; 682 } 683 684 for (iter = triangleObjectsList.begin(); iter != triangleObjectsList.end(); ++iter) { 685 float min_dist = iter->second + radius; 686 if (distSqr(iter->first, buildingCenter) < min_dist * min_dist) { 687 close = true; 688 continue; 689 } 690 } 691 692 if (close) { 693 random_dropped++; 694 continue; 695 } 696 697 std::pair<SGVec3f, float> pt = std::make_pair(buildingCenter, radius); 698 triangleBuildingList.push_back(pt); 699 bin->insert(randomPoint, rotation, buildingtype); 700 } 701 } 702 703 triangleObjectsList.clear(); 704 triangleBuildingList.clear(); 705 } 706 707 const int numBuildings = (bin) ? bin->getNumBuildings() : 0; 708 if (numBuildings > 0) { 709 SG_LOG(SG_TERRAIN, SG_DEBUG, "computed Random Buildings: " << numBuildings); 710 SG_LOG(SG_TERRAIN, SG_DEBUG, " Dropped due to mask: " << mask_dropped); 711 SG_LOG(SG_TERRAIN, SG_DEBUG, " Dropped due to random object: " << random_dropped); 712 SG_LOG(SG_TERRAIN, SG_DEBUG, " Dropped due to other buildings: " << building_dropped); 713 } 714 } 715 } 716 computeRandomForest(std::vector<SGTriangleInfo> & matTris,float vegetation_density,SGTreeBinList & randomForest)717 void computeRandomForest(std::vector<SGTriangleInfo>& matTris, float vegetation_density, SGTreeBinList& randomForest) 718 { 719 unsigned int i; 720 721 // generate a repeatable random seed 722 mt seed; 723 mt_init(&seed, unsigned(586)); 724 725 for ( i=0; i<matTris.size(); i++ ) { 726 SGMaterial *mat = matTris[i].getMaterial(); 727 if (!mat) 728 continue; 729 730 float wood_coverage = mat->get_wood_coverage(); 731 if ((wood_coverage <= 0) || (vegetation_density <= 0)) 732 continue; 733 734 // Attributes that don't vary by tree but do vary by material 735 bool found = false; 736 TreeBin* bin = NULL; 737 738 BOOST_FOREACH(bin, randomForest) 739 { 740 if ((bin->texture == mat->get_tree_texture() ) && 741 (bin->teffect == mat->get_tree_effect() ) && 742 (bin->texture_varieties == mat->get_tree_varieties()) && 743 (bin->range == mat->get_tree_range() ) && 744 (bin->width == mat->get_tree_width() ) && 745 (bin->height == mat->get_tree_height() ) ) { 746 found = true; 747 break; 748 } 749 } 750 751 if (!found) { 752 bin = new TreeBin(); 753 bin->texture = mat->get_tree_texture(); 754 SG_LOG(SG_TERRAIN, SG_DEBUG, "Tree texture " << bin->texture); 755 bin->teffect = mat->get_tree_effect(); 756 SG_LOG(SG_TERRAIN, SG_DEBUG, "Tree effect " << bin->teffect); 757 bin->range = mat->get_tree_range(); 758 bin->width = mat->get_tree_width(); 759 bin->height = mat->get_tree_height(); 760 bin->texture_varieties = mat->get_tree_varieties(); 761 randomForest.push_back(bin); 762 } 763 764 std::vector<SGVec3f> randomPoints; 765 std::vector<SGVec3f> randomPointNormals; 766 matTris[i].addRandomTreePoints(wood_coverage, 767 mat->get_one_object_mask(matTris[i].getTextureIndex()), 768 vegetation_density, 769 mat->get_cos_tree_max_density_slope_angle(), 770 mat->get_cos_tree_zero_density_slope_angle(), 771 mat->get_is_plantation(), 772 randomPoints, 773 randomPointNormals); 774 775 std::vector<SGVec3f>::iterator k; 776 std::vector<SGVec3f>::iterator j; 777 for (k = randomPoints.begin(), j = randomPointNormals.begin(); k != randomPoints.end(); ++k, ++j) { 778 bin->insert(*k, *j); 779 } 780 } 781 } 782 computeRandomSurfaceLights(std::vector<SGTriangleInfo> & matTris,SGLightBin & randomTileLights)783 void computeRandomSurfaceLights(std::vector<SGTriangleInfo>& matTris, SGLightBin& randomTileLights ) 784 { 785 unsigned int i; 786 787 // Only compute the lights if we haven't already done so. 788 // For example, the light data will still exist if the 789 // PagedLOD expires. 790 if ( _randomSurfaceLightsComputed ) 791 { 792 return; 793 } 794 _randomSurfaceLightsComputed = true; 795 796 // generate a repeatable random seed 797 mt seed; 798 mt_init(&seed, unsigned(123)); 799 800 for ( i=0; i<matTris.size(); i++ ) { 801 SGMaterial *mat = matTris[i].getMaterial(); 802 if (!mat) 803 continue; 804 805 float coverage = mat->get_light_coverage(); 806 if (coverage <= 0) 807 continue; 808 809 int texIndex = matTris[i].getTextureIndex(); 810 811 std::vector<SGVec3f> randomPoints; 812 matTris[i].addRandomSurfacePoints(coverage, 3, mat->get_one_object_mask(texIndex), randomPoints); 813 std::vector<SGVec3f>::iterator j; 814 for (j = randomPoints.begin(); j != randomPoints.end(); ++j) { 815 float zombie = mt_rand(&seed); 816 // factor = sg_random() ^ 2, range = 0 .. 1 concentrated towards 0 817 float factor = mt_rand(&seed); 818 factor *= factor; 819 820 float bright = 1; 821 SGVec4f color; 822 if ( zombie > 0.5 ) { 823 // 50% chance of yellowish 824 color = SGVec4f(0.9f, 0.9f, 0.3f, bright - factor * 0.2f); 825 } else if (zombie > 0.15f) { 826 // 35% chance of whitish 827 color = SGVec4f(0.9, 0.9f, 0.8f, bright - factor * 0.2f); 828 } else if (zombie > 0.05f) { 829 // 10% chance of orangish 830 color = SGVec4f(0.9f, 0.6f, 0.2f, bright - factor * 0.2f); 831 } else { 832 // 5% chance of redish 833 color = SGVec4f(0.9f, 0.2f, 0.2f, bright - factor * 0.2f); 834 } 835 randomTileLights.insert(*j, color); 836 } 837 } 838 } 839 840 // Generate all the lighting objects for the tile. generateLightingTileObjects(std::vector<SGTriangleInfo> & matTris,const SGMaterialCache * matcache)841 osg::LOD* generateLightingTileObjects(std::vector<SGTriangleInfo>& matTris, const SGMaterialCache* matcache) 842 { 843 SGLightBin randomTileLights; 844 computeRandomSurfaceLights(matTris, randomTileLights); 845 846 GroundLightManager* lightManager = GroundLightManager::instance(); 847 osg::ref_ptr<osg::Group> lightGroup = new SGOffsetTransform(0.94); 848 SGVec3f up(0, 0, 1); 849 850 if (tileLights.getNumLights() > 0 || randomTileLights.getNumLights() > 0) { 851 osg::ref_ptr<osg::Group> groundLights0 = new osg::Group; 852 853 groundLights0->setStateSet(lightManager->getGroundLightStateSet()); 854 groundLights0->setNodeMask(GROUNDLIGHTS0_BIT); 855 856 osg::ref_ptr<EffectGeode> geode = new EffectGeode; 857 osg::ref_ptr<Effect> lightEffect = getLightEffect(24, osg::Vec3(1, 0.001, 0.00001), 1, 8, false, _options); 858 859 geode->setEffect(lightEffect); 860 geode->addDrawable(SGLightFactory::getLights(tileLights)); 861 geode->addDrawable(SGLightFactory::getLights(randomTileLights, 4, -0.3f)); 862 groundLights0->addChild(geode); 863 lightGroup->addChild(groundLights0); 864 } 865 866 if (randomTileLights.getNumLights() > 0) { 867 osg::ref_ptr<osg::Group> groundLights1 = new osg::Group; 868 groundLights1->setStateSet(lightManager->getGroundLightStateSet()); 869 groundLights1->setNodeMask(GROUNDLIGHTS1_BIT); 870 871 osg::ref_ptr<osg::Group> groundLights2 = new osg::Group; 872 groundLights2->setStateSet(lightManager->getGroundLightStateSet()); 873 groundLights2->setNodeMask(GROUNDLIGHTS2_BIT); 874 875 osg::ref_ptr<EffectGeode> geode1 = new EffectGeode; 876 877 osg::ref_ptr<Effect> lightEffect = getLightEffect(24, osg::Vec3(1, 0.001, 0.00001), 1, 8, false, _options); 878 geode1->setEffect(lightEffect); 879 geode1->addDrawable(SGLightFactory::getLights(randomTileLights, 2, -0.15f)); 880 groundLights1->addChild(geode1); 881 lightGroup->addChild(groundLights1); 882 883 osg::ref_ptr<EffectGeode> geode2 = new EffectGeode; 884 885 geode2->setEffect(lightEffect); 886 geode2->addDrawable(SGLightFactory::getLights(randomTileLights)); 887 groundLights2->addChild(geode2); 888 lightGroup->addChild(groundLights2); 889 } 890 891 if (! vasiLights.empty()) { 892 EffectGeode* vasiGeode = new EffectGeode; 893 Effect* vasiEffect = getLightEffect(24, osg::Vec3(1, 0.0001, 0.000001), 1, 24, true, _options); 894 vasiGeode->setEffect(vasiEffect); 895 SGVec4f red(1, 0, 0, 1); 896 SGMaterial* mat = 0; 897 if (matcache) 898 mat = matcache->find("RWY_RED_LIGHTS"); 899 if (mat) { 900 red = mat->get_light_color(); 901 } 902 903 SGVec4f white(1, 1, 1, 1); 904 mat = 0; 905 if (matcache) 906 mat = matcache->find("RWY_WHITE_LIGHTS"); 907 if (mat) { 908 white = mat->get_light_color(); 909 } 910 SGDirectionalLightListBin::const_iterator i; 911 for (i = vasiLights.begin(); 912 i != vasiLights.end(); ++i) { 913 osg::Drawable* vasiDraw = SGLightFactory::getVasi(up, *i, red, white); 914 vasiGeode->addDrawable( vasiDraw ); 915 } 916 osg::StateSet* ss = lightManager->getRunwayLightStateSet(); 917 vasiGeode->setStateSet( ss ); 918 lightGroup->addChild(vasiGeode); 919 } 920 921 Effect* runwayEffect = 0; 922 if (runwayLights.getNumLights() > 0 || taxiLights.getNumLights() > 0) { 923 runwayEffect = getLightEffect(16, osg::Vec3(1, 0.001, 0.0002), 1, 16, true, _options); 924 } 925 926 if (runwayLights.getNumLights() > 0 927 || !rabitLights.empty() 928 || !reilLights.empty() 929 || !odalLights.empty() 930 || !holdshortLights.empty() 931 || !guardLights.empty()) { 932 933 osg::Group* rwyLightsGroup = new osg::Group; 934 rwyLightsGroup->setStateSet(lightManager->getRunwayLightStateSet()); 935 rwyLightsGroup->setNodeMask(RUNWAYLIGHTS_BIT); 936 937 SGDirectionalLightListBin::const_iterator i; 938 939 for (i = rabitLights.begin() ; i != rabitLights.end() ; ++i) { 940 rwyLightsGroup->addChild(SGLightFactory::getSequenced(*i, _options)); 941 } 942 for (i = reilLights.begin() ; i != reilLights.end() ; ++i) { 943 rwyLightsGroup->addChild(SGLightFactory::getReil(*i, _options)); 944 } 945 for (i = holdshortLights.begin() ; i != holdshortLights.end() ; ++i) { 946 rwyLightsGroup->addChild(SGLightFactory::getHoldShort(*i, _options)); 947 } 948 for (i = guardLights.begin() ; i != guardLights.end() ; ++i) { 949 rwyLightsGroup->addChild(SGLightFactory::getGuard(*i, _options)); 950 } 951 SGLightListBin::const_iterator j; 952 for (j = odalLights.begin() ; j != odalLights.end() ; ++j) { 953 rwyLightsGroup->addChild(SGLightFactory::getOdal(*j, _options)); 954 } 955 956 if (runwayLights.getNumLights() > 0) { 957 osg::ref_ptr<EffectGeode> geode = new EffectGeode; 958 geode->setEffect(runwayEffect); 959 geode->addDrawable(SGLightFactory::getLights(runwayLights)); 960 rwyLightsGroup->addChild(geode); 961 } 962 963 lightGroup->addChild(rwyLightsGroup); 964 } 965 966 if (taxiLights.getNumLights() > 0) { 967 osg::Group* taxiLightsGroup = new osg::Group; 968 taxiLightsGroup->setStateSet(lightManager->getTaxiLightStateSet()); 969 taxiLightsGroup->setNodeMask(RUNWAYLIGHTS_BIT); 970 EffectGeode* geode = new EffectGeode; 971 geode->setEffect(runwayEffect); 972 geode->addDrawable(SGLightFactory::getLights(taxiLights)); 973 taxiLightsGroup->addChild(geode); 974 lightGroup->addChild(taxiLightsGroup); 975 } 976 977 osg::LOD* lightLOD = NULL; 978 979 if (lightGroup->getNumChildren() > 0) { 980 lightLOD = new osg::LOD; 981 lightLOD->addChild(lightGroup.get(), 0, 60000); 982 // VASI is always on, so doesn't use light bits. 983 lightLOD->setNodeMask(LIGHTS_BITS | MODEL_BIT | PERMANENTLIGHT_BIT); 984 } 985 986 return lightLOD; 987 } 988 989 // Generate all the random forest, objects and buildings for the tile generateRandomTileObjects(std::vector<SGTriangleInfo> & matTris,const SGMaterialCache * matcache)990 osg::LOD* generateRandomTileObjects(std::vector<SGTriangleInfo>& matTris, const SGMaterialCache* matcache) 991 { 992 SGMaterialLibPtr matlib; 993 bool use_random_objects = false; 994 bool use_random_vegetation = false; 995 bool use_random_buildings = false; 996 float vegetation_density = 1.0f; 997 float building_density = 1.0f; 998 float object_range = SG_OBJECT_RANGE_ROUGH; 999 bool useVBOs = false; 1000 1001 osg::ref_ptr<osg::Group> randomObjects; 1002 osg::ref_ptr<osg::Group> forestNode; 1003 osg::ref_ptr<osg::Group> buildingNode; 1004 1005 if (_options) { 1006 matlib = _options->getMaterialLib(); 1007 SGPropertyNode* propertyNode = _options->getPropertyNode().get(); 1008 if (propertyNode) { 1009 use_random_objects 1010 = propertyNode->getBoolValue("/sim/rendering/random-objects", 1011 use_random_objects); 1012 use_random_vegetation 1013 = propertyNode->getBoolValue("/sim/rendering/random-vegetation", 1014 use_random_vegetation); 1015 vegetation_density 1016 = propertyNode->getFloatValue("/sim/rendering/vegetation-density", 1017 vegetation_density); 1018 use_random_buildings 1019 = propertyNode->getBoolValue("/sim/rendering/random-buildings", 1020 use_random_buildings); 1021 building_density 1022 = propertyNode->getFloatValue("/sim/rendering/building-density", 1023 building_density); 1024 object_range 1025 = propertyNode->getFloatValue("/sim/rendering/static-lod/rough", 1026 object_range); 1027 } 1028 1029 useVBOs = (_options->getPluginStringData("SimGear::USE_VBOS") == "ON"); 1030 } 1031 1032 SGMatModelBin randomModels; 1033 1034 SGBuildingBinList randomBuildings; 1035 1036 if (matlib && (use_random_objects || use_random_buildings)) { 1037 computeRandomObjectsAndBuildings( matTris, 1038 building_density, 1039 use_random_objects, 1040 use_random_buildings, 1041 useVBOs, 1042 randomModels, 1043 randomBuildings 1044 ); 1045 } 1046 1047 if (randomModels.getNumModels() > 0) { 1048 // Generate a repeatable random seed 1049 mt seed; 1050 mt_init(&seed, unsigned(123)); 1051 1052 std::vector<ModelLOD> models; 1053 for (unsigned int i = 0; i < randomModels.getNumModels(); i++) { 1054 SGMatModelBin::MatModel obj = randomModels.getMatModel(i); 1055 1056 SGPropertyNode* root = _options->getPropertyNode()->getRootNode(); 1057 osg::Node* node = obj.model->get_random_model(root, &seed); 1058 1059 // Create a matrix to place the object in the correct 1060 // location, and then apply the rotation matrix created 1061 // above, with an additional random (or taken from 1062 // the object mask) heading rotation if appropriate. 1063 osg::Matrix transformMat; 1064 transformMat = osg::Matrix::translate(toOsg(obj.position)); 1065 if (obj.model->get_heading_type() == SGMatModel::HEADING_RANDOM) { 1066 // Rotate the object around the z axis. 1067 double hdg = mt_rand(&seed) * M_PI * 2; 1068 transformMat.preMult(osg::Matrix::rotate(hdg, 1069 osg::Vec3d(0.0, 0.0, 1.0))); 1070 } 1071 1072 if (obj.model->get_heading_type() == SGMatModel::HEADING_MASK) { 1073 // Rotate the object around the z axis. 1074 double hdg = - obj.rotation * M_PI * 2; 1075 transformMat.preMult(osg::Matrix::rotate(hdg, 1076 osg::Vec3d(0.0, 0.0, 1.0))); 1077 } 1078 1079 osg::MatrixTransform* position = 1080 new osg::MatrixTransform(transformMat); 1081 position->setName("positionRandomModel"); 1082 position->addChild(node); 1083 models.push_back(ModelLOD(position, obj.lod)); 1084 } 1085 RandomObjectsQuadtree quadtree((GetModelLODCoord()), (AddModelLOD())); 1086 quadtree.buildQuadTree(models.begin(), models.end()); 1087 randomObjects = quadtree.getRoot(); 1088 randomObjects->setName("Random objects"); 1089 } 1090 1091 if (!randomBuildings.empty()) { 1092 buildingNode = createRandomBuildings(randomBuildings, osg::Matrix::identity(), _options); 1093 buildingNode->setName("Random buildings"); 1094 randomBuildings.clear(); 1095 } 1096 1097 if (use_random_vegetation && matlib) { 1098 // Now add some random forest. 1099 SGTreeBinList randomForest; 1100 computeRandomForest(matTris, vegetation_density, randomForest); 1101 1102 if (!randomForest.empty()) { 1103 forestNode = createForest(randomForest, osg::Matrix::identity(),_options); 1104 forestNode->setName("Random trees"); 1105 } 1106 } 1107 1108 osg::LOD* objectLOD = NULL; 1109 1110 if (randomObjects.valid() || forestNode.valid() || buildingNode.valid()) { 1111 objectLOD = new osg::LOD; 1112 1113 // random objects, trees and buildings can be displayed up to 2xrange out. 1114 if (randomObjects.valid()) objectLOD->addChild(randomObjects.get(), 0, 2.0 * object_range + SG_TILE_RADIUS); 1115 if (forestNode.valid()) objectLOD->addChild(forestNode.get(), 0, 2.0 * object_range + SG_TILE_RADIUS); 1116 if (buildingNode.valid()) objectLOD->addChild(buildingNode.get(), 0, 2.0 * object_range + SG_TILE_RADIUS); 1117 1118 unsigned nodeMask = SG_NODEMASK_RECEIVESHADOW_BIT | SG_NODEMASK_TERRAIN_BIT; 1119 objectLOD->setNodeMask(nodeMask); 1120 } 1121 1122 return objectLOD; 1123 } 1124 1125 /// The original options to use for this bunch of models 1126 osg::ref_ptr<SGReaderWriterOptions> _options; 1127 string _path; 1128 bool _loadterrain; 1129 osg::ref_ptr<osg::Node> _rootNode; 1130 SGVec3d _gbs_center; 1131 bool _randomSurfaceLightsComputed; 1132 bool _tileRandomObjectsComputed; 1133 1134 // most of these are just point and color arrays - extracted from the 1135 // .BTG PointGeometry at tile load time. 1136 // It shouldn't be too much to keep this in memory even if we don't use it. 1137 SGLightBin tileLights; 1138 SGDirectionalLightBin runwayLights; 1139 SGDirectionalLightBin taxiLights; 1140 SGDirectionalLightListBin vasiLights; 1141 SGDirectionalLightListBin rabitLights; 1142 SGLightListBin odalLights; 1143 SGDirectionalLightListBin holdshortLights; 1144 SGDirectionalLightListBin guardLights; 1145 SGDirectionalLightListBin reilLights; 1146 }; 1147