// Filename: eggBinMaker.h
// Created by:  drose (21Jan99)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
//
// All use of this software is subject to the terms of the Panda 3d
// Software license.  You should have received a copy of this license
// along with this source code; you will also find a current copy of
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
//
// To contact the maintainers of this program write to
// panda3d-general@lists.sourceforge.net .
//
////////////////////////////////////////////////////////////////////

#ifndef EGGBINMAKER_H
#define EGGBINMAKER_H

///////////////////////////////////////////////////////////////////
//
// EggBinMaker
//
// This is a handy class for collecting related nodes together.  Its
// purpose is to make it easier to process egg files for converting to
// another scene graph format.  Egg is very general and allows nodes
// to be parented willy-nilly anywhere you like, while many other
// scene graph formats have requirements that certain kinds of nodes
// be grouped together.
//
// Although EggBinMaker can be used to group any kinds of nodes
// together, one of the most common examples is grouping polygons into
// polysets.  Egg allows individual polygons to be parented directly
// to any group node, while most scene graph formats prefer to have
// polygons with similar attributes grouped into some kind of a
// polyset node.  Therefore, the following usage discussion will use
// grouping polygons into polysets as an example.
//
// EggBinMaker is actually an abstract class; it cannot be used
// directly.  To use it, you must create a subclass and redefine some
// or all of its virtual functions to specify the precise behavior you
// require.
//
// You must define at least the following function:
//
// virtual int get_bin_number(const EggNode *node);
//
//    This function identifies the kinds of nodes in the graph, for
//    instance EggPolygons, that are to be put into bins.  It will be
//    called once for each node encountered, and it should return
//    nonzero if the node is to be binned, and zero otherwise.  To
//    group polygons into polysets, this function might look like:
//
//        virtual int get_bin_number(const EggNode *node) {
//          if (node->is_of_type(EggPolygon::get_class_type())) {
//            return 1;
//          } else {
//            return 0;
//          }
//        }
//
//
//    This function may also return the bin number that a given node
//    should be dropped into.  The bin number is completely arbitrary,
//    and it just serves to differentiate different bins.
//
//    By default, all sibling nodes will be dropped into the same bin;
//    you can redefine this to sort nodes further into categories.
//    For instance, if you wanted to put textured polygons into a
//    different polyset than untextured polygons, you might define
//    this function as follows:
//
//        virtual int get_bin_number(const EggNode *node) {
//          if (node->is_of_type(EggPolygon::get_class_type())) {
//            EggPolygon *poly = DCAST(EggPolygon, node);
//            return (poly->has_texture()) ? 1 : 2;
//          } else {
//            return 0;
//          }
//        }
//
//    Of course, unrelated nodes--nodes that belong to different
//    parents--will never be placed into the same bin together,
//    regardless of the bin number.
//
//    It is important to note that it is not necessarily true that
//    there is only one bin for each bin number.  If you redefine
//    sorts_less(), below, you provide a finer-grained control that
//    may create multiple bins for a given bin number.
//
//    This function may be called several times for a given node, and
//    it should return the same number each time.
//
//
// You may also redefine any or all of the following functions:
//
// virtual bool sorts_less(int bin_number, const EggNode *a, const EggNode *b);
//
//    Sometimes a simple bin number alone is not enough.  For
//    instance, suppose you needed to group together not just all
//    textured polygons, but all polygons that shared a particular
//    texture map.  Two polygons that are each textured with a
//    different texture map should go into different polysets.  To do
//    this with bin numbers, you'd have to know ahead of time all the
//    texture maps that are in use, and assign a unique number to each
//    one.
//
//    sorts_less() can make this unnecessary.  It's a finer-grained
//    sorting than by bin numbers.  Once two nodes have been grouped
//    together into the same bin number, sorts_less is called on them.
//    If it returns true, then node a should be placed into an earlier
//    bin than node b, even though they share the same bin number.  If
//    sorts_less(a, b) and sorts_less(b, a) both return false, then
//    nodes a and b are placed into the same bin.
//
//    To continue the example, and sort polygons into different bins
//    based on the texture map:
//
//        virtual bool sorts_less(int bin_number,
//                                const EggNode *a, const EggNode *b) {
//          if (bin_number == 2) {
//            // bin 2, textured geometry
//            return (a->get_texture() < b->get_texture());
//          } else {
//            // bin 1, untextured geometry
//            return false;
//          }
//        }
//
//    The actual comparison can be arbitrary, as long as it is
//    consistent.  Its only purpose is to assign some ordering among
//    bins.  In the example, for instance, the comparison is based on
//    the pointer to the texture maps--it doesn't matter which comes
//    before the other, as long as it's consistent.
//
//    In particular, it should never be true that sorts_less(a, b) and
//    sorts_less(b, a) both return true--that is a clear
//    contradiction.
//
//    Of course, if you're using sorts_less() anyway, you could put
//    *all* of the logic for binning into this function; there's no
//    need to use both get_bin_number() and sorts_less(), necessarily.
//    In the current example, here's another version of sorts_less()
//    that accomplishes the same thing as the combined effects of the
//    above get_bin_number() and sorts_less() working together:
//
//        virtual bool sorts_less(int bin_number,
//                                const EggNode *a, const EggNode *b) {
//          if (a->has_texture() != b->has_texture()) {
//            return ((int)a->has_texture() < (int)b->has_texture());
//          }
//          if (a->has_texture()) {
//            return (a->get_texture() < b->get_texture());
//          }
//          return false;
//        }
//
//
// virtual bool collapse_group(const EggGroup *group, int bin_number);
//
//    After all the nodes have been assigned to bins and the
//    individual bins (polysets) have been created, it might turn out
//    that some groups have had all their children placed into the
//    same bin.  In this case, the group node is now redundant, since
//    it contains just the one child, the new EggBin (polyset) node.
//    It might be advantageous to remove the group and collapse its
//    properties into the new node.
//
//    In this case (and this case only), collapse_group() will be
//    called, given the node and the bin number.  If it returns true,
//    the node will indeed be collapsed into its bin; otherwise, they
//    will be left separate.
//
//    The point is that there might be some attributes in the group
//    node (for instance, a matrix transform) that cannot be
//    represented in a polyset node in the new scene graph format, so
//    there may be some cases in which the group cannot be safely
//    collapsed.  Since the egg library cannot know about which such
//    cases cause problems, it leaves it up to you.  The default
//    behavior is never to collapse nodes.
//
//
// virtual string get_bin_name(int bin_number);
//
//    This function is called as each new bin is created, to
//    optionally define a name for the new node.  If it returns the
//    empty string, the node name will be empty, unless it was
//    collapsed with its parent group, in which case it will inherit
//    its former parent's name.
//
//
//
// Once you have subclassed EggBinMaker and defined the functions as
// you require, you use it by simply calling make_bins() one or more
// times, passing it the pointer to the root of the scene graph or of
// some subgraph.  It will traverse the subgraph and create a series
// of EggBin objects, as required, moving all the binned geometry
// under the EggBin objects.  The return value is the number of
// EggBins created.  Each EggBin stores its bin number, which may be
// retrieved via get_bin_number().
//
///////////////////////////////////////////////////////////////////


