1
0
Fork 0

Randomly-place object overhaul and enhancements

-----------------------------------------------

Fixed a segfault on exit.

Changed the radius of the dummy bounding sphere from 10m to 1000m to
ensure that FOV culling doesn't leave anything out.

Allow an object to have more than one variant model, which will be
chosen randomly.  Simply repeat the <path>...</path> property.

Removed the <billboard> property and replaced it with <heading-type>,
which can be set to "fixed" (leave the model oriented as it is),
"random" (give the model a random heading between 0 and 359 deg), or
"billboard" (always turn the model to face the camera).  The default
is "fixed".  Models look much better when they are not all facing the
same direction.

Allow the user to group models with the same visual range, so that
there can be *many* fewer nodes in the scene graph when the models are
not visible.  This causes an XML-format change, so that instead of

  <object>
   <range-m>...</range-m>
   ...
  </object>
  <object>
   <range-m>...</range-m>
   ...
  </object>
  ...

we now have

  <object-group>
   <range-m>...</range-m>
   <object>
    ...
   </object>
   <object>
    ...
   </object>
   ...
  </object-group>

Every object in a group can still have its own model(s), coverage, and
heading-type, but they all share the same range selector.

This change should already help users with tight memory constraints,
but it will matter much more when we add more object types -- for
example, we can now add dozens of different urban building types
without bloating the scene graph or slowing down the LOD tests for
tris that are out of range (i.e. most of them).
This commit is contained in:
david 2002-07-20 14:56:37 +00:00
parent cecedd302b
commit 29268401b2
3 changed files with 265 additions and 158 deletions

View file

