]> git.mxchange.org Git - simgear.git/commitdiff
Separate out the SGMaterial::Object{,Group} code into it's own source file.
authorcurt <curt>
Thu, 15 May 2003 15:08:39 +0000 (15:08 +0000)
committercurt <curt>
Thu, 15 May 2003 15:08:39 +0000 (15:08 +0000)
simgear/scene/material/Makefile.am
simgear/scene/material/mat.cxx
simgear/scene/material/mat.hxx
simgear/scene/material/matobj.cxx [new file with mode: 0644]
simgear/scene/material/matobj.hxx [new file with mode: 0644]

index 2ad6e66f4acce5e0bf26f9cd0bf32dfc15eb1960..f781c0016a74497801ec273bf3dd47214ad54d42 100644 (file)
@@ -6,10 +6,12 @@ noinst_HEADERS =
 
 include_HEADERS = \
        mat.hxx \
-       matlib.hxx
+       matlib.hxx \
+       matobj.hxx
 
 libsgmaterial_a_SOURCES = \
        mat.cxx \
-       matlib.cxx
+       matlib.cxx \
+       matobj.cxx
 
 INCLUDES = -I$(top_srcdir)
index 77f74969e26dc0fdf096658e51897e97e3f8eeb3..d81947e75f0ca13df680c38cb507408d55e6ecf5 100644 (file)
@@ -66,187 +66,6 @@ local_file_exists( const string& path ) {
 }
 
 
-\f
-////////////////////////////////////////////////////////////////////////
-// Implementation of SGMaterial::Object.
-////////////////////////////////////////////////////////////////////////
-
-SGMaterial::Object::Object (const SGPropertyNode * node, double range_m)
-  : _models_loaded(false),
-    _coverage_m2(node->getDoubleValue("coverage-m2", 1000000)),
-    _range_m(range_m)
-{
-                               // Sanity check
-  if (_coverage_m2 < 1000) {
-    SG_LOG(SG_INPUT, SG_ALERT, "Random object coverage " << _coverage_m2
-          << " is too small, forcing, to 1000");
-    _coverage_m2 = 1000;
-  }
-
-                               // Note all the model paths
-  vector <SGPropertyNode_ptr> path_nodes = node->getChildren("path");
-  for (unsigned 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;
-  } else if (hdg == "billboard") {
-    _heading_type = HEADING_BILLBOARD;
-  } else if (hdg == "random") {
-    _heading_type = HEADING_RANDOM;
-  } else {
-    _heading_type = HEADING_FIXED;
-    SG_LOG(SG_INPUT, SG_ALERT, "Unknown heading type: " << hdg
-          << "; using 'fixed' instead.");
-  }
-
-  // uncomment to preload models
-  // load_models();
-}
-
-SGMaterial::Object::~Object ()
-{
-  for (unsigned int i = 0; i < _models.size(); i++) {
-    if (_models[i] != 0) {
-      _models[i]->deRef();
-      _models[i] = 0;
-    }
-  }
-}
-
-int
-SGMaterial::Object::get_model_count( SGModelLoader *loader,
-                                   const string &fg_root,
-                                   SGPropertyNode *prop_root,
-                                   double sim_time_sec )
-{
-  load_models( loader, fg_root, prop_root, sim_time_sec );
-  return _models.size();
-}
-
-inline void
-SGMaterial::Object::load_models ( SGModelLoader *loader,
-                                const string &fg_root,
-                                SGPropertyNode *prop_root,
-                                double sim_time_sec )
-{
-                               // Load model only on demand
-  if (!_models_loaded) {
-    for (unsigned int i = 0; i < _paths.size(); i++) {
-      ssgEntity *entity = loader->load_model( fg_root, _paths[i],
-                                              prop_root, sim_time_sec );
-      if (entity != 0) {
-                                // FIXME: this stuff can be handled
-                                // in the XML wrapper as well (at least,
-                                // the billboarding should be handled
-                                // there).
-       float ranges[] = {0, _range_m};
-       ssgRangeSelector * lod = new ssgRangeSelector;
-        lod->ref();
-        lod->setRanges(ranges, 2);
-       if (_heading_type == HEADING_BILLBOARD) {
-         ssgCutout * cutout = new ssgCutout(false);
-         cutout->addKid(entity);
-         lod->addKid(cutout);
-       } else {
-         lod->addKid(entity);
-       }
-       _models.push_back(lod);
-      } else {
-       SG_LOG(SG_INPUT, SG_ALERT, "Failed to load object " << _paths[i]);
-      }
-    }
-  }
-  _models_loaded = true;
-}
-
-ssgEntity *
-SGMaterial::Object::get_model( int index,
-                             SGModelLoader *loader,
-                             const string &fg_root,
-                             SGPropertyNode *prop_root,
-                             double sim_time_sec )
-{
-  load_models( loader, fg_root, prop_root, sim_time_sec ); // comment this out if preloading models
-  return _models[index];
-}
-
-ssgEntity *
-SGMaterial::Object::get_random_model( SGModelLoader *loader,
-                                    const string &fg_root,
-                                    SGPropertyNode *prop_root,
-                                    double sim_time_sec )
-{
-  load_models( loader, fg_root, prop_root, sim_time_sec ); // comment this out if preloading models
-  int nModels = _models.size();
-  int index = int(sg_random() * nModels);
-  if (index >= nModels)
-    index = 0;
-  return _models[index];
-}
-
-double
-SGMaterial::Object::get_coverage_m2 () const
-{
-  return _coverage_m2;
-}
-
-SGMaterial::Object::HeadingType
-SGMaterial::Object::get_heading_type () const
-{
-  return _heading_type;
-}
-
-
-\f
-////////////////////////////////////////////////////////////////////////
-// Implementation of SGMaterial::ObjectGroup.
-////////////////////////////////////////////////////////////////////////
-
-SGMaterial::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");
-  }
-}
-
-SGMaterial::ObjectGroup::~ObjectGroup ()
-{
-  for (unsigned int i = 0; i < _objects.size(); i++) {
-    delete _objects[i];
-    _objects[i] = 0;
-  }
-}
-
-double
-SGMaterial::ObjectGroup::get_range_m () const
-{
-  return _range_m;
-}
-
-int
-SGMaterial::ObjectGroup::get_object_count () const
-{
-  return _objects.size();
-}
-
-SGMaterial::Object *
-SGMaterial::ObjectGroup::get_object (int index) const
-{
-  return _objects[index];
-}
-
-
 \f
 ////////////////////////////////////////////////////////////////////////
 // Constructors and destructor.
