1 // future API - just run through once to convert from OSG to SG 2 // then we can use these triangle lists for random 3 // trees/lights/buildings/objects 4 struct SGTexturedTriangle 5 { 6 public: 7 std::vector<SGVec3f> vertices; 8 std::vector<SGVec2f> texcoords; 9 }; 10 11 struct SGBorderContour 12 { 13 public: 14 SGVec3d start; 15 SGVec3d end; 16 }; 17 18 class SGTriangleInfo 19 { 20 public: SGTriangleInfo(const SGVec3d & center)21 SGTriangleInfo( const SGVec3d& center ) { 22 gbs_center = center; 23 mt_init(&seed, 123); 24 } 25 26 // API used to build the Info by the visitor addGeometry(osg::Geometry * g)27 void addGeometry( osg::Geometry* g ) { 28 geometries.push_back(g); 29 } 30 setMaterial(SGMaterial * m)31 void setMaterial( SGMaterial* m ) { 32 mat = m; 33 } 34 getMaterial(void) const35 SGMaterial* getMaterial( void ) const { 36 return mat; 37 } 38 39 // API used to get a specific texture or effect from a material. Materials can have 40 // multiple textures - use the floor of the x coordinate of the first vertes to select it. 41 // This will be constant, and give the same result each time to select one effect/texture per drawable. getTextureIndex(void) const42 int getTextureIndex( void ) const { 43 int texInfo = 0; 44 const osg::Vec3Array* vertices = dynamic_cast<osg::Vec3Array*>(geometries[0]->getVertexArray()); 45 if ( vertices ) { 46 const osg::Vec3 *v0 = &vertices->operator[](0); 47 texInfo = floor(v0->x()); 48 } 49 return texInfo; 50 } 51 52 // new API - TODO getTriangles(std::vector<SGTexturedTriangle> & tris)53 void getTriangles( std::vector<SGTexturedTriangle>& tris ) 54 { 55 const osg::Vec3Array* vertices = dynamic_cast<osg::Vec3Array*>(geometries[0]->getVertexArray()); 56 const osg::Vec2Array* texcoords = dynamic_cast<osg::Vec2Array*>(geometries[0]->getTexCoordArray(0)); 57 58 int numPrimitiveSets = geometries[0]->getNumPrimitiveSets(); 59 if ( numPrimitiveSets > 0 ) { 60 const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0); 61 unsigned int numIndices = ps->getNumIndices(); 62 63 for ( unsigned int i=2; i<numIndices; i+= 3 ) { 64 SGTexturedTriangle tri; 65 66 tri.vertices.push_back( toSG(vertices->operator[](ps->index(i-2))) ); 67 tri.vertices.push_back( toSG(vertices->operator[](ps->index(i-1))) ); 68 tri.vertices.push_back( toSG(vertices->operator[](ps->index(i-0))) ); 69 70 tri.texcoords.push_back( toSG(texcoords->operator[](ps->index(i-2))) ); 71 tri.texcoords.push_back( toSG(texcoords->operator[](ps->index(i-1))) ); 72 tri.texcoords.push_back( toSG(texcoords->operator[](ps->index(i-0))) ); 73 } 74 } 75 } 76 getBorderContours(std::vector<SGBorderContour> & border)77 void getBorderContours( std::vector<SGBorderContour>& border ) 78 { 79 // each structure contains a list of target indexes and a count 80 int numPrimitiveSets = geometries[0]->getNumPrimitiveSets(); 81 if ( numPrimitiveSets > 0 ) { 82 const osg::Vec3Array* vertices = dynamic_cast<osg::Vec3Array*>(geometries[0]->getVertexArray()); 83 84 const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0); 85 unsigned int numTriangles = ps->getNumIndices()/3; 86 87 // use a map for fast lookup map the segment as a 64 bit int 88 std::map<uint64_t, int> segCounter; 89 uint32_t idx1, idx2; 90 uint64_t key; 91 92 for ( unsigned int i=0; i<numTriangles; i+= 3 ) { 93 // first seg 94 if ( ps->index(i+0) < ps->index(i+1) ) { 95 idx1 = ps->index(i+0); 96 idx2 = ps->index(i+1); 97 } else { 98 idx1 = ps->index(i+1); 99 idx2 = ps->index(i+0); 100 } 101 102 key=( (uint64_t)idx1<<32) | (uint64_t)idx2; 103 SG_LOG(SG_TERRAIN, SG_ALERT, "key " << std::hex << key << std::dec << " count is " << segCounter[key] ); 104 segCounter[key]++; 105 SG_LOG(SG_TERRAIN, SG_ALERT, "after increment key " << std::hex << key << std::dec << " count is " << segCounter[key] ); 106 107 // second seg 108 if ( ps->index(i+1) < ps->index(i+2) ) { 109 idx1 = ps->index(i+1); 110 idx2 = ps->index(i+2); 111 } else { 112 idx1 = ps->index(i+2); 113 idx2 = ps->index(i+1); 114 } 115 116 key=( (uint64_t)idx1<<32) | (uint64_t)idx2; 117 SG_LOG(SG_TERRAIN, SG_ALERT, "key " << std::hex << key << std::dec << " count is " << segCounter[key] ); 118 segCounter[key]++; 119 SG_LOG(SG_TERRAIN, SG_ALERT, "after increment key " << std::hex << key << std::dec << " count is " << segCounter[key] ); 120 121 // third seg 122 if ( ps->index(i+2) < ps->index(i+0) ) { 123 idx1 = ps->index(i+2); 124 idx2 = ps->index(i+0); 125 } else { 126 idx1 = ps->index(i+0); 127 idx2 = ps->index(i+2); 128 } 129 130 key=( (uint64_t)idx1<<32) | (uint64_t)idx2; 131 SG_LOG(SG_TERRAIN, SG_ALERT, "key " << std::hex << key << std::dec << " count is " << segCounter[key] ); 132 segCounter[key]++; 133 SG_LOG(SG_TERRAIN, SG_ALERT, "after increment key " << std::hex << key << std::dec << " count is " << segCounter[key] ); 134 } 135 136 // return all segments with count = 1 ( border ) 137 std::map<uint64_t, int>::iterator segIt = segCounter.begin(); 138 while ( segIt != segCounter.end() ) { 139 if ( segIt->second == 1 ) { 140 SG_LOG(SG_TERRAIN, SG_ALERT, "key " << std::hex << segIt->first << std::dec << " count is " << segIt->second ); 141 142 unsigned int iStart = segIt->first >> 32; 143 unsigned int iEnd = segIt->first & 0x00000000FFFFFFFF; 144 145 SGBorderContour bc; 146 147 bc.start = toVec3d(toSG(vertices->operator[](iStart))); 148 bc.end = toVec3d(toSG(vertices->operator[](iEnd))); 149 border.push_back( bc ); 150 } 151 segIt++; 152 } 153 154 #if 0 155 // debug out - requires GDAL 156 // 157 // 158 // 159 SGGeod geodPos = SGGeod::fromCart(gbs_center); 160 SGQuatd hlOr = SGQuatd::fromLonLat(geodPos)*SGQuatd::fromEulerDeg(0, 0, 180); 161 162 for ( unsigned int i=0; i<border.size(); i++ ){ 163 // de-rotate and translate : todo - create a paralell vertex list so we just do this 164 // once per vertex, not for every triangle's use of the vertex 165 SGVec3d sgVStart = hlOr.backTransform( border[i].start) + gbs_center; 166 SGVec3d sgVEnd = hlOr.backTransform( border[i].end) + gbs_center; 167 168 // convert from cartesian to Geodetic, and save as a list of Geods for output 169 SGGeod gStart = SGGeod::fromCart(sgVStart); 170 SGGeod gEnd = SGGeod::fromCart(sgVEnd); 171 172 SGShapefile::FromSegment( gStart, gEnd, true, "./borders", mat->get_names()[0], "border" ); 173 } 174 #endif 175 } 176 } 177 178 // Random buildings API - get num triangles, then get a triangle at index getNumTriangles(void) const179 unsigned int getNumTriangles( void ) const { 180 unsigned int num_triangles = 0; 181 182 if ( !geometries.empty() ) { 183 int numPrimitiveSets = geometries[0]->getNumPrimitiveSets(); 184 if ( numPrimitiveSets > 0 ) { 185 const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0); 186 unsigned int numIndices = ps->getNumIndices(); 187 num_triangles = numIndices/3; 188 } 189 } 190 191 return num_triangles; 192 } 193 getTriangle(unsigned int i,std::vector<SGVec3f> & triVerts,std::vector<SGVec2f> & triTCs) const194 void getTriangle(unsigned int i, std::vector<SGVec3f>& triVerts, std::vector<SGVec2f>& triTCs) const { 195 const osg::Vec3Array* vertices = dynamic_cast<osg::Vec3Array*>(geometries[0]->getVertexArray()); 196 const osg::Vec2Array* texcoords = dynamic_cast<osg::Vec2Array*>(geometries[0]->getTexCoordArray(0)); 197 198 if ( !geometries.empty() ) { 199 int numPrimitiveSets = geometries[0]->getNumPrimitiveSets(); 200 if ( numPrimitiveSets > 0 ) { 201 const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0); 202 int idxStart = i*3; 203 204 triVerts.push_back( toSG(vertices->operator[](ps->index(idxStart+0))) ); 205 triVerts.push_back( toSG(vertices->operator[](ps->index(idxStart+1))) ); 206 triVerts.push_back( toSG(vertices->operator[](ps->index(idxStart+2))) ); 207 208 triTCs.push_back( toSG(texcoords->operator[](ps->index(idxStart+0))) ); 209 triTCs.push_back( toSG(texcoords->operator[](ps->index(idxStart+1))) ); 210 triTCs.push_back( toSG(texcoords->operator[](ps->index(idxStart+2))) ); 211 } 212 } 213 } 214 215 // random lights and trees - just get a list of points on where to add the light / tree 216 // TODO move this out - and handle in the random light / tree code 217 // just use generic triangle API. addRandomSurfacePoints(float coverage,float offset,osg::Texture2D * object_mask,std::vector<SGVec3f> & points)218 void addRandomSurfacePoints(float coverage, float offset, 219 osg::Texture2D* object_mask, 220 std::vector<SGVec3f>& points) 221 { 222 if ( !geometries.empty() ) { 223 const osg::Vec3Array* vertices = dynamic_cast<osg::Vec3Array*>(geometries[0]->getVertexArray()); 224 const osg::Vec2Array* texcoords = dynamic_cast<osg::Vec2Array*>(geometries[0]->getTexCoordArray(0)); 225 226 int numPrimitiveSets = geometries[0]->getNumPrimitiveSets(); 227 if ( numPrimitiveSets > 0 ) { 228 const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0); 229 unsigned int numIndices = ps->getNumIndices(); 230 231 for ( unsigned int i=2; i<numIndices; i+= 3 ) { 232 SGVec3f v0 = toSG(vertices->operator[](ps->index(i-2))); 233 SGVec3f v1 = toSG(vertices->operator[](ps->index(i-1))); 234 SGVec3f v2 = toSG(vertices->operator[](ps->index(i-0))); 235 236 SGVec2f t0 = toSG(texcoords->operator[](ps->index(i-2))); 237 SGVec2f t1 = toSG(texcoords->operator[](ps->index(i-1))); 238 SGVec2f t2 = toSG(texcoords->operator[](ps->index(i-0))); 239 240 SGVec3f normal = cross(v1 - v0, v2 - v0); 241 242 // Compute the area 243 float area = 0.5f*length(normal); 244 if (area <= SGLimitsf::min()) 245 continue; 246 247 // For partial units of area, use a zombie door method to 248 // create the proper random chance of a light being created 249 // for this triangle 250 float unit = area + mt_rand(&seed)*coverage; 251 252 SGVec3f offsetVector = offset*normalize(normal); 253 // generate a light point for each unit of area 254 255 while ( coverage < unit ) { 256 float a = mt_rand(&seed); 257 float b = mt_rand(&seed); 258 259 if ( a + b > 1 ) { 260 a = 1 - a; 261 b = 1 - b; 262 } 263 float c = 1 - a - b; 264 SGVec3f randomPoint = offsetVector + a*v0 + b*v1 + c*v2; 265 266 if (object_mask != NULL) { 267 SGVec2f texCoord = a*t0 + b*t1 + c*t2; 268 269 // Check this random point against the object mask 270 // red channel. 271 osg::Image* img = object_mask->getImage(); 272 unsigned int x = (int) (img->s() * texCoord.x()) % img->s(); 273 unsigned int y = (int) (img->t() * texCoord.y()) % img->t(); 274 275 if (mt_rand(&seed) < img->getColor(x, y).r()) { 276 points.push_back(randomPoint); 277 } 278 } else { 279 // No object mask, so simply place the object 280 points.push_back(randomPoint); 281 } 282 unit -= coverage; 283 } 284 } 285 } 286 } 287 } 288 addRandomTreePoints(float wood_coverage,osg::Texture2D * object_mask,float vegetation_density,float cos_max_density_angle,float cos_zero_density_angle,bool is_plantation,std::vector<SGVec3f> & points,std::vector<SGVec3f> & normals)289 void addRandomTreePoints(float wood_coverage, 290 osg::Texture2D* object_mask, 291 float vegetation_density, 292 float cos_max_density_angle, 293 float cos_zero_density_angle, 294 bool is_plantation, 295 std::vector<SGVec3f>& points, 296 std::vector<SGVec3f>& normals) 297 { 298 if ( !geometries.empty() ) { 299 const osg::Vec3Array* vertices = dynamic_cast<osg::Vec3Array*>(geometries[0]->getVertexArray()); 300 const osg::Vec2Array* texcoords = dynamic_cast<osg::Vec2Array*>(geometries[0]->getTexCoordArray(0)); 301 302 int numPrimitiveSets = geometries[0]->getNumPrimitiveSets(); 303 if ( numPrimitiveSets > 0 ) { 304 const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0); 305 unsigned int numIndices = ps->getNumIndices(); 306 307 for ( unsigned int i=2; i<numIndices; i+= 3 ) { 308 SGVec3f v0 = toSG(vertices->operator[](ps->index(i-2))); 309 SGVec3f v1 = toSG(vertices->operator[](ps->index(i-1))); 310 SGVec3f v2 = toSG(vertices->operator[](ps->index(i-0))); 311 312 SGVec2f t0 = toSG(texcoords->operator[](ps->index(i-2))); 313 SGVec2f t1 = toSG(texcoords->operator[](ps->index(i-1))); 314 SGVec2f t2 = toSG(texcoords->operator[](ps->index(i-0))); 315 316 SGVec3f normal = cross(v1 - v0, v2 - v0); 317 318 // Ensure the slope isn't too steep by checking the 319 // cos of the angle between the slope normal and the 320 // vertical (conveniently the z-component of the normalized 321 // normal) and values passed in. 322 float alpha = normalize(normal).z(); 323 float slope_density = 1.0; 324 325 if (alpha < cos_zero_density_angle) 326 continue; // Too steep for any vegetation 327 328 if (alpha < cos_max_density_angle) { 329 slope_density = 330 (alpha - cos_zero_density_angle) / (cos_max_density_angle - cos_zero_density_angle); 331 } 332 333 // Compute the area 334 float area = 0.5f*length(normal); 335 if (area <= SGLimitsf::min()) 336 continue; 337 338 if (is_plantation) { // regularly-spaced vegetation 339 // separate vegetation in integral 1m units 340 int separation = (int) ceil(sqrt(wood_coverage)); 341 float max_x = ceil(max(max(v1.x(),v2.x()),v0.x())); 342 float min_x = floor(min(min(v1.x(),v2.x()),v0.x())); 343 float max_y = ceil(max(max(v1.y(),v2.y()),v0.y())); 344 float min_y = floor(min(min(v1.y(),v2.y()),v0.y())); 345 346 /* equation of the plane ax+by+cz+d=0, need d */ 347 348 float d = -1*(normal.x()*v0.x() + normal.y()*v0.y()+normal.z()*v0.z()); 349 /* Now loop over a grid, skipping points not in the triangle */ 350 int x_steps = (int) (max_x - min_x)/separation; 351 int y_steps = (int) (max_y - min_y)/separation; 352 SGVec2f v02d = SGVec2f(v0.x(),v0.y()); 353 SGVec2f v12d = SGVec2f(v1.x(),v1.y()); 354 SGVec2f v22d = SGVec2f(v2.x(),v2.y()); 355 356 for (int jx = 0; jx < x_steps; jx++) { 357 float ptx = min_x + jx * separation; 358 359 for (int jy = 0; jy < y_steps; jy++) { 360 float pty = min_y + jy * separation; 361 SGVec2f newpt = SGVec2f(ptx,pty); 362 if (!point_in_triangle(newpt,v02d,v12d,v22d)) 363 continue; 364 365 // z = (-ax-by-d)/c; c is not zero as 366 // that would be alpha of 1.0 367 368 float ptz = (-normal.x()*ptx - normal.y()*pty-d)/normal.z(); 369 SGVec3f randomPoint = SGVec3f(ptx,pty,ptz); 370 371 if (object_mask != NULL) { 372 // Check this point against the object mask 373 // green (for trees) channel. 374 osg::Image* img = object_mask->getImage(); 375 unsigned int x = (int) (img->s() * newpt.x()) % img->s(); 376 unsigned int y = (int) (img->t() * newpt.y()) % img->t(); 377 378 if (mt_rand(&seed) < img->getColor(x, y).g()) { 379 // The red channel contains the rotation for this object 380 points.push_back(randomPoint); 381 normals.push_back(normalize(normal)); 382 } 383 } else { 384 points.push_back(randomPoint); 385 normals.push_back(normalize(normal)); 386 } 387 } 388 } 389 } else { 390 // Determine the number of trees, taking into account vegetation 391 // density (which is linear) and the slope density factor. 392 // Use a zombie door method to create the proper random chance 393 // of a tree being created for partial values. 394 int woodcount = (int) (vegetation_density * vegetation_density * 395 slope_density * 396 area / wood_coverage + mt_rand(&seed)); 397 for (int j = 0; j < woodcount; j++) { 398 // Use barycentric coordinates 399 float a = mt_rand(&seed); 400 float b = mt_rand(&seed); 401 402 if ( a + b > 1.0f ) { 403 a = 1.0f - a; 404 b = 1.0f - b; 405 } 406 407 float c = 1.0f - a - b; 408 409 SGVec3f randomPoint = a*v0 + b*v1 + c*v2; 410 if (object_mask != NULL) { 411 SGVec2f texCoord = a*t0 + b*t1 + c*t2; 412 413 // Check this random point against the object mask 414 // green (for trees) channel. 415 osg::Image* img = object_mask->getImage(); 416 unsigned int x = (int) (img->s() * texCoord.x()) % img->s(); 417 unsigned int y = (int) (img->t() * texCoord.y()) % img->t(); 418 419 if (mt_rand(&seed) < img->getColor(x, y).g()) { 420 // The red channel contains the rotation for this object 421 points.push_back(randomPoint); 422 normals.push_back(normalize(normal)); 423 } 424 } else { 425 points.push_back(randomPoint); 426 normals.push_back(normalize(normal)); 427 428 } 429 } 430 } 431 } 432 } 433 } 434 } 435 436 #if 0 437 // debug : this will save the tile as a shapefile that can be viewed in QGIS. 438 // NOTE: this is really slow.... 439 // remember - we need to de-rotate the tile, then translate back to gbs_center. 440 void dumpBorder() { 441 //dump the first triangle only of the first geometry, for now... 442 SG_LOG(SG_TERRAIN, SG_ALERT, "effect geode has " << geometries.size() << " geometries" ); 443 444 const osg::Vec3Array* vertices = dynamic_cast<osg::Vec3Array*>(geometries[0]->getVertexArray()); 445 if ( vertices ) { 446 SG_LOG(SG_TERRAIN, SG_ALERT, " geometry has " << vertices->getNumElements() << " vertices" ); 447 } 448 449 if ( !geometries.empty() ) { 450 int numPrimitiveSets = geometries[0]->getNumPrimitiveSets(); 451 SG_LOG(SG_TERRAIN, SG_ALERT, " geometry has " << numPrimitiveSets << " primitive sets" ); 452 453 if ( numPrimitiveSets > 0 ) { 454 const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0); 455 unsigned int numIndices = ps->getNumIndices(); 456 457 // create the same quat we used to rotate here 458 // - use backTransform to go back to original node location 459 SGGeod geodPos = SGGeod::fromCart(gbs_center); 460 SGQuatd hlOr = SGQuatd::fromLonLat(geodPos)*SGQuatd::fromEulerDeg(0, 0, 180); 461 462 SG_LOG(SG_TERRAIN, SG_ALERT, " primitive set has has " << numIndices << " indices" ); 463 for ( unsigned int i=2; i<numIndices; i+= 3 ) { 464 if ( numIndices >= 3 ) { 465 unsigned int v0i = ps->index(i-2); 466 unsigned int v1i = ps->index(i-1); 467 unsigned int v2i = ps->index(i-0); 468 469 const osg::Vec3 *v0 = &vertices->operator[](v0i); 470 const osg::Vec3 *v1 = &vertices->operator[](v1i); 471 const osg::Vec3 *v2 = &vertices->operator[](v2i); 472 473 // de-rotate and translate : todo - create a paralell vertex list so we just do this 474 // once per vertex, not for every triangle's use of the vertex 475 SGVec3d vec0 = hlOr.backTransform( toVec3d(toSG(*v0))) + gbs_center; 476 SGVec3d vec1 = hlOr.backTransform( toVec3d(toSG(*v1))) + gbs_center; 477 SGVec3d vec2 = hlOr.backTransform( toVec3d(toSG(*v2))) + gbs_center; 478 479 // convert from cartesian to Geodetic, and save as a list of Geods for output 480 std::vector<SGGeod> triangle; 481 triangle.push_back( SGGeod::fromCart(vec0) ); 482 triangle.push_back( SGGeod::fromCart(vec1) ); 483 triangle.push_back( SGGeod::fromCart(vec2) ); 484 485 SGShapefile::FromGeodList( triangle, true, "./triangles", mat->get_names()[0], "tri" ); 486 } 487 } 488 } 489 490 } 491 } 492 #endif 493 494 private: 495 mt seed; 496 SGMaterial* mat; 497 SGVec3d gbs_center; 498 std::vector<osg::Geometry*> geometries; 499 std::vector<int> polygon_border; // TODO 500 }; 501 502 // This visitor will generate an SGTriangleInfo. 503 // currently, it looks like it could save multiple lists, which could be the case 504 // if multiple osg::geods are found with osg::Geometry. 505 // But right now, we store a single PrimitiveSet under a single EffectGeod. 506 // so the traversal should only find a single EffectGeod - building a single SGTriangleInfo 507 class GetNodeTriangles : public osg::NodeVisitor 508 { 509 public: GetNodeTriangles(const SGVec3d & c,std::vector<SGTriangleInfo> * nt)510 GetNodeTriangles(const SGVec3d& c, std::vector<SGTriangleInfo>* nt) : osg::NodeVisitor( osg::NodeVisitor::TRAVERSE_ALL_CHILDREN ), center(c), nodeTris(nt) {} 511 512 // This method gets called for every node in the scene 513 // graph. Check each node to see if it has user 514 // out target. If so, save the node's address. apply(osg::Node & node)515 virtual void apply( osg::Node& node ) 516 { 517 EffectGeode* eg = dynamic_cast<EffectGeode*>(&node); 518 if ( eg ) { 519 // get the material from the user info 520 SGTriangleInfo triInfo( center ); 521 triInfo.setMaterial( eg->getMaterial() ); 522 523 // let's find the drawables for this node 524 int numDrawables = eg->getNumDrawables(); 525 for ( int i=0; i<numDrawables; i++ ) { 526 triInfo.addGeometry( eg->getDrawable(i)->asGeometry() ); 527 } 528 529 nodeTris->push_back( triInfo ); 530 } 531 532 // Keep traversing the rest of the scene graph. 533 traverse( node ); 534 } 535 536 protected: 537 SGVec3d center; 538 std::vector<SGTriangleInfo>* nodeTris; 539 }; 540