227 lines
10 KiB
C
227 lines
10 KiB
C
|
/**
|
||
|
* PANDA 3D SOFTWARE
|
||
|
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||
|
*
|
||
|
* All use of this software is subject to the terms of the revised BSD
|
||
|
* license. You should have received a copy of this license along
|
||
|
* with this source code in a file named "LICENSE."
|
||
|
*
|
||
|
* @file eggBinMaker.h
|
||
|
* @author drose
|
||
|
* @date 1999-01-21
|
||
|
*/
|
||
|
|
||
|
#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
|
||
|
* void prepare_node(EggNode *node); This method is called, once, on each node
|
||
|
* in the egg hierarchy as it is visited the first time. It allows the
|
||
|
* subclass a chance to analyze the node or do any other initial processing.
|
||
|
* This is a fine opportunity to tag an EggUserData onto the node, for
|
||
|
* instance. 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, EggNode *child); 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 "pnotify.h"
|
||
|
|
||
|
#include "pset.h"
|
||
|
#include "pmap.h"
|
||
|
|
||
|
class EggNode;
|
||
|
class EggGroup;
|
||
|
class EggGroupNode;
|
||
|
class EggBin;
|
||
|
class EggBinMaker;
|
||
|
|
||
|
/**
|
||
|
* This is just an STL function object, used to sort nodes within EggBinMaker.
|
||
|
* It's part of the private interface; ignore it.
|
||
|
*/
|
||
|
class EXPCL_PANDA_EGG 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;
|
||
|
};
|
||
|
|
||
|
|
||
|
/**
|
||
|
* 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_PANDA_EGG EggBinMaker : public EggObject {
|
||
|
PUBLISHED:
|
||
|
EggBinMaker();
|
||
|
~EggBinMaker();
|
||
|
|
||
|
int make_bins(EggGroupNode *root_group);
|
||
|
|
||
|
virtual void
|
||
|
prepare_node(EggNode *node);
|
||
|
|
||
|
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 std::string
|
||
|
get_bin_name(int bin_number, const EggNode *child);
|
||
|
|
||
|
virtual PT(EggBin)
|
||
|
make_bin(int bin_number, const EggNode *child, EggGroup *collapse_from);
|
||
|
|
||
|
private:
|
||
|
// The logic is two-pass. First, we make a scene graph traversal and store
|
||
|
// all the pointers into the GroupNodesSortedNodes 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 BinsNodes 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
|