@ -37,6 +37,7 @@ SG_USING_STD(map);
#endif
#include <simgear/debug/logstream.hxx>
#include <simgear/math/sg_random.h>
#include <simgear/misc/sg_path.hxx>
#include <simgear/misc/sgstream.hxx>
@ -95,12 +96,17 @@ local_file_exists( const string& path ) {
// Implementation of FGNewMat::Object.
////////////////////////////////////////////////////////////////////////
FGNewMat::Object::Object (const SGPropertyNode * node)
: _path(node->getStringValue("path")),
_model(0),
FGNewMat::Object::Object (const SGPropertyNode * node, double range_m)
: _models_loaded(false),
_coverage_m2(node->getDoubleValue("coverage-m2", 100000)),
_range_m(node->getDoubleValue("range-m", 2000))
_range_m(range_m)
{
// Note all the model paths
vector <SGPropertyNode_ptr> path_nodes = node->getChildren("path");
for (int i = 0; i < path_nodes.size(); i++)
_paths.push_back(path_nodes[i]->getStringValue());
// Note the heading type
string hdg = node->getStringValue("heading-type", "fixed");
if (hdg == "fixed") {
_heading_type = HEADING_FIXED;
@ -117,41 +123,67 @@ FGNewMat::Object::Object (const SGPropertyNode * node)
FGNewMat::Object::~Object ()
{
_model->deRef();
for (int i = 0; i < _models.size(); i++) {
if (_models[i] != 0) {
_models[i]->deRef();
_models[i] = 0;
}
}
}
const string &
FGNewMat::Object::get_path () const
int
FGNewMat::Object::get_model_count () const
{
return _path;
return _models.size();
}
inline void
FGNewMat::Object::load_models () const
{
// Load model only on demand
if (!_models_loaded) {
for (int i = 0; i < _paths.size(); i++) {
SGPath path = globals->get_fg_root();
path.append(_paths[i]);
ssgTexturePath((char *)path.dir().c_str());
ssgEntity * entity = load_object((char *)path.c_str());
if (entity != 0) {
float ranges[] = {0, _range_m};
ssgRangeSelector * lod = new ssgRangeSelector;
lod->setRanges(ranges, 2);
if (_heading_type == HEADING_BILLBOARD) {
ssgCutout * cutout = new ssgCutout(false);
cutout->addKid(entity);
lod->addKid(cutout);
} else {
lod->addKid(entity);
}
lod->ref();
_models.push_back(lod);
} else {
SG_LOG(SG_INPUT, SG_ALERT, "Failed to load object " << path.str());
}
}
}
_models_loaded = true;
}
ssgEntity *
FGNewMat::Object::get_model () const
FGNewMat::Object::get_model (int index) const
{
// Load model only on demand
if (_model == 0) {
SGPath path = globals->get_fg_root();
path.append(_path);
ssgTexturePath((char *)path.dir().c_str());
ssgEntity * entity = load_object((char *)path.c_str());
if (entity != 0) {
float ranges[] = {0, _range_m};
_model = new ssgRangeSelector;
((ssgRangeSelector *)_model)->setRanges(ranges, 2);
if (_heading_type == HEADING_BILLBOARD) {
ssgCutout * cutout = new ssgCutout(false);
cutout->addKid(entity);
((ssgBranch *)_model)->addKid(cutout);
} else {
((ssgBranch *)_model)->addKid(entity);
}
_model->ref();
} else {
SG_LOG(SG_INPUT, SG_ALERT, "Failed to load object " << path.str());
}
}
return _model;
load_models();
return _models[index];
}
ssgEntity *
FGNewMat::Object::get_random_model () const
{
load_models();
int nModels = _models.size();
int index = int(sg_random() * nModels);
if (index >= nModels)
index = 0;
return _models[index];
}
double
@ -160,12 +192,59 @@ FGNewMat::Object::get_coverage_m2 () const
return _coverage_m2;
}
FGNewMat::Object::HeadingType
FGNewMat::Object::get_heading_type () const
{
return _heading_type;
}
////////////////////////////////////////////////////////////////////////
// Implementation of FGNewMat::ObjectGroup.
////////////////////////////////////////////////////////////////////////
FGNewMat::ObjectGroup::ObjectGroup (SGPropertyNode * node)
: _range_m(node->getDoubleValue("range-m", 2000))
{
// Load the object subnodes
vector<SGPropertyNode_ptr> object_nodes =
((SGPropertyNode *)node)->getChildren("object");
for (unsigned int i = 0; i < object_nodes.size(); i++) {
const SGPropertyNode * object_node = object_nodes[i];
if (object_node->hasChild("path"))
_objects.push_back(new Object(object_node, _range_m));
else
SG_LOG(SG_INPUT, SG_ALERT, "No path supplied for object");
}
}
FGNewMat::ObjectGroup::~ObjectGroup ()
{
for (int i = 0; i < _objects.size(); i++) {
delete _objects[i];
_objects[i] = 0;
}
}
double
FGNewMat::Object::get_range_m () const
FGNewMat::ObjectGroup::get_range_m () const
{
return _range_m;
}
int
FGNewMat::ObjectGroup::get_object_count () const
{
return _objects.size();
}
FGNewMat::Object *
FGNewMat::ObjectGroup::get_object (int index) const
{
return _objects[index];
}
////////////////////////////////////////////////////////////////////////
@ -195,9 +274,9 @@ FGNewMat::FGNewMat (ssgSimpleState * s)
FGNewMat::~FGNewMat (void)
{
for (unsigned int i = 0; i < objects.size(); i++) {
delete objects[i];
objects[i] = 0;
for (unsigned int i = 0; i < object_groups.size(); i++) {
delete object_groups[i];
object_groups[i] = 0;
}
}
@ -249,15 +328,10 @@ FGNewMat::read_properties (const SGPropertyNode * props)
emission[2] = props->getDoubleValue("emissive/b", 0.0);
emission[3] = props->getDoubleValue("emissive/a", 0.0);
vector<SGPropertyNode_ptr> object_nodes =
((SGPropertyNode *)props)->getChildren("object");
for (unsigned int i = 0; i < object_nodes.size(); i++) {
const SGPropertyNode * object_node = object_nodes[i];
if (object_node->hasChild("path"))
objects.push_back(new Object(object_node));
else
SG_LOG(SG_INPUT, SG_ALERT, "No path supplied for object");
}
vector<SGPropertyNode_ptr> object_group_nodes =
((SGPropertyNode *)props)->getChildren("object-group");
for (unsigned int i = 0; i < object_group_nodes.size(); i++)
object_groups.push_back(new ObjectGroup(object_group_nodes[i]));
}

View file

@ -66,9 +66,10 @@ public:
//////////////////////////////////////////////////////////////////////
// Inner class.
// Inner classes.
//////////////////////////////////////////////////////////////////////
class ObjectGroup;
/**
* A randomly-placeable object.
@ -83,24 +84,46 @@ public:
HEADING_RANDOM
};
const string &get_path () const;
ssgEntity * get_model () const;
int get_model_count () const;
ssgEntity * get_model (int index) const;
ssgEntity * get_random_model () const;
double get_coverage_m2 () const;
double get_range_m () const;
HeadingType get_heading_type () const;
protected:
friend class FGNewMat;
Object (const SGPropertyNode * node);
friend class ObjectGroup;
Object (const SGPropertyNode * node, double range_m);
virtual ~Object ();
private:
string _path;
mutable ssgEntity * _model;
void load_models () const;
vector<string> _paths;
mutable vector<ssgEntity *> _models;
mutable bool _models_loaded;
double _coverage_m2;
double _range_m;
HeadingType _heading_type;
};
/**
* A collection of related objects with the same visual range.
*/
class ObjectGroup
{
public:
virtual ~ObjectGroup ();
double get_range_m () const;
int get_object_count () const;
Object * get_object (int index) const;
protected:
friend class FGNewMat;
ObjectGroup (SGPropertyNode * node);
private:
double _range_m;
vector<Object *> _objects;
};
////////////////////////////////////////////////////////////////////
// Public Constructors.
@ -187,13 +210,15 @@ public:
/**
* Get the number of randomly-placed objects defined for this material.
*/
virtual int get_object_count () const { return objects.size(); }
virtual int get_object_group_count () const { return object_groups.size(); }
/**
* Get a randomly-placed object for this material.
*/
virtual Object * get_object (int index) const { return objects[index]; }
virtual ObjectGroup * get_object_group (int index) const {
return object_groups[index];
}
/**
@ -270,7 +295,7 @@ private:
// true if texture loading deferred, and not yet loaded
bool texture_loaded;
vector<Object *> objects;
vector<ObjectGroup *> object_groups;
// ref count so we can properly delete if we have multiple
// pointers to this record

View file

@ -313,6 +313,54 @@ static void gen_random_surface_points( ssgLeaf *leaf, ssgVertexArray *lights,
}
/**
* Create a rotation matrix to align an object for the current lat/lon.
*
* By default, objects are aligned for the north pole. This code
* calculates a matrix to rotate them for the surface of the earth in
* the current location.
*
* TODO: there should be a single version of this method somewhere
* for all of SimGear.
*
* @param ROT The resulting rotation matrix.
* @param hdg_deg The object heading in degrees.
* @param lon_deg The longitude in degrees.
* @param lat_deg The latitude in degrees.
*/
static void
makeWorldUpRotationMatrix (sgMat4 ROT, double hdg_deg,
double lon_deg, double lat_deg)
{
SGfloat sin_lat = sin( lat_deg * SGD_DEGREES_TO_RADIANS );
SGfloat cos_lat = cos( lat_deg * SGD_DEGREES_TO_RADIANS );
SGfloat sin_lon = sin( lon_deg * SGD_DEGREES_TO_RADIANS );
SGfloat cos_lon = cos( lon_deg * SGD_DEGREES_TO_RADIANS );
SGfloat sin_hdg = sin( hdg_deg * SGD_DEGREES_TO_RADIANS ) ;
SGfloat cos_hdg = cos( hdg_deg * SGD_DEGREES_TO_RADIANS ) ;
ROT[0][0] = cos_hdg * sin_lat * cos_lon - sin_hdg * sin_lon;
ROT[0][1] = cos_hdg * sin_lat * sin_lon + sin_hdg * cos_lon;
ROT[0][2] = -cos_hdg * cos_lat;
ROT[0][3] = SG_ZERO;
ROT[1][0] = -sin_hdg * sin_lat * cos_lon - cos_hdg * sin_lon;
ROT[1][1] = -sin_hdg * sin_lat * sin_lon + cos_hdg * cos_lon;
ROT[1][2] = sin_hdg * cos_lat;
ROT[1][3] = SG_ZERO;
ROT[2][0] = cos_lat * cos_lon;
ROT[2][1] = cos_lat * sin_lon;
ROT[2][2] = sin_lat;
ROT[2][3] = SG_ZERO;
ROT[3][0] = SG_ZERO;
ROT[3][1] = SG_ZERO;
ROT[3][2] = SG_ZERO;
ROT[3][3] = SG_ONE ;
}
/**
* Add an object to a random location inside a triangle.
*
@ -320,20 +368,27 @@ static void gen_random_surface_points( ssgLeaf *leaf, ssgVertexArray *lights,
* @param p2 The second vertex of the triangle.
* @param p3 The third vertex of the triangle.
* @param center The center of the triangle.
* @param ROT The world-up rotation matrix.
* @param mat The material object.
* @param object_index The index of the dynamically-placed object in
* the material.
* @param lon_deg The longitude of the surface center, in degrees.
* @param lat_deg The latitude of the surface center, in degrees.
* @param object The randomly-placed object.
* @param branch The branch where the object should be added to the
* scene graph.
*/
static void
add_object_to_triangle (sgVec3 p1, sgVec3 p2, sgVec3 p3, sgVec3 center,
sgMat4 ROT, FGNewMat::Object * object,
ssgBranch * branch)
double lon_deg, double lat_deg,
FGNewMat::Object * object, ssgBranch * branch)
{
// Set up the random heading if required.
double hdg_deg = 0;
if (object->get_heading_type() == FGNewMat::Object::HEADING_RANDOM)
hdg_deg = sg_random() * 360;
sgVec3 result;
sgMat4 ROT;
makeWorldUpRotationMatrix(ROT, hdg_deg, lon_deg, lat_deg);
random_pt_inside_tri(result, p1, p2, p3);
sgSubVec3(result, center);
sgMat4 OBJ_pos, OBJ;
@ -342,7 +397,7 @@ add_object_to_triangle (sgVec3 p1, sgVec3 p2, sgVec3 p3, sgVec3 center,
sgPostMultMat4(OBJ, OBJ_pos);
ssgTransform * pos = new ssgTransform;
pos->setTransform(OBJ);
pos->addKid(object->get_model());
pos->addKid(object->get_random_model());
branch->addKid(pos);
}
@ -353,9 +408,10 @@ public:
float * p1;
float * p2;
float * p3;
FGNewMat::Object * object;
FGNewMat::ObjectGroup * object_group;
ssgBranch * branch;
sgMat4 ROT;
double lon_deg;
double lat_deg;
};
@ -371,33 +427,40 @@ public:
* @param mat The triangle's material.
* @param object_index The index of the random object in the triangle.
* @param branch The branch where the objects should be added.
* @param ROT The rotation matrix to align objects with the earth's
* surface.
* @param lon_deg The longitude of the surface center, in degrees.
* @param lat_deg The latitude of the surface center, in degrees.
*/
static void
fill_in_triangle (float * p1, float * p2, float * p3,
FGNewMat::Object *object, ssgBranch * branch, sgMat4 ROT)
FGNewMat::ObjectGroup * object_group, ssgBranch * branch,
double lon_deg, double lat_deg)
{
sgVec3 center;
sgSetVec3(center,
(p1[0] + p2[0] + p3[0]) / 3.0,
(p1[1] + p2[1] + p3[1]) / 3.0,
(p1[2] + p2[2] + p3[2]) / 3.0);
double area = sgTriArea(p1, p2, p3);
double num = area / object->get_coverage_m2();
int nObjects = object_group->get_object_count();
for (int i = 0; i < nObjects; i++) {
FGNewMat::Object * object = object_group->get_object(i);
sgVec3 center;
sgSetVec3(center,
(p1[0] + p2[0] + p3[0]) / 3.0,
(p1[1] + p2[1] + p3[1]) / 3.0,
(p1[2] + p2[2] + p3[2]) / 3.0);
double area = sgTriArea(p1, p2, p3);
double num = area / object->get_coverage_m2();
// place an object each unit of area
while ( num > 1.0 ) {
add_object_to_triangle(p1, p2, p3, center, ROT, object, branch);
num -= 1.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
if ( num > 0.0 ) {
if ( sg_random() <= num ) {
// a zombie made it through our door
add_object_to_triangle(p1, p2, p3, center, ROT, object, branch);
// place an object each unit of area
while ( num > 1.0 ) {
add_object_to_triangle(p1, p2, p3, center, lon_deg, lat_deg,
object, branch);
num -= 1.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
if ( num > 0.0 ) {
if ( sg_random() <= num ) {
// a zombie made it through our door
add_object_to_triangle(p1, p2, p3, center, lon_deg, lat_deg,
object, branch);
}
}
}
}
@ -420,8 +483,8 @@ in_range_callback (ssgEntity * entity, int mask)
{
RandomObjectUserData * data = (RandomObjectUserData *)entity->getUserData();
if (!data->is_filled_in) {
fill_in_triangle(data->p1, data->p2, data->p3, data->object,
data->branch, data->ROT);
fill_in_triangle(data->p1, data->p2, data->p3, data->object_group,
data->branch, data->lon_deg, data->lat_deg);
data->is_filled_in = true;
}
return 1;
@ -474,7 +537,7 @@ private:
DummyBSphereEntity ()
{
bsphere.setCenter(0, 0, 0);
bsphere.setRadius(10);
bsphere.setRadius(1000);
}
static DummyBSphereEntity * entity;
};
@ -532,12 +595,13 @@ get_bounding_radius (sgVec3 center, float *p1, float *p2, float *p3)
* @param mat The material data for the triangle.
* @param branch The branch to which the randomly-placed objects
* should be added.
* @param ROT A rotation matrix to align the objects with the earth's
* surface at the current lat/lon.
* @param lon_deg The longitude of the surface center, in degrees.
* @param lat_deg The latitude of the surface center, in degrees.
*/
static void
setup_triangle (float * p1, float * p2, float * p3,
FGNewMat * mat, ssgBranch * branch, sgMat4 ROT)
FGNewMat * mat, ssgBranch * branch,
double lon_deg, double lat_deg)
{
// Set up a single center point for LOD
sgVec3 center;
@ -559,17 +623,17 @@ setup_triangle (float * p1, float * p2, float * p3,
branch->addKid(location);
// Iterate through all the object types.
int num_objects = mat->get_object_count();
for (int i = 0; i < num_objects; i++) {
int num_groups = mat->get_object_group_count();
for (int i = 0; i < num_groups; i++) {
// Look up the random object.
FGNewMat::Object * object = mat->get_object(i);
FGNewMat::ObjectGroup * group = mat->get_object_group(i);
// Set up the range selector for the entire
// triangle; note that we use the object
// range plus the bounding radius here, to
// allow for objects far from the center.
float ranges[] = {0,
object->get_range_m() + bounding_radius,
group->get_range_m() + bounding_radius,
500000};
ssgRangeSelector * lod = new ssgRangeSelector;
lod->setRanges(ranges, 3);
@ -588,9 +652,10 @@ setup_triangle (float * p1, float * p2, float * p3,
data->p1 = p1;
data->p2 = p2;
data->p3 = p3;
data->object = object;
data->object_group = group;
data->branch = in_range;
sgCopyMat4(data->ROT, ROT);
data->lon_deg = lon_deg;
data->lat_deg = lat_deg;
// Set up the in-range node.
in_range->setUserData(data);
@ -608,54 +673,6 @@ setup_triangle (float * p1, float * p2, float * p3,
}
/**
* Create a rotation matrix to align an object for the current lat/lon.
*
* By default, objects are aligned for the north pole. This code
* calculates a matrix to rotate them for the surface of the earth in
* the current location.
*
* TODO: there should be a single version of this method somewhere
* for all of SimGear.
*
* @param ROT The resulting rotation matrix.
* @param hdg_deg The object heading in degrees.
* @param lon_deg The longitude in degrees.
* @param lat_deg The latitude in degrees.
*/
void
makeWorldUpRotationMatrix (sgMat4 ROT, double hdg_deg,
double lon_deg, double lat_deg)
{
SGfloat sin_lat = sin( lat_deg * SGD_DEGREES_TO_RADIANS );
SGfloat cos_lat = cos( lat_deg * SGD_DEGREES_TO_RADIANS );
SGfloat sin_lon = sin( lon_deg * SGD_DEGREES_TO_RADIANS );
SGfloat cos_lon = cos( lon_deg * SGD_DEGREES_TO_RADIANS );
SGfloat sin_hdg = sin( hdg_deg * SGD_DEGREES_TO_RADIANS ) ;
SGfloat cos_hdg = cos( hdg_deg * SGD_DEGREES_TO_RADIANS ) ;
ROT[0][0] = cos_hdg * sin_lat * cos_lon - sin_hdg * sin_lon;
ROT[0][1] = cos_hdg * sin_lat * sin_lon + sin_hdg * cos_lon;
ROT[0][2] = -cos_hdg * cos_lat;
ROT[0][3] = SG_ZERO;
ROT[1][0] = -sin_hdg * sin_lat * cos_lon - cos_hdg * sin_lon;
ROT[1][1] = -sin_hdg * sin_lat * sin_lon + cos_hdg * cos_lon;
ROT[1][2] = sin_hdg * cos_lat;
ROT[1][3] = SG_ZERO;
ROT[2][0] = cos_lat * cos_lon;
ROT[2][1] = cos_lat * sin_lon;
ROT[2][2] = sin_lat;
ROT[2][3] = SG_ZERO;
ROT[3][0] = SG_ZERO;
ROT[3][1] = SG_ZERO;
ROT[3][2] = SG_ZERO;
ROT[3][3] = SG_ONE ;
}
/**
* Randomly place objects on a surface.
*
@ -677,8 +694,6 @@ gen_random_surface_objects (ssgLeaf *leaf,
float lat_deg,
const string &material_name)
{
float hdg_deg = 0.0; // do something here later
// First, look up the material
// for this surface.
FGNewMat * mat = material_lib.find(material_name);
@ -689,8 +704,7 @@ gen_random_surface_objects (ssgLeaf *leaf,
// If the material has no randomly-placed
// objects, return now.
int num_objects = mat->get_object_count();
if (num_objects < 1)
if (mat->get_object_group_count() < 1)
return;
// If the surface has no triangles, return
@ -699,12 +713,6 @@ gen_random_surface_objects (ssgLeaf *leaf,
if (num_tris < 1)
return;
// Make a rotation matrix to align the
// object for this point on the earth's
// surface.
sgMat4 ROT;
makeWorldUpRotationMatrix(ROT, hdg_deg, lon_deg, lat_deg);
// generate a repeatable random seed
sg_srandom((unsigned int)(leaf->getVertex(0)[0]));
@ -716,7 +724,7 @@ gen_random_surface_objects (ssgLeaf *leaf,
setup_triangle(leaf->getVertex(n1),
leaf->getVertex(n2),
leaf->getVertex(n3),
mat, branch, ROT);
mat, branch, lon_deg, lat_deg);
}
}