panda3d/panda/src/egg/eggGroupNode.cxx
2008-11-04 18:25:21 +00:00

2054 lines
73 KiB
C++

// Filename: eggGroupNode.cxx
// Created by: drose (16Jan99)
//
////////////////////////////////////////////////////////////////////
//
// 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."
//
////////////////////////////////////////////////////////////////////
#include "eggGroupNode.h"
#include "eggCoordinateSystem.h"
#include "eggData.h"
#include "eggFilenameNode.h"
#include "eggExternalReference.h"
#include "eggPrimitive.h"
#include "eggPolygon.h"
#include "eggCompositePrimitive.h"
#include "eggMesher.h"
#include "eggVertexPool.h"
#include "eggVertex.h"
#include "eggTextureCollection.h"
#include "eggMaterialCollection.h"
#include "pt_EggTexture.h"
#include "pt_EggMaterial.h"
#include "config_egg.h"
#include "dSearchPath.h"
#include "deg_2_rad.h"
#include "dcast.h"
#include "bamCacheRecord.h"
#include <algorithm>
TypeHandle EggGroupNode::_type_handle;
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::Copy constructor
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
EggGroupNode::
EggGroupNode(const EggGroupNode &copy) : EggNode(copy) {
if (!copy.empty()) {
egg_cat.warning()
<< "The EggGroupNode copy constructor does not copy children!\n";
}
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::Copy assignment operator
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
EggGroupNode &EggGroupNode::
operator =(const EggGroupNode &copy) {
if (!copy.empty()) {
egg_cat.warning()
<< "The EggGroupNode copy assignment does not copy children!\n";
}
EggNode::operator =(copy);
return *this;
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::Destructor
// Access: Published, Virtual
// Description:
////////////////////////////////////////////////////////////////////
EggGroupNode::
~EggGroupNode() {
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::write
// Access: Published, Virtual
// Description: Writes the group and all of its children to the
// indicated output stream in Egg format.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
write(ostream &out, int indent_level) const {
iterator i;
// Since joints tend to reference vertex pools, which sometimes
// appear later in the file, and since generally non-joints don't
// reference joints, we try to maximize our chance of writing out a
// one-pass readable egg file by writing joints at the end of the
// list of children of a particular node.
for (i = begin(); i != end(); ++i) {
PT(EggNode) child = (*i);
if (!child->is_joint()) {
child->write(out, indent_level);
}
}
for (i = begin(); i != end(); ++i) {
PT(EggNode) child = (*i);
if (child->is_joint()) {
child->write(out, indent_level);
}
}
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::begin
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
EggGroupNode::iterator EggGroupNode::
begin() const {
return _children.begin();
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::end
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
EggGroupNode::iterator EggGroupNode::
end() const {
return _children.end();
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::rbegin
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
EggGroupNode::reverse_iterator EggGroupNode::
rbegin() const {
return _children.rbegin();
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::rend
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
EggGroupNode::reverse_iterator EggGroupNode::
rend() const {
return _children.rend();
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::insert
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
EggGroupNode::iterator EggGroupNode::
insert(iterator position, PT(EggNode) x) {
prepare_add_child(x);
return _children.insert((Children::iterator &)position, x);
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::erase
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
EggGroupNode::iterator EggGroupNode::
erase(iterator position) {
prepare_remove_child(*position);
return _children.erase((Children::iterator &)position);
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::erase
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
EggGroupNode::iterator EggGroupNode::
erase(iterator first, iterator last) {
iterator i;
for (i = first; i != last; ++i) {
prepare_remove_child(*i);
}
return _children.erase((Children::iterator &)first,
(Children::iterator &)last);
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::replace
// Access: Published
// Description: Replaces the node at the indicated position with
// the indicated node. It is an error to call this
// with an invalid position iterator (e.g. end()).
////////////////////////////////////////////////////////////////////
void EggGroupNode::
replace(iterator position, PT(EggNode) x) {
nassertv(position != end());
prepare_remove_child(*position);
prepare_add_child(x);
*(Children::iterator &)position = x;
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::empty
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
bool EggGroupNode::
empty() const {
return _children.empty();
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::size
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
EggGroupNode::size_type EggGroupNode::
size() const {
return _children.size();
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::clear
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
void EggGroupNode::
clear() {
erase(begin(), end());
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::get_first_child
// Access: Published
// Description: Returns the first child in the group's list of
// children, or NULL if the list of children is empty.
// Can be used with get_next_child() to return the
// complete list of children without using the iterator
// class; however, this is non-thread-safe, and so is
// not recommended except for languages other than C++
// which cannot use the iterators.
////////////////////////////////////////////////////////////////////
EggNode *EggGroupNode::
get_first_child() {
_gnc_iterator = begin();
return get_next_child();
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::get_next_child
// Access: Published
// Description: Returns the next child in the group's list of
// children since the last call to get_first_child() or
// get_next_child(), or NULL if the last child has been
// returned. Can be used with get_first_child() to
// return the complete list of children without using
// the iterator class; however, this is non-thread-safe,
// and so is not recommended except for languages other
// than C++ which cannot use the iterators.
//
// It is an error to call this without previously
// calling get_first_child().
////////////////////////////////////////////////////////////////////
EggNode *EggGroupNode::
get_next_child() {
if (_gnc_iterator != end()) {
return *_gnc_iterator++;
}
return NULL;
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::add_child
// Access: Published
// Description: Adds the indicated child to the group and returns it.
// If the child node is already a child of some other
// node, removes it first.
////////////////////////////////////////////////////////////////////
EggNode *EggGroupNode::
add_child(EggNode *node) {
test_ref_count_integrity();
PT(EggNode) ptnode = node;
if (node->_parent != NULL) {
node->_parent->remove_child(node);
}
prepare_add_child(node);
_children.push_back(node);
return node;
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::remove_child
// Access: Published
// Description: Removes the indicated child node from the group and
// returns it. If the child was not already in the
// group, does nothing and returns NULL.
////////////////////////////////////////////////////////////////////
PT(EggNode) EggGroupNode::
remove_child(EggNode *node) {
PT(EggNode) ptnode = node;
iterator i = find(begin(), end(), ptnode);
if (i == end()) {
return PT(EggNode)();
} else {
// erase() calls prepare_remove_child().
erase(i);
return ptnode;
}
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::steal_children
// Access: Published
// Description: Moves all the children from the other node to this
// one. This is especially useful because the group
// node copy assignment operator does not copy children.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
steal_children(EggGroupNode &other) {
Children::iterator ci;
for (ci = other._children.begin();
ci != other._children.end();
++ci) {
other.prepare_remove_child(*ci);
prepare_add_child(*ci);
}
_children.splice(_children.end(), other._children);
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::find_child
// Access: Published
// Description: Returns the child of this node whose name is the
// indicated string, or NULL if there is no child of
// this node by that name. Does not search recursively.
////////////////////////////////////////////////////////////////////
EggNode *EggGroupNode::
find_child(const string &name) const {
Children::const_iterator ci;
for (ci = _children.begin(); ci != _children.end(); ++ci) {
EggNode *child = (*ci);
if (child->get_name() == name) {
return child;
}
}
return NULL;
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::has_absolute_pathnames
// Access: Published
// Description: Returns true if any nodes at this level and below
// include a reference to a file via an absolute
// pathname, or false if all references are relative.
////////////////////////////////////////////////////////////////////
bool EggGroupNode::
has_absolute_pathnames() const {
Children::const_iterator ci;
for (ci = _children.begin();
ci != _children.end();
++ci) {
EggNode *child = *ci;
if (child->is_of_type(EggTexture::get_class_type())) {
EggTexture *tex = DCAST(EggTexture, child);
if (!tex->get_filename().is_local()) {
if (egg_cat.is_debug()) {
egg_cat.debug()
<< "Absolute pathname: " << tex->get_filename()
<< "\n";
}
return true;
}
if (tex->has_alpha_filename()) {
if (!tex->get_alpha_filename().is_local()) {
if (egg_cat.is_debug()) {
egg_cat.debug()
<< "Absolute pathname: " << tex->get_alpha_filename()
<< "\n";
}
return true;
}
}
} else if (child->is_of_type(EggFilenameNode::get_class_type())) {
EggFilenameNode *fnode = DCAST(EggFilenameNode, child);
if (!fnode->get_filename().is_local()) {
if (egg_cat.is_debug()) {
egg_cat.debug()
<< "Absolute pathname: " << fnode->get_filename()
<< "\n";
}
return true;
}
} else if (child->is_of_type(EggGroupNode::get_class_type())) {
if (DCAST(EggGroupNode, child)->has_absolute_pathnames()) {
return true;
}
}
}
return false;
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::resolve_filenames
// Access: Published
// Description: Walks the tree and attempts to resolve any filenames
// encountered. This looks up filenames along the
// specified search path; it does not automatically
// search the model_path for missing files.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
resolve_filenames(const DSearchPath &searchpath) {
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
Children::iterator ci;
for (ci = _children.begin();
ci != _children.end();
++ci) {
EggNode *child = *ci;
if (child->is_of_type(EggTexture::get_class_type())) {
EggTexture *tex = DCAST(EggTexture, child);
Filename tex_filename = tex->get_filename();
vfs->resolve_filename(tex_filename, searchpath);
tex->set_filename(tex_filename);
if (tex->has_alpha_filename()) {
Filename alpha_filename = tex->get_alpha_filename();
vfs->resolve_filename(alpha_filename, searchpath);
tex->set_alpha_filename(alpha_filename);
}
} else if (child->is_of_type(EggFilenameNode::get_class_type())) {
EggFilenameNode *fnode = DCAST(EggFilenameNode, child);
Filename filename = fnode->get_filename();
vfs->resolve_filename(filename, searchpath, fnode->get_default_extension());
fnode->set_filename(filename);
} else if (child->is_of_type(EggGroupNode::get_class_type())) {
DCAST(EggGroupNode, child)->resolve_filenames(searchpath);
}
}
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::force_filenames
// Access: Published
// Description: Similar to resolve_filenames, but each non-absolute
// filename encountered is arbitrarily taken to be in
// the indicated directory, whether or not the so-named
// filename exists.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
force_filenames(const Filename &directory) {
Children::iterator ci;
for (ci = _children.begin();
ci != _children.end();
++ci) {
EggNode *child = *ci;
if (child->is_of_type(EggTexture::get_class_type())) {
EggTexture *tex = DCAST(EggTexture, child);
Filename tex_filename = tex->get_filename();
if (tex_filename.is_local()) {
tex->set_filename(Filename(directory, tex_filename));
}
if (tex->has_alpha_filename()) {
Filename alpha_filename = tex->get_alpha_filename();
if (alpha_filename.is_local()) {
tex->set_alpha_filename(Filename(directory, alpha_filename));
}
}
} else if (child->is_of_type(EggFilenameNode::get_class_type())) {
EggFilenameNode *fnode = DCAST(EggFilenameNode, child);
Filename filename = fnode->get_filename();
if (filename.is_local()) {
fnode->set_filename(Filename(directory, filename));
}
} else if (child->is_of_type(EggGroupNode::get_class_type())) {
DCAST(EggGroupNode, child)->force_filenames(directory);
}
}
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::reverse_vertex_ordering
// Access: Published
// Description: Reverses the vertex ordering of all polygons defined
// at this node and below. Does not change the surface
// normals, if any.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
reverse_vertex_ordering() {
Children::iterator ci;
for (ci = _children.begin();
ci != _children.end();
++ci) {
EggNode *child = *ci;
if (child->is_of_type(EggPrimitive::get_class_type())) {
EggPrimitive *prim = DCAST(EggPrimitive, child);
prim->reverse_vertex_ordering();
} else if (child->is_of_type(EggGroupNode::get_class_type())) {
DCAST(EggGroupNode, child)->reverse_vertex_ordering();
}
}
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::recompute_vertex_normals
// Access: Published
// Description: Recomputes all the vertex normals for polygon
// geometry at this group node and below so that they
// accurately reflect the vertex positions. A shared
// edge between two polygons (even in different groups)
// is considered smooth if the angle between the two
// edges is less than threshold degrees.
//
// This function also removes degenerate polygons that
// do not have enough vertices to define a normal. It
// does not affect normals for other kinds of primitives
// like Nurbs or Points.
//
// This function does not remove or adjust vertices in
// the vertex pool; it only adds new vertices with the
// correct normals. Thus, it is a good idea to call
// remove_unused_vertices() after calling this.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
recompute_vertex_normals(double threshold, CoordinateSystem cs) {
// First, collect all the vertices together with their shared
// polygons.
NVertexCollection collection;
r_collect_vertex_normals(collection, threshold, cs);
// Now bust them into separate groups according to the edge
// threshold. Two polygons that share a vertex belong in the same
// group only if the angle between their normals is within the
// threshold.
double cos_angle = cos(deg_2_rad(threshold));
NVertexCollection::iterator ci;
for (ci = collection.begin(); ci != collection.end(); ++ci) {
NVertexGroup &group = (*ci).second;
// Here's a group of polygons that share a vertex. Build up a new
// group that consists of just the first polygon and all the ones
// that are within threshold degrees from it.
NVertexGroup::iterator gi;
gi = group.begin();
while (gi != group.end()) {
const NVertexReference &base_ref = (*gi);
NVertexGroup new_group;
NVertexGroup leftover_group;
new_group.push_back(base_ref);
++gi;
while (gi != group.end()) {
const NVertexReference &ref = (*gi);
double dot = base_ref._normal.dot(ref._normal);
if (dot > cos_angle) {
// These polygons are close enough to the same angle.
new_group.push_back(ref);
} else {
// These polygons are not.
leftover_group.push_back(ref);
}
++gi;
}
// Now new_group is a collection of connected polygons and the
// vertices that connect them. Smooth these vertices.
do_compute_vertex_normals(new_group);
// And reset the group of remaining polygons.
group.swap(leftover_group);
gi = group.begin();
}
}
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::recompute_polygon_normals
// Access: Published
// Description: Recomputes all the polygon normals for polygon
// geometry at this group node and below so that they
// accurately reflect the vertex positions. Normals are
// removed from the vertices and defined only on
// polygons, giving the geometry a faceted appearance.
//
// This function also removes degenerate polygons that
// do not have enough vertices to define a normal. It
// does not affect normals for other kinds of primitives
// like Nurbs or Points.
//
// This function does not remove or adjust vertices in
// the vertex pool; it only adds new vertices with the
// normals removed. Thus, it is a good idea to call
// remove_unused_vertices() after calling this.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
recompute_polygon_normals(CoordinateSystem cs) {
Children::iterator ci, cnext;
ci = _children.begin();
while (ci != _children.end()) {
cnext = ci;
++cnext;
EggNode *child = *ci;
if (child->is_of_type(EggPolygon::get_class_type())) {
EggPolygon *polygon = DCAST(EggPolygon, child);
if (!polygon->recompute_polygon_normal(cs)) {
// The polygon is degenerate. Remove it.
prepare_remove_child(child);
_children.erase(ci);
} else {
// Remove the normal from each polygon vertex.
size_t num_vertices = polygon->size();
for (size_t i = 0; i < num_vertices; i++) {
EggVertex *vertex = polygon->get_vertex(i);
EggVertexPool *pool = vertex->get_pool();
if (vertex->has_normal()) {
EggVertex new_vertex(*vertex);
new_vertex.clear_normal();
EggVertex *unique = pool->create_unique_vertex(new_vertex);
unique->copy_grefs_from(*vertex);
polygon->set_vertex(i, unique);
}
}
}
} else if (child->is_of_type(EggGroupNode::get_class_type())) {
DCAST(EggGroupNode, child)->recompute_polygon_normals(cs);
}
ci = cnext;
}
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::strip_normals
// Access: Published
// Description: Removes all normals from primitives, and the vertices
// they reference, at this node and below.
//
// This function does not remove or adjust vertices in
// the vertex pool; it only adds new vertices with the
// normal removed. Thus, it is a good idea to call
// remove_unused_vertices() after calling this.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
strip_normals() {
Children::iterator ci;
for (ci = _children.begin(); ci != _children.end(); ++ci) {
EggNode *child = *ci;
if (child->is_of_type(EggPrimitive::get_class_type())) {
EggPrimitive *prim = DCAST(EggPrimitive, child);
prim->clear_normal();
// Remove the normal from each prim vertex.
size_t num_vertices = prim->size();
for (size_t i = 0; i < num_vertices; i++) {
EggVertex *vertex = prim->get_vertex(i);
EggVertexPool *pool = vertex->get_pool();
if (vertex->has_normal()) {
EggVertex new_vertex(*vertex);
new_vertex.clear_normal();
EggVertex *unique = pool->create_unique_vertex(new_vertex);
unique->copy_grefs_from(*vertex);
prim->set_vertex(i, unique);
}
}
} else if (child->is_of_type(EggGroupNode::get_class_type())) {
DCAST(EggGroupNode, child)->strip_normals();
}
}
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::recompute_tangent_binormal
// Access: Published
// Description: This function recomputes the tangent and binormal for
// the named texture coordinate set for all vertices at
// this level and below. Use the empty string for the
// default texture coordinate set.
//
// It is necessary for each vertex to already have a
// normal (or at least a polygon normal), as well as a
// texture coordinate in the named texture coordinate
// set, before calling this function. You might precede
// this with recompute_vertex_normals() to ensure that
// the normals exist.
//
// Like recompute_vertex_normals(), this function does
// not remove or adjust vertices in the vertex pool; it
// only adds new vertices with the new tangents and
// binormals computed. Thus, it is a good idea to call
// remove_unused_vertices() after calling this.
////////////////////////////////////////////////////////////////////
bool EggGroupNode::
recompute_tangent_binormal(const GlobPattern &uv_name) {
// First, collect all the vertices together with their shared
// polygons.
TBNVertexCollection collection;
r_collect_tangent_binormal(uv_name, collection);
// Now compute the tangent and binormal separately for each common
// group of vertices.
TBNVertexCollection::const_iterator ci;
for (ci = collection.begin(); ci != collection.end(); ++ci) {
const TBNVertexValue &value = (*ci).first;
const TBNVertexGroup &group = (*ci).second;
do_compute_tangent_binormal(value, group);
}
return true;
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::recompute_tangent_binormal
// Access: Published
// Description: This function recomputes the tangent and binormal for
// the named texture coordinate sets.
// Returns true if anything was done.
////////////////////////////////////////////////////////////////////
bool EggGroupNode::
recompute_tangent_binormal(const vector_string &names) {
bool changed = false;
for (vector_string::const_iterator si = names.begin();
si != names.end();
++si) {
GlobPattern uv_name(*si);
nout << "Computing tangent and binormal for \"" << uv_name << "\"\n";
recompute_tangent_binormal(uv_name);
changed = true;
}
return changed;
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::recompute_tangent_binormal_auto
// Access: Published
// Description: This function recomputes the tangent and binormal for
// any texture coordinate set that affects a normal map.
// Returns true if anything was done.
////////////////////////////////////////////////////////////////////
bool EggGroupNode::
recompute_tangent_binormal_auto() {
vector_string names;
EggTextureCollection texs;
EggTextureCollection::iterator eti;
texs.find_used_textures(this);
for (eti = texs.begin(); eti != texs.end(); eti++) {
EggTexture *eggtex = (*eti);
if ((eggtex->get_env_type() == EggTexture::ET_normal)||
(eggtex->get_env_type() == EggTexture::ET_normal_height)) {
string uv = eggtex->get_uv_name();
vector_string::iterator it = find(names.begin(), names.end(), uv);
if (it == names.end()) {
names.push_back(uv);
}
}
}
return recompute_tangent_binormal(names);
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::triangulate_polygons
// Access: Published
// Description: Replace all higher-order polygons at this point in
// the scene graph and below with triangles. Returns
// the total number of new triangles produced, less
// degenerate polygons removed.
//
// If flags contains T_polygon and T_convex, both
// concave and convex polygons will be subdivided into
// triangles; with only T_polygon, only concave polygons
// will be subdivided, and convex polygons will be
// largely unchanged.
////////////////////////////////////////////////////////////////////
int EggGroupNode::
triangulate_polygons(int flags) {
int num_produced = 0;
Children children_copy = _children;
Children::iterator ci;
for (ci = children_copy.begin();
ci != children_copy.end();
++ci) {
EggNode *child = (*ci);
if (child->is_of_type(EggPolygon::get_class_type())) {
if ((flags & T_polygon) != 0) {
EggPolygon *poly = DCAST(EggPolygon, child);
poly->triangulate_in_place((flags & T_convex) != 0);
}
} else if (child->is_of_type(EggCompositePrimitive::get_class_type())) {
if ((flags & T_composite) != 0) {
EggCompositePrimitive *comp = DCAST(EggCompositePrimitive, child);
comp->triangulate_in_place();
}
} else if (child->is_of_type(EggGroupNode::get_class_type())) {
if ((flags & T_recurse) != 0) {
num_produced += DCAST(EggGroupNode, child)->triangulate_polygons(flags);
}
}
}
num_produced += max(0, (int)(_children.size() - children_copy.size()));
return num_produced;
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::mesh_triangles
// Access: Published
// Description: Combine triangles together into triangle strips, at
// this group and below.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
mesh_triangles(int flags) {
EggMesher mesher;
mesher.mesh(this, (flags & T_flat_shaded) != 0);
if ((flags & T_recurse) != 0) {
EggGroupNode::iterator ci;
for (ci = begin(); ci != end(); ++ci) {
if ((*ci)->is_of_type(EggGroupNode::get_class_type())) {
EggGroupNode *group_child = DCAST(EggGroupNode, *ci);
group_child->mesh_triangles(flags);
}
}
}
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::rename_nodes
// Access: Published
// Description: Rename by stripping out the prefix
////////////////////////////////////////////////////////////////////
int EggGroupNode::
rename_nodes(vector_string strip_prefix, bool recurse) {
int num_renamed = 0;
for (unsigned int ni = 0; ni < strip_prefix.size(); ++ni) {
string axe_name = strip_prefix[ni];
if (this->get_name().substr(0, axe_name.size()) == axe_name) {
string new_name = this->get_name().substr(axe_name.size());
//cout << "renaming " << this->get_name() << "->" << new_name << endl;
this->set_name(new_name);
num_renamed += 1;
}
}
if (recurse) {
EggGroupNode::iterator ci;
for (ci = begin(); ci != end(); ++ci) {
if ((*ci)->is_of_type(EggGroupNode::get_class_type())) {
EggGroupNode *group_child = DCAST(EggGroupNode, *ci);
num_renamed += group_child->rename_nodes(strip_prefix, recurse);
}
else if ((*ci)->is_of_type(EggNode::get_class_type())) {
EggNode *node_child = DCAST(EggNode, *ci);
num_renamed += node_child->rename_node(strip_prefix);
}
}
}
return num_renamed;
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::remove_unused_vertices
// Access: Published
// Description: Removes all vertices from VertexPools within this
// group or below that are not referenced by at least
// one primitive. Also collapses together equivalent
// vertices, and renumbers all vertices after the
// operation so their indices are consecutive, beginning
// at zero. Returns the total number of vertices
// removed.
//
// Note that this operates on the VertexPools within
// this group level, without respect to primitives that
// reference these vertices (unlike other functions like
// strip_normals()). It is therefore most useful to
// call this on the EggData root, rather than on a
// subgroup within the hierarchy, since a VertexPool may
// appear anywhere in the hierarchy.
////////////////////////////////////////////////////////////////////
int EggGroupNode::
remove_unused_vertices(bool recurse) {
int num_removed = 0;
Children::iterator ci, cnext;
ci = _children.begin();
while (ci != _children.end()) {
cnext = ci;
++cnext;
EggNode *child = *ci;
if (child->is_of_type(EggVertexPool::get_class_type())) {
EggVertexPool *vpool = DCAST(EggVertexPool, child);
num_removed += vpool->remove_unused_vertices();
if (vpool->empty()) {
// If, after removing all the vertices, there's nothing left
// in the vertex pool, then remove the whole vertex pool.
_children.erase(ci);
}
} else if (child->is_of_type(EggGroupNode::get_class_type())) {
if (recurse) {
num_removed += DCAST(EggGroupNode, child)->remove_unused_vertices(recurse);
}
}
ci = cnext;
}
return num_removed;
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::remove_invalid_primitives
// Access: Published
// Description: Removes primitives at this level and below which
// appear to be degenerate; e.g. polygons with fewer
// than 3 vertices, etc. Returns the number of
// primitives removed.
////////////////////////////////////////////////////////////////////
int EggGroupNode::
remove_invalid_primitives(bool recurse) {
int num_removed = 0;
Children::iterator ci, cnext;
ci = _children.begin();
while (ci != _children.end()) {
cnext = ci;
++cnext;
EggNode *child = *ci;
if (child->is_of_type(EggPrimitive::get_class_type())) {
EggPrimitive *prim = DCAST(EggPrimitive, child);
if (!prim->cleanup()) {
_children.erase(ci);
num_removed++;
}
} else if (child->is_of_type(EggGroupNode::get_class_type())) {
if (recurse) {
num_removed += DCAST(EggGroupNode, child)->remove_invalid_primitives(recurse);
}
}
ci = cnext;
}
return num_removed;
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::clear_connected_shading
// Access: Published
// Description: Resets the connected_shading information on all
// primitives at this node and below, so that it may be
// accurately rederived by the next call to
// get_connected_shading().
//
// It may be a good idea to call
// remove_unused_vertices() as well, to establish the
// correct connectivity between common vertices.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
clear_connected_shading() {
Children::iterator ci;
for (ci = _children.begin(); ci != _children.end(); ++ci) {
EggNode *child = *ci;
if (child->is_of_type(EggPrimitive::get_class_type())) {
EggPrimitive *prim = DCAST(EggPrimitive, child);
prim->clear_connected_shading();
} else if (child->is_of_type(EggGroupNode::get_class_type())) {
DCAST(EggGroupNode, child)->clear_connected_shading();
}
}
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::get_connected_shading
// Access: Published
// Description: Queries the connected_shading information on all
// primitives at this node and below, to ensure that it
// has been completely filled in before we start mucking
// around with vertices.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
get_connected_shading() {
Children::iterator ci;
for (ci = _children.begin(); ci != _children.end(); ++ci) {
EggNode *child = *ci;
if (child->is_of_type(EggPrimitive::get_class_type())) {
EggPrimitive *prim = DCAST(EggPrimitive, child);
prim->get_connected_shading();
} else if (child->is_of_type(EggGroupNode::get_class_type())) {
DCAST(EggGroupNode, child)->get_connected_shading();
}
}
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::unify_attributes
// Access: Published
// Description: Applies per-vertex normal and color to all vertices,
// if they are in fact per-vertex (and different for
// each vertex), or moves them to the primitive if they
// are all the same.
//
// After this call, either the primitive will have
// normals or its vertices will, but not both. Ditto
// for colors.
//
// If use_connected_shading is true, each polygon is
// considered in conjunction with all connected
// polygons; otherwise, each polygon is considered
// individually.
//
// If allow_per_primitive is false, S_per_face or
// S_overall will treated like S_per_vertex: normals and
// colors will always be assigned to the vertices. In
// this case, there will never be per-primitive colors
// or normals after this call returns. On the other
// hand, if allow_per_primitive is true, then S_per_face
// means that normals and colors should be assigned to
// the primitives, and removed from the vertices, as
// described above.
//
// This may create redundant vertices in the vertex
// pool, so it may be a good idea to follow this up with
// remove_unused_vertices().
////////////////////////////////////////////////////////////////////
void EggGroupNode::
unify_attributes(bool use_connected_shading, bool allow_per_primitive,
bool recurse) {
Children::iterator ci;
for (ci = _children.begin(); ci != _children.end(); ++ci) {
EggNode *child = *ci;
if (child->is_of_type(EggPrimitive::get_class_type())) {
EggPrimitive *prim = DCAST(EggPrimitive, child);
EggPrimitive::Shading shading = EggPrimitive::S_per_vertex;
if (allow_per_primitive) {
shading = prim->get_shading();
if (use_connected_shading) {
shading = prim->get_connected_shading();
}
}
prim->unify_attributes(shading);
} else if (child->is_of_type(EggGroupNode::get_class_type())) {
if (recurse) {
DCAST(EggGroupNode, child)->unify_attributes
(use_connected_shading, allow_per_primitive, recurse);
}
}
}
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::apply_last_attribute
// Access: Published
// Description: Sets the last vertex of the triangle (or each
// component) to the primitive normal and/or color, if
// the primitive is flat-shaded. This reflects the
// OpenGL convention of storing flat-shaded properties on
// the last vertex, although it is not usually a
// convention in Egg.
//
// This may create redundant vertices in the vertex
// pool, so it may be a good idea to follow this up with
// remove_unused_vertices().
////////////////////////////////////////////////////////////////////
void EggGroupNode::
apply_last_attribute(bool recurse) {
Children::iterator ci;
for (ci = _children.begin(); ci != _children.end(); ++ci) {
EggNode *child = *ci;
if (child->is_of_type(EggPrimitive::get_class_type())) {
EggPrimitive *prim = DCAST(EggPrimitive, child);
prim->apply_last_attribute();
} else if (child->is_of_type(EggGroupNode::get_class_type())) {
if (recurse) {
DCAST(EggGroupNode, child)->apply_last_attribute(recurse);
}
}
}
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::apply_first_attribute
// Access: Published
// Description: Sets the first vertex of the triangle (or each
// component) to the primitive normal and/or color, if
// the primitive is flat-shaded. This reflects the
// DirectX convention of storing flat-shaded properties on
// the first vertex, although it is not usually a
// convention in Egg.
//
// This may create redundant vertices in the vertex
// pool, so it may be a good idea to follow this up with
// remove_unused_vertices().
////////////////////////////////////////////////////////////////////
void EggGroupNode::
apply_first_attribute(bool recurse) {
Children::iterator ci;
for (ci = _children.begin(); ci != _children.end(); ++ci) {
EggNode *child = *ci;
if (child->is_of_type(EggPrimitive::get_class_type())) {
EggPrimitive *prim = DCAST(EggPrimitive, child);
prim->apply_first_attribute();
} else if (child->is_of_type(EggGroupNode::get_class_type())) {
if (recurse) {
DCAST(EggGroupNode, child)->apply_first_attribute(recurse);
}
}
}
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::post_apply_flat_attribute
// Access: Published
// Description: Intended as a followup to apply_last_attribute(),
// this also sets an attribute on the first vertices of
// the primitive, if they don't already have an
// attribute set, just so they end up with *something*.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
post_apply_flat_attribute(bool recurse) {
Children::iterator ci;
for (ci = _children.begin(); ci != _children.end(); ++ci) {
EggNode *child = *ci;
if (child->is_of_type(EggPrimitive::get_class_type())) {
EggPrimitive *prim = DCAST(EggPrimitive, child);
prim->post_apply_flat_attribute();
} else if (child->is_of_type(EggGroupNode::get_class_type())) {
if (recurse) {
DCAST(EggGroupNode, child)->post_apply_flat_attribute(recurse);
}
}
}
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::has_primitives
// Access: Published, Virtual
// Description: Returns true if there are any primitives
// (e.g. polygons) defined within this group or below,
// false otherwise.
////////////////////////////////////////////////////////////////////
bool EggGroupNode::
has_primitives() const {
Children::const_iterator ci;
for (ci = _children.begin();
ci != _children.end();
++ci) {
if ((*ci)->has_primitives()) {
return true;
}
}
return false;
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::joint_has_primitives
// Access: Published, Virtual
// Description: Returns true if there are any primitives
// (e.g. polygons) defined within this group or below,
// but the search does not include nested joints.
////////////////////////////////////////////////////////////////////
bool EggGroupNode::
joint_has_primitives() const {
Children::const_iterator ci;
for (ci = _children.begin();
ci != _children.end();
++ci) {
EggNode *child = (*ci);
if (!child->is_joint()) {
if (child->joint_has_primitives()) {
return true;
}
}
}
return false;
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::has_normals
// Access: Published, Virtual
// Description: Returns true if any of the primitives (e.g. polygons)
// defined within this group or below have either face
// or vertex normals defined, false otherwise.
////////////////////////////////////////////////////////////////////
bool EggGroupNode::
has_normals() const {
Children::const_iterator ci;
for (ci = _children.begin();
ci != _children.end();
++ci) {
if ((*ci)->has_normals()) {
return true;
}
}
return false;
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::rebuild_vertex_pools
// Access: Published
// Description: Copies vertices used by the primitives at this group
// node (and below, if recurse is true) into one or more
// new vertex pools, and updates the primitives to
// reference these pools. It is up to the caller to
// parent the newly-created vertex pools somewhere
// appropriate in the egg hierarchy.
//
// No more than max_vertices will be placed into any one
// vertex pool. This is the sole criteria for splitting
// vertex pools.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
rebuild_vertex_pools(EggVertexPools &vertex_pools, unsigned int max_vertices,
bool recurse) {
Children::iterator ci;
for (ci = _children.begin(); ci != _children.end(); ++ci) {
EggNode *child = *ci;
if (child->is_of_type(EggPrimitive::get_class_type())) {
typedef pvector< PT(EggVertex) > Vertices;
Vertices vertices;
EggPrimitive *prim = DCAST(EggPrimitive, child);
// Copy all of the vertices out.
EggPrimitive::const_iterator pi;
for (pi = prim->begin(); pi != prim->end(); ++pi) {
vertices.push_back(*pi);
}
typedef pvector<EggAttributes> Attributes;
Attributes attributes;
if (prim->is_of_type(EggCompositePrimitive::get_class_type())) {
// A compositive primitive has the additional complication of
// dealing with its attributes.
EggCompositePrimitive *cprim = DCAST(EggCompositePrimitive, prim);
int i;
int num_components = cprim->get_num_components();
for (i = 0; i < num_components; i++) {
attributes.push_back(*cprim->get_component(i));
}
}
prim->clear();
// Now look for a new home for the vertices. First, see if any
// of the vertex pools we've already created already have a copy
// of each one of the vertices.
bool found_pool = false;
EggVertexPool *best_pool = NULL;
int best_new_vertices = 0;
Vertices new_vertices;
EggVertexPools::iterator vpi;
for (vpi = vertex_pools.begin();
vpi != vertex_pools.end() && !found_pool;
++vpi) {
EggVertexPool *vertex_pool = (*vpi);
int num_new_vertices = 0;
new_vertices.clear();
new_vertices.reserve(vertices.size());
Vertices::const_iterator vi;
for (vi = vertices.begin();
vi != vertices.end() && !found_pool;
++vi) {
EggVertex *vertex = (*vi);
EggVertex *new_vertex = vertex_pool->find_matching_vertex(*vertex);
new_vertices.push_back(new_vertex);
if (new_vertex == (EggVertex *)NULL) {
++num_new_vertices;
}
}
if (num_new_vertices == 0) {
// Great, we found a vertex pool that already shares all
// these vertices. No need to look any further.
found_pool = true;
} else if (vertex_pool->size() + num_new_vertices <= max_vertices) {
// We would have to add some vertices to this pool, so this
// vertex pool qualifies only if the number of vertices we
// have to add would still keep it within our limit.
if (best_pool == (EggVertexPool *)NULL ||
num_new_vertices < best_new_vertices) {
// This is currently our most favorable vertex pool.
best_pool = vertex_pool;
best_new_vertices = num_new_vertices;
}
}
}
if (!found_pool) {
if (best_pool == (EggVertexPool *)NULL) {
// There was no vertex pool that qualified. We will have to
// create a new vertex pool.
best_pool = new EggVertexPool("");
vertex_pools.push_back(best_pool);
}
new_vertices.clear();
new_vertices.reserve(vertices.size());
Vertices::const_iterator vi;
for (vi = vertices.begin(); vi != vertices.end(); ++vi) {
EggVertex *vertex = (*vi);
EggVertex *new_vertex = best_pool->create_unique_vertex(*vertex);
new_vertex->copy_grefs_from(*vertex);
new_vertices.push_back(new_vertex);
}
}
Vertices::const_iterator vi;
nassertv(new_vertices.size() == vertices.size());
for (vi = new_vertices.begin(); vi != new_vertices.end(); ++vi) {
EggVertex *new_vertex = (*vi);
nassertv(new_vertex != (EggVertex *)NULL);
prim->add_vertex(new_vertex);
}
if (prim->is_of_type(EggCompositePrimitive::get_class_type())) {
// Now restore the composite attributes.
EggCompositePrimitive *cprim = DCAST(EggCompositePrimitive, prim);
int i;
int num_components = cprim->get_num_components();
nassertv(num_components == (int)attributes.size());
for (i = 0; i < num_components; i++) {
cprim->set_component(i, &attributes[i]);
}
}
} else if (child->is_of_type(EggGroupNode::get_class_type())) {
if (recurse) {
DCAST(EggGroupNode, child)->rebuild_vertex_pools(vertex_pools, max_vertices, recurse);
}
}
}
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::update_under
// Access: Protected, Virtual
// Description: This function is called from within EggGroupNode
// whenever the parentage of the node has changed. It
// should update the depth and under_instance flags
// accordingly.
//
// Offset is the difference between the old depth value
// and the new value. It should be consistent with the
// supplied depth value. If it is not, we have some
// error.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
update_under(int depth_offset) {
EggNode::update_under(depth_offset);
Children::iterator ci;
for (ci = _children.begin();
ci != _children.end();
++ci) {
nassertv((*ci)->get_parent() == this);
(*ci)->update_under(depth_offset);
}
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::r_transform
// Access: Protected, Virtual
// Description: This is called from within the egg code by
// transform(). It applies a transformation matrix
// to the current node in some sensible way, then
// continues down the tree.
//
// The first matrix is the transformation to apply; the
// second is its inverse. The third parameter is the
// coordinate system we are changing to, or CS_default
// if we are not changing coordinate systems.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
r_transform(const LMatrix4d &mat, const LMatrix4d &inv,
CoordinateSystem to_cs) {
Children::iterator ci;
for (ci = _children.begin();
ci != _children.end();
++ci) {
(*ci)->r_transform(mat, inv, to_cs);
}
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::r_transform_vertices
// Access: Protected, Virtual
// Description: This is called from within the egg code by
// transform_vertices_only()(). It applies a
// transformation matrix to the current node in some
// sensible way (if the current node is a vertex pool
// with vertices), then continues down the tree.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
r_transform_vertices(const LMatrix4d &mat) {
Children::iterator ci;
for (ci = _children.begin();
ci != _children.end();
++ci) {
(*ci)->r_transform_vertices(mat);
}
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::r_mark_coordsys
// Access: Protected, Virtual
// Description: This is only called immediately after loading an egg
// file from disk, to propagate the value found in the
// CoordinateSystem entry (or the default Y-up
// coordinate system) to all nodes that care about what
// the coordinate system is.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
r_mark_coordsys(CoordinateSystem cs) {
Children::iterator ci;
for (ci = _children.begin();
ci != _children.end();
++ci) {
(*ci)->r_mark_coordsys(cs);
}
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::r_flatten_transforms
// Access: Protected, Virtual
// Description: The recursive implementation of flatten_transforms().
////////////////////////////////////////////////////////////////////
void EggGroupNode::
r_flatten_transforms() {
Children::iterator ci;
for (ci = _children.begin();
ci != _children.end();
++ci) {
(*ci)->r_flatten_transforms();
}
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::r_apply_texmats
// Access: Protected, Virtual
// Description: The recursive implementation of apply_texmats().
////////////////////////////////////////////////////////////////////
void EggGroupNode::
r_apply_texmats(EggTextureCollection &textures) {
Children::iterator ci;
for (ci = _children.begin();
ci != _children.end();
++ci) {
(*ci)->r_apply_texmats(textures);
}
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::find_coordsys_entry()
// Access: Protected
// Description: Walks the tree, looking for an EggCoordinateSystem
// entry. If one is found, extracts it and returns its
// value. If multiple entries are found, extracts all
// of them and returns CS_invalid if they disagree.
////////////////////////////////////////////////////////////////////
CoordinateSystem EggGroupNode::
find_coordsys_entry() {
CoordinateSystem coordsys = CS_default;
// We can do this ci/cnext iteration through the list as we modify
// it, only because we know this works with an STL list type
// container. If this were a vector or a set, this wouldn't
// necessarily work.
Children::iterator ci, cnext;
ci = _children.begin();
while (ci != _children.end()) {
cnext = ci;
++cnext;
EggNode *child = *ci;
if (child->is_of_type(EggCoordinateSystem::get_class_type())) {
CoordinateSystem new_cs =
DCAST(EggCoordinateSystem, child)->get_value();
// Now remove the CoordinateSystem entry from our child list.
prepare_remove_child(child);
_children.erase(ci);
if (new_cs != CS_default) {
if (coordsys != CS_default && coordsys != new_cs) {
coordsys = CS_invalid;
} else {
coordsys = new_cs;
}
}
} else if (child->is_of_type(EggGroupNode::get_class_type())) {
CoordinateSystem new_cs =
DCAST(EggGroupNode, child)->find_coordsys_entry();
if (new_cs != CS_default) {
if (coordsys != CS_default && coordsys != new_cs) {
coordsys = CS_invalid;
} else {
coordsys = new_cs;
}
}
}
ci = cnext;
}
return coordsys;
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::find_textures()
// Access: Protected
// Description: Walks the tree, looking for EggTextures. Each
// EggTexture that is found is removed from the
// hierarchy and added to the EggTextureCollection.
// Returns the number of EggTextures found.
////////////////////////////////////////////////////////////////////
int EggGroupNode::
find_textures(EggTextureCollection *collection) {
int num_found = 0;
// We can do this ci/cnext iteration through the list as we modify
// it, only because we know this works with an STL list type
// container. If this were a vector or a set, this wouldn't
// necessarily work.
Children::iterator ci, cnext;
ci = _children.begin();
while (ci != _children.end()) {
cnext = ci;
++cnext;
EggNode *child = *ci;
if (child->is_of_type(EggTexture::get_class_type())) {
PT_EggTexture tex = DCAST(EggTexture, child);
// Now remove the EggTexture entry from our child list.
prepare_remove_child(tex);
_children.erase(ci);
// And add it to the collection.
collection->add_texture(tex);
num_found++;
} else if (child->is_of_type(EggGroupNode::get_class_type())) {
num_found +=
DCAST(EggGroupNode, child)->find_textures(collection);
}
ci = cnext;
}
return num_found;
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::find_materials()
// Access: Protected
// Description: Walks the tree, looking for EggMaterials. Each
// EggMaterial that is found is removed from the
// hierarchy and added to the EggMaterialCollection.
// Returns the number of EggMaterials found.
////////////////////////////////////////////////////////////////////
int EggGroupNode::
find_materials(EggMaterialCollection *collection) {
int num_found = 0;
// We can do this ci/cnext iteration through the list as we modify
// it, only because we know this works with an STL list type
// container. If this were a vector or a set, this wouldn't
// necessarily work.
Children::iterator ci, cnext;
ci = _children.begin();
while (ci != _children.end()) {
cnext = ci;
++cnext;
EggNode *child = *ci;
if (child->is_of_type(EggMaterial::get_class_type())) {
PT_EggMaterial tex = DCAST(EggMaterial, child);
// Now remove the EggMaterial entry from our child list.
prepare_remove_child(tex);
_children.erase(ci);
// And add it to the collection.
collection->add_material(tex);
num_found++;
} else if (child->is_of_type(EggGroupNode::get_class_type())) {
num_found +=
DCAST(EggGroupNode, child)->find_materials(collection);
}
ci = cnext;
}
return num_found;
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::r_load_externals
// Access: Protected
// Description: Walks the tree and locates unloaded external
// reference nodes, which it attempts to locate and load
// in. The reference node is replaced with the entire
// subtree loaded. This is intended to be called from
// EggData::load_externals().
////////////////////////////////////////////////////////////////////
bool EggGroupNode::
r_load_externals(const DSearchPath &searchpath, CoordinateSystem coordsys,
BamCacheRecord *record) {
bool success = true;
Children::iterator ci;
for (ci = _children.begin();
ci != _children.end();
++ci) {
EggNode *child = *ci;
if (child->is_of_type(EggExternalReference::get_class_type())) {
PT(EggExternalReference) ref = DCAST(EggExternalReference, child);
// Replace the reference with an empty group node. When we load
// the external file successfully, we'll put its contents here.
Filename filename = ref->get_filename();
EggGroupNode *new_node =
new EggGroupNode(filename.get_basename_wo_extension());
replace(ci, new_node);
if (!EggData::resolve_egg_filename(filename, searchpath)) {
egg_cat.error()
<< "Could not locate " << filename << " in "
<< searchpath << "\n";
} else {
// Now define a new EggData structure to hold the external
// reference, and load it.
EggData ext_data;
ext_data.set_coordinate_system(coordsys);
ext_data.set_auto_resolve_externals(true);
if (ext_data.read(filename)) {
// The external file was read correctly. Add its contents
// into the tree at this point.
if (record != (BamCacheRecord *)NULL) {
record->add_dependent_file(filename);
}
success =
ext_data.load_externals(searchpath, record)
&& success;
new_node->steal_children(ext_data);
}
}
} else if (child->is_of_type(EggGroupNode::get_class_type())) {
EggGroupNode *group_child = DCAST(EggGroupNode, child);
success =
group_child->r_load_externals(searchpath, coordsys, record)
&& success;
}
}
return success;
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::prepare_add_child
// Access: Private
// Description: Marks the node as a child of the group. This is an
// internal function called by the STL-like functions
// push_back() and insert(), in preparation for actually
// adding the child.
//
// It is an error to add a node that is already a child
// of this group or some other group.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
prepare_add_child(EggNode *node) {
nassertv(node != (EggNode *)NULL);
test_ref_count_integrity();
node->test_ref_count_integrity();
// Make sure the node is not already a child of some other group.
nassertv(node->get_parent() == NULL);
nassertv(node->get_depth() == 0);
node->_parent = this;
node->update_under(get_depth() + 1);
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::prepare_remove_child
// Access: Private
// Description: Marks the node as removed from the group. This is an
// internal function called by the STL-like functions
// pop_back() and erase(), in preparation for actually
// doing the removal.
//
// It is an error to attempt to remove a node that is
// not already a child of this group.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
prepare_remove_child(EggNode *node) {
nassertv(node != (EggNode *)NULL);
// Make sure the node is in fact a child of this group.
nassertv(node->get_parent() == this);
nassertv(node->get_depth() == get_depth() + 1);
node->_parent = NULL;
node->update_under(-(get_depth() + 1));
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::r_collect_vertex_normals
// Access: Private
// Description: This is part of the implementation of
// recompute_vertex_normals(). It walks the scene graph
// at this group node and below, identifying all the
// polygons and the vertices they have in common.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
r_collect_vertex_normals(EggGroupNode::NVertexCollection &collection,
double threshold, CoordinateSystem cs) {
// We can do this ci/cnext iteration through the list as we modify
// it, only because we know this works with an STL list type
// container. If this were a vector or a set, this wouldn't
// necessarily work.
Children::iterator ci, cnext;
ci = _children.begin();
while (ci != _children.end()) {
cnext = ci;
++cnext;
EggNode *child = *ci;
if (child->is_of_type(EggPolygon::get_class_type())) {
EggPolygon *polygon = DCAST(EggPolygon, child);
polygon->clear_normal();
NVertexReference ref;
ref._polygon = polygon;
if (!polygon->calculate_normal(ref._normal, cs)) {
// The polygon is degenerate. Remove it.
prepare_remove_child(child);
_children.erase(ci);
} else {
// Now add each vertex from the polygon separately to our
// collection.
size_t num_vertices = polygon->size();
for (size_t i = 0; i < num_vertices; i++) {
EggVertex *vertex = polygon->get_vertex(i);
ref._vertex = i;
collection[vertex->get_pos3()].push_back(ref);
}
}
} else if (child->is_of_type(EggGroupNode::get_class_type())) {
EggGroupNode *group = DCAST(EggGroupNode, child);
// We can't share vertices across an Instance node. Don't
// even bother trying. Instead, just restart.
if (group->is_under_instance()) {
group->recompute_vertex_normals(threshold, cs);
} else {
group->r_collect_vertex_normals(collection, threshold, cs);
}
}
ci = cnext;
}
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::do_compute_vertex_normals
// Access: Private
// Description: This is part of the implementation of
// recompute_vertex_normals(). It accepts a group of
// polygons and their common normals, and computes the
// same normal for all their shared vertices.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
do_compute_vertex_normals(const NVertexGroup &group) {
nassertv(!group.empty());
// Determine the common normal. This is simply the average of all
// the polygon normals that share this vertex.
Normald normal(0.0, 0.0, 0.0);
NVertexGroup::const_iterator gi;
for (gi = group.begin(); gi != group.end(); ++gi) {
const NVertexReference &ref = (*gi);
normal += ref._normal;
}
normal /= (double)group.size();
normal.normalize();
// Now we have the common normal; apply it to all the vertices.
for (gi = group.begin(); gi != group.end(); ++gi) {
const NVertexReference &ref = (*gi);
EggVertex *vertex = ref._polygon->get_vertex(ref._vertex);
EggVertexPool *pool = vertex->get_pool();
EggVertex new_vertex(*vertex);
new_vertex.set_normal(normal);
EggVertex *unique = pool->create_unique_vertex(new_vertex);
unique->copy_grefs_from(*vertex);
ref._polygon->set_vertex(ref._vertex, unique);
}
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::r_collect_tangent_binormal
// Access: Private
// Description: This is part of the implementation of
// recompute_tangent_binormal(). It walks the scene
// graph at this group node and below, identifying all
// the polygons and the vertices they have in common.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
r_collect_tangent_binormal(const GlobPattern &uv_name,
EggGroupNode::TBNVertexCollection &collection) {
Children::iterator ci;
for (ci = _children.begin(); ci != _children.end(); ++ci) {
EggNode *child = *ci;
if (child->is_of_type(EggPolygon::get_class_type())) {
EggPolygon *polygon = DCAST(EggPolygon, child);
TBNVertexReference ref;
ref._polygon = polygon;
// Now add each vertex from the polygon separately to our
// collection.
size_t num_vertices = polygon->size();
for (size_t i = 0; i < num_vertices; i++) {
// We look at the triangle formed by each three consecutive
// vertices to determine the s direction and t direction at
// each vertex. v1 is the key vertex, the one at position i;
// v2 is vertex i + 1, and v3 is vertex i - 1.
EggVertex *v1 = polygon->get_vertex(i);
EggVertex *v2 = polygon->get_vertex((i + 1) % num_vertices);
EggVertex *v3 = polygon->get_vertex((i + num_vertices - 1) % num_vertices);
if (v1->has_normal() || polygon->has_normal()) {
// Go through all of the UV names on the vertex, looking for
// one that matches the glob pattern.
EggVertex::const_uv_iterator uvi;
for (uvi = v1->uv_begin(); uvi != v1->uv_end(); ++uvi) {
EggVertexUV *uv_obj = (*uvi);
string name = uv_obj->get_name();
if (uv_name.matches(name) &&
v2->has_uv(name) && v3->has_uv(name)) {
TBNVertexValue value;
value._uv_name = name;
value._pos = v1->get_pos3();
if (v1->has_normal()) {
value._normal = v1->get_normal();
} else {
value._normal = polygon->get_normal();
}
value._uv = v1->get_uv(name);
// Compute the s direction and t direction for this vertex.
LPoint3d p1 = v1->get_pos3();
LPoint3d p2 = v2->get_pos3();
LPoint3d p3 = v3->get_pos3();
TexCoordd w1 = v1->get_uv(name);
TexCoordd w2 = v2->get_uv(name);
TexCoordd w3 = v3->get_uv(name);
// Check the facing of the texture; we will have to
// split vertices whose UV's are mirrored along a seam.
// The facing is determined by the winding order of the
// texcoords on the polygon. A front-facing polygon
// should not contribute to the tangent and binormal of
// a back-facing polygon, and vice-versa.
value._facing = is_right(w1 - w2, w3 - w1);
double x1 = p2[0] - p1[0];
double x2 = p3[0] - p1[0];
double y1 = p2[1] - p1[1];
double y2 = p3[1] - p1[1];
double z1 = p2[2] - p1[2];
double z2 = p3[2] - p1[2];
double s1 = w2[0] - w1[0];
double s2 = w3[0] - w1[0];
double t1 = w2[1] - w1[1];
double t2 = w3[1] - w1[1];
double denom = (s1 * t2 - s2 * t1);
if (denom == 0.0) {
ref._sdir.set(0.0, 0.0, 0.0);
ref._tdir.set(0.0, 0.0, 0.0);
} else {
double r = 1.0 / denom;
ref._sdir.set((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r,
(t2 * z1 - t1 * z2) * r);
ref._tdir.set((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r,
(s1 * z2 - s2 * z1) * r);
}
// Store the vertex referenced to the polygon.
ref._vertex = i;
collection[value].push_back(ref);
}
}
}
}
} else if (child->is_of_type(EggGroupNode::get_class_type())) {
EggGroupNode *group = DCAST(EggGroupNode, child);
// We can't share vertices across an Instance node. Don't
// even bother trying. Instead, just restart.
if (group->is_under_instance()) {
group->recompute_tangent_binormal(uv_name);
} else {
group->r_collect_tangent_binormal(uv_name, collection);
}
}
}
}
////////////////////////////////////////////////////////////////////
// Function: EggGroupNode::do_compute_tangent_binormal
// Access: Private
// Description: This is part of the implementation of
// recompute_tangent_binormal(). It accepts a group of
// polygons and their common normals and UV's, and
// computes the tangent and binormal for all their
// shared vertices.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
do_compute_tangent_binormal(const TBNVertexValue &value,
const TBNVertexGroup &group) {
nassertv(!group.empty());
// Accumulate together all of the s vectors and t vectors computed
// for the different vertices that are together here.
Normald sdir(0.0, 0.0, 0.0);
Normald tdir(0.0, 0.0, 0.0);
TBNVertexGroup::const_iterator gi;
for (gi = group.begin(); gi != group.end(); ++gi) {
const TBNVertexReference &ref = (*gi);
sdir += ref._sdir;
tdir += ref._tdir;
}
// If sdir and/or tdir are zero, choose an arbitrary vector instead.
// (This is really the only reason we normalize sdir and tdir,
// though it also helps stabilize the math below in case the vectors
// are very small but not quite zero.)
if (!sdir.normalize()) {
sdir.set(1.0, 0.0, 0.0);
}
if (!tdir.normalize()) {
tdir = sdir.cross(Normald(0.0, 0.0, -1.0));
}
Normald tangent = (sdir - value._normal * value._normal.dot(sdir));
tangent.normalize();
Normald binormal = cross(value._normal, tangent);
if (dot(binormal, tdir) < 0.0f) {
binormal = -binormal;
}
// Shouldn't need to normalize this, but we do just for good measure.
binormal.normalize();
// Now we have the common tangent and binormal; apply them to all
// the vertices.
for (gi = group.begin(); gi != group.end(); ++gi) {
const TBNVertexReference &ref = (*gi);
EggVertex *vertex = ref._polygon->get_vertex(ref._vertex);
EggVertexPool *pool = vertex->get_pool();
EggVertex new_vertex(*vertex);
EggVertexUV *uv_obj = new_vertex.modify_uv_obj(value._uv_name);
nassertv(uv_obj != (EggVertexUV *)NULL);
uv_obj->set_tangent(tangent);
uv_obj->set_binormal(binormal);
EggVertex *unique = pool->create_unique_vertex(new_vertex);
unique->copy_grefs_from(*vertex);
ref._polygon->set_vertex(ref._vertex, unique);
}
}