@@ -334,7 +153,7 @@ SGMaterial::read_properties( const string &fg_root, const SGPropertyNode * props
   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]));
+    object_groups.push_back(new SGMatObjectGroup(object_group_nodes[i]));
 }
 
 
@@ -429,4 +248,4 @@ void SGMaterial::set_ssg_state( ssgSimpleState *s )
     texture_loaded = true;
 }
 
-// end of newmat.cxx
+// end of mat.cxx
index 66bf19e9735ee8dc176709be9a735f04e08fa6eb..12e545452c6f6e9d2ddcee3daa2b0c17236c9f26 100644 (file)
@@ -40,6 +40,8 @@
 #include <simgear/props/props.hxx>
 #include <simgear/scene/model/loader.hxx>
 
+#include "matobj.hxx"
+
 SG_USING_STD(string);
 
 
@@ -56,169 +58,6 @@ class SGMaterial {
 
 public:
 
-\f
-  //////////////////////////////////////////////////////////////////////
-  // Inner classes.
-  //////////////////////////////////////////////////////////////////////
-
-  class ObjectGroup;
-
-  /**
-   * A randomly-placeable object.
-   *
-   * SGMaterial uses this class to keep track of the model(s) and
-   * parameters for a single instance of a randomly-placeable object.
-   * The object can have more than one variant model (i.e. slightly
-   * different shapes of trees), but they are considered equivalent
-   * and interchangeable.
-   */
-  class Object
-  {
-  public:
-
-    /**
-     * The heading type for a randomly-placed object.
-     */
-    enum HeadingType {
-      HEADING_FIXED,
-      HEADING_BILLBOARD,
-      HEADING_RANDOM
-    };
-
-
-    /**
-     * Get the number of variant models available for the object.
-     *
-     * @return The number of variant models.
-     */
-    int get_model_count( SGModelLoader *loader,
-                         const string &fg_root,
-                         SGPropertyNode *prop_root,
-                         double sim_time_sec );
-
-
-    /**
-     * Get a specific variant model for the object.
-     *
-     * @param index The index of the model.
-     * @return The model.
-     */
-    ssgEntity *get_model( int index,
-                          SGModelLoader *loader,
-                          const string &fg_root,
-                          SGPropertyNode *prop_root,
-                          double sim_time_sec );
-
-
-    /**
-     * Get a randomly-selected variant model for the object.
-     *
-     * @return A randomly select model from the variants.
-     */
-    ssgEntity *get_random_model( SGModelLoader *loader,
-                                 const string &fg_root,
-                                 SGPropertyNode *prop_root,
-                                 double sim_time_sec );
-
-
-    /**
-     * Get the average number of meters^2 occupied by each instance.
-     *
-     * @return The coverage in meters^2.
-     */
-    double get_coverage_m2 () const;
-
-
-    /**
-     * Get the heading type for the object.
-     *
-     * @return The heading type.
-     */
-    HeadingType get_heading_type () const;
-
-  protected:
-
-    friend class ObjectGroup;
-
-    Object (const SGPropertyNode * node, double range_m);
-
-    virtual ~Object ();
-
-  private:
-
-    /**
-     * Actually load the models.
-     *
-     * This class uses lazy loading so that models won't be held
-     * in memory for materials that are never referenced.
-     */
-    void load_models( SGModelLoader *loader,
-                      const string &fg_root,
-                      SGPropertyNode *prop_root,
-                      double sim_time_sec );
-
-    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.
-   *
-   * Grouping objects with the same range together significantly
-   * reduces the memory requirements of randomly-placed objects.
-   * Each SGMaterial instance keeps a (possibly-empty) list of
-   * object groups for placing randomly on the scenery.
-   */
-  class ObjectGroup
-  {
-  public:
-    virtual ~ObjectGroup ();
-
-
-    /**
-     * Get the visual range of the object in meters.
-     *
-     * @return The visual range.
-     */
-    double get_range_m () const;
-
-
-    /**
-     * Get the number of objects in the group.
-     *
-     * @return The number of objects.
-     */
-    int get_object_count () const;
-
-
-    /**
-     * Get a specific object.
-     *
-     * @param index The object's index, zero-based.
-     * @return The object selected.
-     */
-    Object * get_object (int index) const;
-
-  protected:
-
-    friend class SGMaterial;
-
-    ObjectGroup (SGPropertyNode * node);
-
-  private:
-
-    double _range_m;
-    vector<Object *> _objects;
-
-  };
-
-
-
 \f
   ////////////////////////////////////////////////////////////////////
   // Public Constructors.
@@ -311,7 +150,7 @@ public:
   /**
    * Get a randomly-placed object for this material.
    */
-  virtual ObjectGroup * get_object_group (int index) const {
+  virtual SGMatObjectGroup * get_object_group (int index) const {
     return object_groups[index];
   }
 
@@ -383,7 +222,7 @@ private:
   // true if texture loading deferred, and not yet loaded
   bool texture_loaded;
 
-  vector<ObjectGroup *> object_groups;
+  vector<SGMatObjectGroup *> object_groups;
 
   // ref count so we can properly delete if we have multiple
   // pointers to this record
diff --git a/simgear/scene/material/matobj.cxx b/simgear/scene/material/matobj.cxx
new file mode 100644 (file)
index 0000000..9f581d6
--- /dev/null
@@ -0,0 +1,250 @@
+// matobj.cxx -- class to handle material properties
+//
+// Written by Curtis Olson, started May 1998.
+//
+// Copyright (C) 1998 - 2003  Curtis L. Olson  - curt@flightgear.org
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+//
+// $Id$
+
+
+#ifdef HAVE_CONFIG_H
+#  include <simgear_config.h>
+#endif
+
+#include <simgear/compiler.h>
+
+#include <map>
+SG_USING_STD(map);
+
+#include <simgear/compiler.h>
+
+#ifdef SG_MATH_EXCEPTION_CLASH
+#  include <math.h>
+#endif
+
+#include <simgear/debug/logstream.hxx>
+#include <simgear/math/sg_random.h>
+#include <simgear/misc/sg_path.hxx>
+#include <simgear/misc/sgstream.hxx>
+#include <simgear/scene/model/loader.hxx>
+
+#include "matobj.hxx"
+
+\f
+////////////////////////////////////////////////////////////////////////
+// Local static functions.
+////////////////////////////////////////////////////////////////////////
+
+/**
+ * Internal method to test whether a file exists.
+ *
+ * TODO: this should be moved to a SimGear library of local file
+ * functions.
+ */
+static inline bool
+local_file_exists( const string& path ) {
+    sg_gzifstream in( path );
+    if ( ! in.is_open() ) {
+       return false;
+    } else {
+       return true;
+    }
+}
+
+
+\f
+////////////////////////////////////////////////////////////////////////
+// Implementation of SGMatObject.
+////////////////////////////////////////////////////////////////////////
+
+SGMatObject::SGMatObject (const SGPropertyNode * node, double range_m)
+  : _models_loaded(false),
+    _coverage_m2(node->getDoubleValue("coverage-m2", 1000000)),
+    _range_m(range_m)
+{
+                               // Sanity check
+  if (_coverage_m2 < 1000) {
+    SG_LOG(SG_INPUT, SG_ALERT, "Random object coverage " << _coverage_m2
+          << " is too small, forcing, to 1000");
+    _coverage_m2 = 1000;
+  }
+
+                               // Note all the model paths
+  vector <SGPropertyNode_ptr> path_nodes = node->getChildren("path");
+  for (unsigned 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;
+  } else if (hdg == "billboard") {
+    _heading_type = HEADING_BILLBOARD;
+  } else if (hdg == "random") {
+    _heading_type = HEADING_RANDOM;
+  } else {
+    _heading_type = HEADING_FIXED;
+    SG_LOG(SG_INPUT, SG_ALERT, "Unknown heading type: " << hdg
+          << "; using 'fixed' instead.");
+  }
+
+  // uncomment to preload models
+  // load_models();
+}
+
+SGMatObject::~SGMatObject ()
+{
+  for (unsigned int i = 0; i < _models.size(); i++) {
+    if (_models[i] != 0) {
+      _models[i]->deRef();
+      _models[i] = 0;
+    }
+  }
+}
+
+int
+SGMatObject::get_model_count( SGModelLoader *loader,
+                                   const string &fg_root,
+                                   SGPropertyNode *prop_root,
+                                   double sim_time_sec )
+{
+  load_models( loader, fg_root, prop_root, sim_time_sec );
+  return _models.size();
+}
+
+inline void
+SGMatObject::load_models ( SGModelLoader *loader,
+                                const string &fg_root,
+                                SGPropertyNode *prop_root,
+                                double sim_time_sec )
+{
+                               // Load model only on demand
+  if (!_models_loaded) {
+    for (unsigned int i = 0; i < _paths.size(); i++) {
+      ssgEntity *entity = loader->load_model( fg_root, _paths[i],
+                                              prop_root, sim_time_sec );
+      if (entity != 0) {
+                                // FIXME: this stuff can be handled
+                                // in the XML wrapper as well (at least,
+                                // the billboarding should be handled
+                                // there).
+       float ranges[] = {0, _range_m};
+       ssgRangeSelector * lod = new ssgRangeSelector;
+        lod->ref();
+        lod->setRanges(ranges, 2);
+       if (_heading_type == HEADING_BILLBOARD) {
+         ssgCutout * cutout = new ssgCutout(false);
+         cutout->addKid(entity);
+         lod->addKid(cutout);
+       } else {
+         lod->addKid(entity);
+       }
+       _models.push_back(lod);
+      } else {
+       SG_LOG(SG_INPUT, SG_ALERT, "Failed to load object " << _paths[i]);
+      }
+    }
+  }
+  _models_loaded = true;
+}
+
+ssgEntity *
+SGMatObject::get_model( int index,
+                               SGModelLoader *loader,
+                               const string &fg_root,
+                               SGPropertyNode *prop_root,
+                               double sim_time_sec )
+{
+  load_models( loader, fg_root, prop_root, sim_time_sec ); // comment this out if preloading models
+  return _models[index];
+}
+
+ssgEntity *
+SGMatObject::get_random_model( SGModelLoader *loader,
+                                      const string &fg_root,
+                                      SGPropertyNode *prop_root,
+                                      double sim_time_sec )
+{
+  load_models( loader, fg_root, prop_root, sim_time_sec ); // comment this out if preloading models
+  int nModels = _models.size();
+  int index = int(sg_random() * nModels);
+  if (index >= nModels)
+    index = 0;
+  return _models[index];
+}
+
+double
+SGMatObject::get_coverage_m2 () const
+{
+  return _coverage_m2;
+}
+
+SGMatObject::HeadingType
+SGMatObject::get_heading_type () const
+{
+  return _heading_type;
+}
+
+
+\f
+////////////////////////////////////////////////////////////////////////
+// Implementation of SGMatObjectGroup.
+////////////////////////////////////////////////////////////////////////
+
+SGMatObjectGroup::SGMatObjectGroup (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 SGMatObject(object_node, _range_m));
+    else
+      SG_LOG(SG_INPUT, SG_ALERT, "No path supplied for object");
+  }
+}
+
+SGMatObjectGroup::~SGMatObjectGroup ()
+{
+  for (unsigned int i = 0; i < _objects.size(); i++) {
+    delete _objects[i];
+    _objects[i] = 0;
+  }
+}
+
+double
+SGMatObjectGroup::get_range_m () const
+{
+  return _range_m;
+}
+
+int
+SGMatObjectGroup::get_object_count () const
+{
+  return _objects.size();
+}
+
+SGMatObject *
+SGMatObjectGroup::get_object (int index) const
+{
+  return _objects[index];
+}
+
+
+// end of matobj.cxx
diff --git a/simgear/scene/material/matobj.hxx b/simgear/scene/material/matobj.hxx
new file mode 100644 (file)
index 0000000..8d92b5f
--- /dev/null
@@ -0,0 +1,205 @@
+// matobj.hxx -- a material in the scene graph.
+// TODO: this class needs to be renamed.
+//
+// Written by Curtis Olson, started May 1998.
+// Overhauled by David Megginson, December 2001
+//
+// Copyright (C) 1998 - 2003  Curtis L. Olson  - curt@flightgear.org
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+//
+// $Id$
+
+
+#ifndef _SG_MAT_OBJ_HXX
+#define _SG_MAT_OBJ_HXX
+
+#ifndef __cplusplus                                                          
+# error This library requires C++
+#endif                                   
+
+#include <simgear/compiler.h>
+
+#include STL_STRING      // Standard C++ string library
+
+#include <plib/sg.h>
+#include <plib/ssg.h>
+
+#include <simgear/props/props.hxx>
+#include <simgear/scene/model/loader.hxx>
+
+SG_USING_STD(string);
+
+
+class SGMatObjectGroup;
+
+
+/**
+ * A randomly-placeable object.
+ *
+ * SGMaterial uses this class to keep track of the model(s) and
+ * parameters for a single instance of a randomly-placeable object.
+ * The object can have more than one variant model (i.e. slightly
+ * different shapes of trees), but they are considered equivalent
+ * and interchangeable.
+ */
+class SGMatObject {
+
+public:
+
+    /**
+     * The heading type for a randomly-placed object.
+     */
+    enum HeadingType {
+        HEADING_FIXED,
+        HEADING_BILLBOARD,
+        HEADING_RANDOM
+    };
+
+    /**
+     * Get the number of variant models available for the object.
+     *
+     * @return The number of variant models.
+     */
+    int get_model_count( SGModelLoader *loader,
+                         const string &fg_root,
+                         SGPropertyNode *prop_root,
+                         double sim_time_sec );
+
+
+    /**
+     * Get a specific variant model for the object.
+     *
+     * @param index The index of the model.
+     * @return The model.
+     */
+    ssgEntity *get_model( int index,
+                          SGModelLoader *loader,
+                          const string &fg_root,
+                          SGPropertyNode *prop_root,
+                          double sim_time_sec );
+
+
+    /**
+     * Get a randomly-selected variant model for the object.
+     *
+     * @return A randomly select model from the variants.
+     */
+    ssgEntity *get_random_model( SGModelLoader *loader,
+                                 const string &fg_root,
+                                 SGPropertyNode *prop_root,
+                                 double sim_time_sec );
+
+
+    /**
+     * Get the average number of meters^2 occupied by each instance.
+     *
+     * @return The coverage in meters^2.
+     */
+    double get_coverage_m2 () const;
+
+
+    /**
+     * Get the heading type for the object.
+     *
+     * @return The heading type.
+     */
+    HeadingType get_heading_type () const;
+
+protected:
+
+    friend class SGMatObjectGroup;
+
+    SGMatObject (const SGPropertyNode * node, double range_m);
+
+    virtual ~SGMatObject ();
+
+private:
+
+    /**
+     * Actually load the models.
+     *
+     * This class uses lazy loading so that models won't be held
+     * in memory for materials that are never referenced.
+     */
+    void load_models( SGModelLoader *loader,
+                      const string &fg_root,
+                      SGPropertyNode *prop_root,
+                      double sim_time_sec );
+
+    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.
+ *
+ * Grouping objects with the same range together significantly
+ * reduces the memory requirements of randomly-placed objects.
+ * Each SGMaterial instance keeps a (possibly-empty) list of
+ * object groups for placing randomly on the scenery.
+ */
+class SGMatObjectGroup {
+
+public:
+
+    virtual ~SGMatObjectGroup ();
+
+
+    /**
+     * Get the visual range of the object in meters.
+     *
+     * @return The visual range.
+     */
+    double get_range_m () const;
+
+
+    /**
+     * Get the number of objects in the group.
+     *
+     * @return The number of objects.
+     */
+    int get_object_count () const;
+
+
+    /**
+     * Get a specific object.
+     *
+     * @param index The object's index, zero-based.
+     * @return The object selected.
+     */
+    SGMatObject * get_object (int index) const;
+
+protected:
+
+    friend class SGMaterial;
+
+    SGMatObjectGroup (SGPropertyNode * node);
+
+private:
+
+    double _range_m;
+    vector<SGMatObject *> _objects;
+
+};
+
+
+
+#endif // _SG_MAT_OBJ_HXX