#include "pandabase.h"

#include "eggObject.h"

#include "pointerTo.h"
#include "notify.h"

#include "pset.h"
#include "pmap.h"

class EggNode;
class EggGroup;
class EggGroupNode;
class EggBin;
class EggBinMaker;

////////////////////////////////////////////////////////////////////
//       Class : EggBinMakerCompareNodes
// Description : This is just an STL function object, used to sort
//               nodes within EggBinMaker.  It's part of the private
//               interface; ignore it.
////////////////////////////////////////////////////////////////////
class EXPCL_PANDAEGG EggBinMakerCompareNodes {
public:
  EggBinMakerCompareNodes() {
    // We need to have a default constructor to compile, but it should
    // never be called.
    nassertv(false);
  }
  EggBinMakerCompareNodes(EggBinMaker *ebm) : _ebm(ebm) { }
  bool operator ()(const EggNode *a, const EggNode *b) const;

  EggBinMaker *_ebm;
};


////////////////////////////////////////////////////////////////////
//       Class : EggBinMaker
// Description : This is a handy class for collecting related nodes
//               together.  It is an abstract class; to use it you
//               must subclass off of it.  See the somewhat lengthy
//               comment above.
////////////////////////////////////////////////////////////////////
class EXPCL_PANDAEGG EggBinMaker : public EggObject {
PUBLISHED:
  EggBinMaker();
  ~EggBinMaker();

  int make_bins(EggGroupNode *root_group);

  virtual int
  get_bin_number(const EggNode *node)=0;

  virtual bool
  sorts_less(int bin_number, const EggNode *a, const EggNode *b);

  virtual bool
  collapse_group(const EggGroup *group, int bin_number);

  virtual string
  get_bin_name(int bin_number);

private:
  // The logic is two-pass.  First, we make a scene graph traversal
  // and store all the pointers into the GroupNodes/SortedNodes
  // structure, which groups nodes by their parent group, and then
  // sorted into bin order.
  typedef pmultiset<PT(EggNode), EggBinMakerCompareNodes> SortedNodes;
  typedef pmap<EggGroupNode *, SortedNodes> GroupNodes;

  // Then we walk through that list and create a Bins/Nodes structure
  // for each group, which separates out the nodes into the individual
  // bins.
  typedef pvector< PT(EggNode) > Nodes;
  typedef pvector<Nodes> Bins;

  void collect_nodes(EggGroupNode *group);
  int get_bins_for_group(GroupNodes::const_iterator gi);
  void make_bins_for_group(EggGroupNode *group, const Bins &bins);
  void setup_bin(EggBin *bin, const Nodes &nodes);

  GroupNodes _group_nodes;


public:

  static TypeHandle get_class_type() {
    return _type_handle;
  }
  static void init_type() {
    EggObject::init_type();
    register_type(_type_handle, "EggBinMaker",
                  EggObject::get_class_type());
  }
  virtual TypeHandle get_type() const {
    return get_class_type();
  }
  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}

private:
  static TypeHandle _type_handle;

};

#endif


