/** * 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 eggGroupNode.cxx * @author drose * @date 1999-01-16 */ #include "eggGroupNode.h" #include "eggGroup.h" #include "eggCoordinateSystem.h" #include "eggData.h" #include "eggFilenameNode.h" #include "eggExternalReference.h" #include "eggPrimitive.h" #include "eggPoint.h" #include "eggPolygon.h" #include "eggCompositePrimitive.h" #include "eggMesher.h" #include "eggVertexPool.h" #include "eggVertex.h" #include "eggTextureCollection.h" #include "eggMaterialCollection.h" #include "epvector.h" #include "pt_EggTexture.h" #include "pt_EggMaterial.h" #include "config_egg.h" #include "dSearchPath.h" #include "virtualFileSystem.h" #include "deg_2_rad.h" #include "dcast.h" #include "bamCacheRecord.h" #include TypeHandle EggGroupNode::_type_handle; /** * */ EggGroupNode:: EggGroupNode(const EggGroupNode ©) : EggNode(copy) { if (!copy.empty()) { egg_cat.warning() << "The EggGroupNode copy constructor does not copy children!\n"; } } /** * */ EggGroupNode &EggGroupNode:: operator =(const EggGroupNode ©) { if (!copy.empty()) { egg_cat.warning() << "The EggGroupNode copy assignment does not copy children!\n"; } EggNode::operator =(copy); return *this; } /** * */ EggGroupNode:: ~EggGroupNode() { } /** * 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); } } } /** * */ EggGroupNode::iterator EggGroupNode:: begin() const { return _children.begin(); } /** * */ EggGroupNode::iterator EggGroupNode:: end() const { return _children.end(); } /** * */ EggGroupNode::reverse_iterator EggGroupNode:: rbegin() const { return _children.rbegin(); } /** * */ EggGroupNode::reverse_iterator EggGroupNode:: rend() const { return _children.rend(); } /** * */ EggGroupNode::iterator EggGroupNode:: insert(iterator position, PT(EggNode) x) { prepare_add_child(x); return _children.insert((Children::iterator &)position, x); } /** * */ EggGroupNode::iterator EggGroupNode:: erase(iterator position) { prepare_remove_child(*position); return _children.erase((Children::iterator &)position); } /** * */ 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); } /** * 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; } /** * */ bool EggGroupNode:: empty() const { return _children.empty(); } /** * */ EggGroupNode::size_type EggGroupNode:: size() const { return _children.size(); } /** * */ void EggGroupNode:: clear() { erase(begin(), end()); } /** * 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(); } /** * 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; } /** * 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; } /** * 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; } } /** * 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); } /** * 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; } /** * 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; } /** * 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); } } } /** * 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); } } } /** * 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(); } } } /** * 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(); } } } /** * 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; } } /** * 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(); } } } /** * 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; } /** * 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; } /** * 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)|| (eggtex->get_env_type() == EggTexture::ET_normal_gloss)) { 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); } /** * 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; } /** * 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); } } } } /** * Creates PointLight primitives to reference any otherwise unreferences * vertices discovered in this group or below. */ void EggGroupNode:: make_point_primitives() { // Create a temporary node to hold the EggPoint objects we might create // while we iterate. (We don't add them during the iteration to avoid // invalidating the iterator.) PT(EggGroupNode) temp = new EggGroup("temp"); 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->make_point_primitives(); } else if ((*ci)->is_of_type(EggVertexPool::get_class_type())) { EggVertexPool *vpool = DCAST(EggVertexPool, *ci); PT(EggPrimitive) prim = new EggPoint; vpool->add_unused_vertices_to_prim(prim); if (!prim->empty()) { temp->add_child(prim); } } } steal_children(*temp); } /** * 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; } /** * 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; } /** * 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; } /** * 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(); } } } /** * 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(); } } } /** * 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); } } } } /** * 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); } } } } /** * 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); } } } } /** * 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); } } } } /** * 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; } /** * 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; } /** * 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; } /** * 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 epvector 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); } } } } /** * 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); } } /** * 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); } } /** * 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); } } /** * 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); } } /** * 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(); } } /** * 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); } } /** * 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 cicnext 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; } /** * 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 cicnext 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; } /** * 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 cicnext 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; } /** * 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; } /** * 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); } /** * 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)); } /** * 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 cicnext 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; } } /** * 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. LNormald 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); } } /** * 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(); LTexCoordd w1 = v1->get_uv(name); LTexCoordd w2 = v2->get_uv(name); LTexCoordd 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); } } } } /** * 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. LNormald sdir(0.0, 0.0, 0.0); LNormald 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 andor 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(LNormald(0.0, 0.0, -1.0)); } LNormald tangent = (sdir - value._normal * value._normal.dot(sdir)); tangent.normalize(); LNormald 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); } }