1359 lines
47 KiB
C++
1359 lines
47 KiB
C++
// Filename: eggOptchar.cxx
|
|
// Created by: drose (18Jul03)
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PANDA 3D SOFTWARE
|
|
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved
|
|
//
|
|
// All use of this software is subject to the terms of the Panda 3d
|
|
// Software license. You should have received a copy of this license
|
|
// along with this source code; you will also find a current copy of
|
|
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
|
|
//
|
|
// To contact the maintainers of this program write to
|
|
// panda3d-general@lists.sourceforge.net .
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
|
|
#include "eggOptchar.h"
|
|
#include "eggOptcharUserData.h"
|
|
#include "vertexMembership.h"
|
|
|
|
#include "eggJointData.h"
|
|
#include "eggSliderData.h"
|
|
#include "eggCharacterCollection.h"
|
|
#include "eggCharacterData.h"
|
|
#include "eggBackPointer.h"
|
|
#include "eggGroupNode.h"
|
|
#include "eggPrimitive.h"
|
|
#include "eggVertexPool.h"
|
|
#include "string_utils.h"
|
|
#include "dcast.h"
|
|
#include "pset.h"
|
|
#include "compose_matrix.h"
|
|
#include "fftCompressor.h"
|
|
#include "pystub.h"
|
|
|
|
#include <algorithm>
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: EggOptchar::Constructor
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
EggOptchar::
|
|
EggOptchar() {
|
|
add_path_replace_options();
|
|
add_path_store_options();
|
|
add_normals_options();
|
|
add_transform_options();
|
|
add_fixrest_option();
|
|
|
|
set_program_description
|
|
("egg-optchar performs basic optimizations of a character model "
|
|
"and its associated animations, primarily by analyzing the "
|
|
"animation tables and removing unneeded joints and/or morphs. "
|
|
"It can also perform basic restructuring operations on the "
|
|
"character hierarchy.");
|
|
|
|
add_option
|
|
("ls", "", 0,
|
|
"List the joint hierarchy instead of performing any operations.",
|
|
&EggOptchar::dispatch_none, &_list_hierarchy);
|
|
|
|
add_option
|
|
("lsv", "", 0,
|
|
"List the joint hierarchy along with an indication of the properties "
|
|
"each joint.",
|
|
&EggOptchar::dispatch_none, &_list_hierarchy_v);
|
|
|
|
add_option
|
|
("lsp", "", 0,
|
|
"List the existing joint hierarchy as a series of -p joint,parent "
|
|
"commands, suitable for pasting into an egg-optchar command line.",
|
|
&EggOptchar::dispatch_none, &_list_hierarchy_p);
|
|
|
|
add_option
|
|
("keep", "joint[,joint...]", 0,
|
|
"Keep the named joints (or sliders) in the character, even if they do "
|
|
"not appear to be needed by the animation.",
|
|
&EggOptchar::dispatch_vector_string_comma, NULL, &_keep_components);
|
|
|
|
add_option
|
|
("drop", "joint[,joint...]", 0,
|
|
"Removes the named joints or sliders, even if they appear to be needed.",
|
|
&EggOptchar::dispatch_vector_string_comma, NULL, &_drop_components);
|
|
|
|
add_option
|
|
("expose", "joint[,joint...]", 0,
|
|
"Expose the named joints by flagging them with a DCS attribute, so "
|
|
"each one can be found in the scene graph when the character is loaded, "
|
|
"and objects can be parented to it. This implies -keep.",
|
|
&EggOptchar::dispatch_vector_string_comma, NULL, &_expose_components);
|
|
|
|
add_option
|
|
("suppress", "joint[,joint...]", 0,
|
|
"The opposite of suppress, this prevents the named joints from "
|
|
"being created with an implicit DCS attribute, even if they contain "
|
|
"rigid geometry. The default is to create an implicit node for any "
|
|
"joint that contains rigid geometry, to take advantage of display "
|
|
"list and/or vertex buffer caching. This does not imply -keep.",
|
|
&EggOptchar::dispatch_vector_string_comma, NULL, &_suppress_components);
|
|
|
|
add_option
|
|
("flag", "node[,node...][=name]", 0,
|
|
"Assign the indicated name to the geometry within the given nodes. "
|
|
"This will make the geometry visible as a node in the resulting "
|
|
"character model when it is loaded in the scene graph (normally, "
|
|
"the node hierarchy is suppressed when loading characters). This "
|
|
"is different from -expose in that it reveals geometry rather than "
|
|
"joints; the revealed node can be hidden or its attributes changed "
|
|
"at runtime, but it will be animated by its vertices, not the node, so "
|
|
"objects parented to this node will not inherit its animation.",
|
|
&EggOptchar::dispatch_flag_groups, NULL, &_flag_groups);
|
|
|
|
add_option
|
|
("zero", "joint[,hprxyzijkabc]", 0,
|
|
"Zeroes out the animation channels for the named joint. If "
|
|
"a subset of the component letters hprxyzijkabc is included, the "
|
|
"operation is restricted to just those components; otherwise the "
|
|
"entire transform is cleared.",
|
|
&EggOptchar::dispatch_name_components, NULL, &_zero_channels);
|
|
|
|
add_option
|
|
("keepall", "", 0,
|
|
"Keep all joints and sliders in the character, except those named "
|
|
"explicitly by -drop.",
|
|
&EggOptchar::dispatch_none, &_keep_all);
|
|
|
|
add_option
|
|
("p", "joint,parent", 0,
|
|
"Moves the named joint under the named parent joint. Use "
|
|
"\"-p joint,\" to reparent a joint to the root. The joint transform "
|
|
"is recomputed appropriately under its new parent so that the animation "
|
|
"is not affected (the effect is similar to NodePath::wrt_reparent_to).",
|
|
&EggOptchar::dispatch_vector_string_pair, NULL, &_reparent_joints);
|
|
|
|
add_option
|
|
("new", "joint,source", 0,
|
|
"Creates a new joint under the named parent joint. The new "
|
|
"joint will inherit the same net transform as its parent.",
|
|
&EggOptchar::dispatch_vector_string_pair, NULL, &_new_joints);
|
|
|
|
if (FFTCompressor::is_compression_available()) {
|
|
add_option
|
|
("optimal", "", 0,
|
|
"Computes the optimal joint hierarchy for the character by analyzing "
|
|
"all of the joint animation and reparenting joints to minimize "
|
|
"transformations. This can repair skeletons that have been flattened "
|
|
"or whose hierarchy was otherwise damaged in conversion; it can also "
|
|
"detect joints that are constrained to follow other joints and should "
|
|
"therefore be parented to the master joints. The result is a file "
|
|
"from which more joints may be successfully removed, that generally "
|
|
"compresses better and with fewer artifacts. However, this is a "
|
|
"fairly expensive operation.",
|
|
&EggOptchar::dispatch_none, &_optimal_hierarchy);
|
|
}
|
|
|
|
add_option
|
|
("q", "quantum", 0,
|
|
"Quantize joint membership values to the given unit. This is "
|
|
"the smallest significant change in joint membership. There can "
|
|
"be a significant performance (and memory utilization) runtime "
|
|
"benefit for eliminating small differences in joint memberships "
|
|
"between neighboring vertices. The default is 0.01; specifying "
|
|
"0 means to preserve the original values.",
|
|
&EggOptchar::dispatch_double, NULL, &_vref_quantum);
|
|
|
|
add_option
|
|
("qa", "quantum[,hprxyzijkabc]", 0,
|
|
"Quantizes animation channels to the given unit. This rounds each "
|
|
"of the named components of all joints to the nearest multiple of unit. "
|
|
"There is no performance benefit, and little compression benefit, "
|
|
"for doing this; and this may introduce visible artifacts to the "
|
|
"animation. However, sometimes it is a useful tool for animation "
|
|
"analysis and comparison. This option may be repeated several times "
|
|
"to quantize different channels by a different amount.",
|
|
&EggOptchar::dispatch_double_components, NULL, &_quantize_anims);
|
|
|
|
_optimal_hierarchy = false;
|
|
_vref_quantum = 0.01;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: EggOptchar::run
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void EggOptchar::
|
|
run() {
|
|
// We have to apply the user-specified reparent requests first,
|
|
// before we even analyze the joints. This is because reparenting
|
|
// the joints may change their properties.
|
|
if (apply_user_reparents()) {
|
|
nout << "Reparenting hierarchy.\n";
|
|
// So we'll have to call do_reparent() twice. It seems wasteful,
|
|
// but it really is necessary, and it's not that bad.
|
|
do_reparent();
|
|
}
|
|
|
|
if (!_zero_channels.empty()) {
|
|
zero_channels();
|
|
}
|
|
|
|
int num_characters = _collection->get_num_characters();
|
|
int ci;
|
|
|
|
// Now we can analyze the joints for their properties.
|
|
for (ci = 0; ci < num_characters; ci++) {
|
|
EggCharacterData *char_data = _collection->get_character(ci);
|
|
analyze_joints(char_data->get_root_joint(), 0);
|
|
analyze_sliders(char_data);
|
|
}
|
|
|
|
if (_list_hierarchy || _list_hierarchy_v) {
|
|
for (ci = 0; ci < num_characters; ci++) {
|
|
EggCharacterData *char_data = _collection->get_character(ci);
|
|
nout << "Character: " << char_data->get_name() << "\n";
|
|
list_joints(char_data->get_root_joint(), 0, _list_hierarchy_v);
|
|
list_scalars(char_data, _list_hierarchy_v);
|
|
nout << char_data->get_num_joints() << " joints.\n";
|
|
}
|
|
|
|
} else if (_list_hierarchy_p) {
|
|
for (ci = 0; ci < num_characters; ci++) {
|
|
EggCharacterData *char_data = _collection->get_character(ci);
|
|
nout << "Character: " << char_data->get_name() << "\n";
|
|
int col = 0;
|
|
list_joints_p(char_data->get_root_joint(), col);
|
|
// A newline to cout is needed after the above call.
|
|
cout << "\n";
|
|
nout << char_data->get_num_joints() << " joints.\n";
|
|
}
|
|
|
|
} else {
|
|
// The meat of the program: determine which joints are to be
|
|
// removed, and then actually remove them.
|
|
determine_removed_components();
|
|
move_vertices();
|
|
if (process_joints()) {
|
|
do_reparent();
|
|
}
|
|
|
|
// We currently do not implement optimizing morph sliders. Need
|
|
// to add this at some point; it's quite easy. Identity and empty
|
|
// morph sliders can simply be removed, while static sliders need
|
|
// to be applied to the vertices and then removed.
|
|
|
|
// Quantize the vertex memberships. We call this even if
|
|
// _vref_quantum is 0, because this also normalizes the vertex
|
|
// memberships.
|
|
quantize_vertices();
|
|
|
|
// Also quantize the animation channels, if the user so requested.
|
|
quantize_channels();
|
|
|
|
// flag all the groups as the user requested.
|
|
if (!_flag_groups.empty()) {
|
|
Eggs::iterator ei;
|
|
for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) {
|
|
do_flag_groups(*ei);
|
|
}
|
|
}
|
|
|
|
write_eggs();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: EggOptchar::handle_args
|
|
// Access: Protected, Virtual
|
|
// Description: Does something with the additional arguments on the
|
|
// command line (after all the -options have been
|
|
// parsed). Returns true if the arguments are good,
|
|
// false otherwise.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool EggOptchar::
|
|
handle_args(ProgramBase::Args &args) {
|
|
if (_list_hierarchy || _list_hierarchy_v || _list_hierarchy_p) {
|
|
_read_only = true;
|
|
}
|
|
|
|
return EggCharacterFilter::handle_args(args);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: ProgramBase::dispatch_vector_string_pair
|
|
// Access: Protected, Static
|
|
// Description: Standard dispatch function for an option that takes
|
|
// a pair of string parameters. The data pointer is to
|
|
// StringPairs vector; the pair will be pushed onto the
|
|
// end of the vector.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool EggOptchar::
|
|
dispatch_vector_string_pair(const string &opt, const string &arg, void *var) {
|
|
StringPairs *ip = (StringPairs *)var;
|
|
|
|
vector_string words;
|
|
tokenize(arg, words, ",");
|
|
|
|
if (words.size() == 2) {
|
|
StringPair sp;
|
|
sp._a = words[0];
|
|
sp._b = words[1];
|
|
ip->push_back(sp);
|
|
|
|
} else {
|
|
nout << "-" << opt
|
|
<< " requires a pair of strings separated by a comma.\n";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: ProgramBase::dispatch_name_components
|
|
// Access: Protected, Static
|
|
// Description: Accepts a name optionally followed by a comma and some
|
|
// of the nine standard component letters,
|
|
//
|
|
// The data pointer is to StringPairs vector; the pair
|
|
// will be pushed onto the end of the vector.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool EggOptchar::
|
|
dispatch_name_components(const string &opt, const string &arg, void *var) {
|
|
StringPairs *ip = (StringPairs *)var;
|
|
|
|
vector_string words;
|
|
tokenize(arg, words, ",");
|
|
|
|
StringPair sp;
|
|
if (words.size() == 1) {
|
|
sp._a = words[0];
|
|
|
|
} else if (words.size() == 2) {
|
|
sp._a = words[0];
|
|
sp._b = words[1];
|
|
|
|
} else {
|
|
nout << "-" << opt
|
|
<< " requires a pair of strings separated by a comma.\n";
|
|
return false;
|
|
}
|
|
|
|
if (sp._b.empty()) {
|
|
sp._b = matrix_component_letters;
|
|
} else {
|
|
for (string::const_iterator si = sp._b.begin(); si != sp._b.end(); ++si) {
|
|
if (strchr(matrix_component_letters, *si) == NULL) {
|
|
nout << "Not a standard matrix component: \"" << *si << "\"\n"
|
|
<< "-" << opt << " requires a joint name followed by a set "
|
|
<< "of component names. The standard component names are \""
|
|
<< matrix_component_letters << "\".\n";
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
ip->push_back(sp);
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: ProgramBase::dispatch_double_components
|
|
// Access: Protected, Static
|
|
// Description: Accepts a double value optionally followed by a comma
|
|
// and some of the nine standard component letters,
|
|
//
|
|
// The data pointer is to a DoubleStrings vector; the
|
|
// pair will be pushed onto the end of the vector.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool EggOptchar::
|
|
dispatch_double_components(const string &opt, const string &arg, void *var) {
|
|
DoubleStrings *ip = (DoubleStrings *)var;
|
|
|
|
vector_string words;
|
|
tokenize(arg, words, ",");
|
|
|
|
bool valid_double = false;
|
|
|
|
DoubleString sp;
|
|
if (words.size() == 1) {
|
|
valid_double = string_to_double(words[0], sp._a);
|
|
|
|
} else if (words.size() == 2) {
|
|
valid_double = string_to_double(words[0], sp._a);
|
|
sp._b = words[1];
|
|
|
|
} else {
|
|
nout << "-" << opt
|
|
<< " requires a numeric value followed by a string.\n";
|
|
return false;
|
|
}
|
|
|
|
if (!valid_double) {
|
|
nout << "-" << opt
|
|
<< " requires a numeric value followed by a string.\n";
|
|
return false;
|
|
}
|
|
|
|
if (sp._b.empty()) {
|
|
sp._b = matrix_component_letters;
|
|
} else {
|
|
for (string::const_iterator si = sp._b.begin(); si != sp._b.end(); ++si) {
|
|
if (strchr(matrix_component_letters, *si) == NULL) {
|
|
nout << "Not a standard matrix component: \"" << *si << "\"\n"
|
|
<< "-" << opt << " requires a joint name followed by a set "
|
|
<< "of component names. The standard component names are \""
|
|
<< matrix_component_letters << "\".\n";
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
ip->push_back(sp);
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: ProgramBase::dispatch_flag_groups
|
|
// Access: Protected, Static
|
|
// Description: Accepts a set of comma-delimited group names followed
|
|
// by an optional name separated with an equal sign.
|
|
//
|
|
// The data pointer is to a FlagGroups object.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool EggOptchar::
|
|
dispatch_flag_groups(const string &opt, const string &arg, void *var) {
|
|
FlagGroups *ip = (FlagGroups *)var;
|
|
|
|
vector_string words;
|
|
|
|
tokenize(arg, words, ",");
|
|
|
|
if (words.empty()) {
|
|
nout << "-" << opt
|
|
<< " requires a series of words separated by a comma.\n";
|
|
return false;
|
|
}
|
|
|
|
FlagGroupsEntry entry;
|
|
|
|
// Check for an equal sign in the last word. This marks the name to
|
|
// assign.
|
|
string &last_word = words.back();
|
|
size_t equals = last_word.rfind('=');
|
|
if (equals != string::npos) {
|
|
entry._name = last_word.substr(equals + 1);
|
|
last_word = last_word.substr(0, equals);
|
|
|
|
} else {
|
|
// If there's no equal sign, the default is to name all groups
|
|
// after the last word.
|
|
entry._name = last_word;
|
|
}
|
|
|
|
// Convert the words to GlobPatterns.
|
|
vector_string::const_iterator si;
|
|
for (si = words.begin(); si != words.end(); ++si) {
|
|
const string &word = (*si);
|
|
entry._groups.push_back(GlobPattern(word));
|
|
}
|
|
|
|
ip->push_back(entry);
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: EggOptchar::determine_removed_components
|
|
// Access: Private
|
|
// Description: Flag all joints and sliders that should be removed
|
|
// for optimization purposes.
|
|
////////////////////////////////////////////////////////////////////
|
|
void EggOptchar::
|
|
determine_removed_components() {
|
|
typedef pset<string> Names;
|
|
Names keep_names;
|
|
Names drop_names;
|
|
Names expose_names;
|
|
Names suppress_names;
|
|
Names names_used;
|
|
|
|
vector_string::const_iterator si;
|
|
for (si = _keep_components.begin(); si != _keep_components.end(); ++si) {
|
|
keep_names.insert(*si);
|
|
}
|
|
for (si = _drop_components.begin(); si != _drop_components.end(); ++si) {
|
|
drop_names.insert(*si);
|
|
}
|
|
for (si = _expose_components.begin(); si != _expose_components.end(); ++si) {
|
|
keep_names.insert(*si);
|
|
expose_names.insert(*si);
|
|
}
|
|
for (si = _suppress_components.begin(); si != _suppress_components.end(); ++si) {
|
|
suppress_names.insert(*si);
|
|
}
|
|
|
|
// We always keep the root joint, which has no name.
|
|
keep_names.insert("");
|
|
|
|
int num_characters = _collection->get_num_characters();
|
|
for (int ci = 0; ci < num_characters; ci++) {
|
|
EggCharacterData *char_data = _collection->get_character(ci);
|
|
int num_components = char_data->get_num_components();
|
|
nout << char_data->get_name() << " has " << num_components << " components.\n";
|
|
for (int i = 0; i < num_components; i++) {
|
|
EggComponentData *comp_data = char_data->get_component(i);
|
|
nassertv(comp_data != (EggComponentData *)NULL);
|
|
|
|
EggOptcharUserData *user_data =
|
|
DCAST(EggOptcharUserData, comp_data->get_user_data());
|
|
nassertv(user_data != (EggOptcharUserData *)NULL);
|
|
|
|
const string &name = comp_data->get_name();
|
|
if (suppress_names.find(name) != suppress_names.end()) {
|
|
// If this component is not dropped, it will not be implicitly
|
|
// exposed.
|
|
names_used.insert(name);
|
|
user_data->_flags |= EggOptcharUserData::F_suppress;
|
|
}
|
|
|
|
if (drop_names.find(name) != drop_names.end()) {
|
|
// Remove this component by user request.
|
|
names_used.insert(name);
|
|
user_data->_flags |= EggOptcharUserData::F_remove;
|
|
|
|
} else if (_keep_all || keep_names.find(name) != keep_names.end()) {
|
|
// Keep this component.
|
|
names_used.insert(name);
|
|
|
|
if (expose_names.find(name) != expose_names.end()) {
|
|
// In fact, expose it.
|
|
user_data->_flags |= EggOptcharUserData::F_expose;
|
|
}
|
|
|
|
} else {
|
|
// Remove this component if it's unanimated or empty.
|
|
if ((user_data->_flags & (EggOptcharUserData::F_static | EggOptcharUserData::F_empty)) != 0) {
|
|
if ((user_data->_flags & (EggOptcharUserData::F_top | EggOptcharUserData::F_empty)) == EggOptcharUserData::F_top) {
|
|
// Actually, we can't remove it if it's a top joint,
|
|
// unless it's also empty. That's because vertices that
|
|
// are partially assigned to this joint would then have no
|
|
// joint to represent the same partial assignment, and
|
|
// they would then appear to be wholly assigned to their
|
|
// other joint, which would be incorrect.
|
|
|
|
} else {
|
|
// But joints that aren't top joints (or that are empty)
|
|
// are o.k. to remove.
|
|
user_data->_flags |= EggOptcharUserData::F_remove;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Go back and tell the user about component names we didn't use,
|
|
// just to be helpful.
|
|
for (si = _keep_components.begin(); si != _keep_components.end(); ++si) {
|
|
const string &name = (*si);
|
|
if (names_used.find(name) == names_used.end()) {
|
|
nout << "No such component: " << name << "\n";
|
|
}
|
|
}
|
|
for (si = _drop_components.begin(); si != _drop_components.end(); ++si) {
|
|
const string &name = (*si);
|
|
if (names_used.find(name) == names_used.end()) {
|
|
nout << "No such component: " << name << "\n";
|
|
}
|
|
}
|
|
for (si = _expose_components.begin(); si != _expose_components.end(); ++si) {
|
|
const string &name = (*si);
|
|
if (names_used.find(name) == names_used.end()) {
|
|
nout << "No such component: " << name << "\n";
|
|
}
|
|
}
|
|
for (si = _suppress_components.begin(); si != _suppress_components.end(); ++si) {
|
|
const string &name = (*si);
|
|
if (names_used.find(name) == names_used.end()) {
|
|
nout << "No such component: " << name << "\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: EggOptchar::move_vertices
|
|
// Access: Private
|
|
// Description: Moves the vertices from joints that are about to be
|
|
// removed into the first suitable parent. This might
|
|
// result in fewer joints being removed (because
|
|
// the parent might suddenly no longer be empty).
|
|
////////////////////////////////////////////////////////////////////
|
|
void EggOptchar::
|
|
move_vertices() {
|
|
int num_characters = _collection->get_num_characters();
|
|
for (int ci = 0; ci < num_characters; ci++) {
|
|
EggCharacterData *char_data = _collection->get_character(ci);
|
|
int num_joints = char_data->get_num_joints();
|
|
|
|
for (int i = 0; i < num_joints; i++) {
|
|
EggJointData *joint_data = char_data->get_joint(i);
|
|
EggOptcharUserData *user_data =
|
|
DCAST(EggOptcharUserData, joint_data->get_user_data());
|
|
|
|
if ((user_data->_flags & EggOptcharUserData::F_empty) == 0 &&
|
|
(user_data->_flags & EggOptcharUserData::F_remove) != 0) {
|
|
// This joint has vertices, but is scheduled to be removed;
|
|
// find a suitable home for its vertices.
|
|
EggJointData *best_joint = find_best_vertex_joint(joint_data->get_parent());
|
|
joint_data->move_vertices_to(best_joint);
|
|
|
|
// Now we can't remove the joint.
|
|
if (best_joint != (EggJointData *)NULL) {
|
|
EggOptcharUserData *best_user_data =
|
|
DCAST(EggOptcharUserData, best_joint->get_user_data());
|
|
best_user_data->_flags &= ~(EggOptcharUserData::F_empty | EggOptcharUserData::F_remove);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: EggOptchar::process_joints
|
|
// Access: Private
|
|
// Description: Effects the actual removal of joints flagged for
|
|
// removal by reparenting the hierarchy appropriately.
|
|
// Returns true if any joints are removed, false
|
|
// otherwise.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool EggOptchar::
|
|
process_joints() {
|
|
bool removed_any = false;
|
|
int num_characters = _collection->get_num_characters();
|
|
for (int ci = 0; ci < num_characters; ci++) {
|
|
EggCharacterData *char_data = _collection->get_character(ci);
|
|
int num_joints = char_data->get_num_joints();
|
|
|
|
int num_static = 0;
|
|
int num_empty = 0;
|
|
int num_identity = 0;
|
|
int num_other = 0;
|
|
int num_kept = 0;
|
|
|
|
for (int i = 0; i < num_joints; i++) {
|
|
EggJointData *joint_data = char_data->get_joint(i);
|
|
EggOptcharUserData *user_data =
|
|
DCAST(EggOptcharUserData, joint_data->get_user_data());
|
|
|
|
if ((user_data->_flags & EggOptcharUserData::F_remove) != 0) {
|
|
// This joint will be removed, so reparent it to nothing.
|
|
joint_data->reparent_to((EggJointData *)NULL);
|
|
|
|
// Determine what kind of node it is we're removing, for the
|
|
// user's information.
|
|
if ((user_data->_flags & EggOptcharUserData::F_identity) != 0) {
|
|
num_identity++;
|
|
} else if ((user_data->_flags & EggOptcharUserData::F_static) != 0) {
|
|
num_static++;
|
|
} else if ((user_data->_flags & EggOptcharUserData::F_empty) != 0) {
|
|
num_empty++;
|
|
} else {
|
|
num_other++;
|
|
}
|
|
removed_any = true;
|
|
|
|
} else {
|
|
// This joint will be preserved, but maybe its parent will
|
|
// change.
|
|
EggJointData *best_parent = find_best_parent(joint_data->get_parent());
|
|
joint_data->reparent_to(best_parent);
|
|
if ((user_data->_flags & EggOptcharUserData::F_expose) != 0) {
|
|
joint_data->expose();
|
|
} else if ((user_data->_flags & EggOptcharUserData::F_suppress) != 0) {
|
|
joint_data->expose(EggGroup::DC_none);
|
|
}
|
|
num_kept++;
|
|
}
|
|
}
|
|
|
|
if (num_joints == num_kept) {
|
|
nout << char_data->get_name() << ": keeping " << num_joints
|
|
<< " joints.\n";
|
|
} else {
|
|
nout << setw(5) << num_joints
|
|
<< " original joints in " << char_data->get_name()
|
|
<< "\n";
|
|
if (num_identity != 0) {
|
|
nout << setw(5) << num_identity << " identity joints\n";
|
|
}
|
|
if (num_static != 0) {
|
|
nout << setw(5) << num_static << " unanimated joints\n";
|
|
}
|
|
if (num_empty != 0) {
|
|
nout << setw(5) << num_empty << " empty joints\n";
|
|
}
|
|
if (num_other != 0) {
|
|
nout << setw(5) << num_other << " other joints\n";
|
|
}
|
|
nout << " ----\n"
|
|
<< setw(5) << num_kept << " joints remaining\n\n";
|
|
}
|
|
}
|
|
|
|
return removed_any;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: EggOptchar::find_best_parent
|
|
// Access: Private
|
|
// Description: Searches for the first joint at this level or above
|
|
// that is not scheduled to be removed. This is the
|
|
// joint that the first child of this joint should be
|
|
// reparented to.
|
|
////////////////////////////////////////////////////////////////////
|
|
EggJointData *EggOptchar::
|
|
find_best_parent(EggJointData *joint_data) const {
|
|
EggOptcharUserData *user_data =
|
|
DCAST(EggOptcharUserData, joint_data->get_user_data());
|
|
|
|
if ((user_data->_flags & EggOptcharUserData::F_remove) != 0) {
|
|
// Keep going.
|
|
if (joint_data->get_parent() != (EggJointData *)NULL) {
|
|
return find_best_parent(joint_data->get_parent());
|
|
}
|
|
}
|
|
|
|
// This is the one!
|
|
return joint_data;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: EggOptchar::find_best_vertex_joint
|
|
// Access: Private
|
|
// Description: Searches for the first joint at this level or above
|
|
// that is not static. This is the joint that the
|
|
// vertices of this joint should be moved into.
|
|
////////////////////////////////////////////////////////////////////
|
|
EggJointData *EggOptchar::
|
|
find_best_vertex_joint(EggJointData *joint_data) const {
|
|
if (joint_data == (EggJointData *)NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
EggOptcharUserData *user_data =
|
|
DCAST(EggOptcharUserData, joint_data->get_user_data());
|
|
|
|
if ((user_data->_flags & EggOptcharUserData::F_static) != 0) {
|
|
// Keep going.
|
|
return find_best_vertex_joint(joint_data->get_parent());
|
|
}
|
|
|
|
// This is the one!
|
|
return joint_data;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: EggOptchar::apply_user_reparents
|
|
// Access: Private
|
|
// Description: Reparents all the joints that the user suggested on
|
|
// the command line. Returns true if any operations
|
|
// were performed, false otherwise.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool EggOptchar::
|
|
apply_user_reparents() {
|
|
bool did_anything = false;
|
|
|
|
int num_characters = _collection->get_num_characters();
|
|
|
|
// First, get the new joints.
|
|
StringPairs::const_iterator spi;
|
|
for (spi = _new_joints.begin(); spi != _new_joints.end(); ++spi) {
|
|
const StringPair &p = (*spi);
|
|
|
|
for (int ci = 0; ci < num_characters; ci++) {
|
|
EggCharacterData *char_data = _collection->get_character(ci);
|
|
EggJointData *node_a = char_data->find_joint(p._a);
|
|
EggJointData *node_b = char_data->get_root_joint();
|
|
if (!p._b.empty()) {
|
|
node_b = char_data->find_joint(p._b);
|
|
}
|
|
|
|
if (node_b == (EggJointData *)NULL) {
|
|
nout << "No joint named " << p._b << " in " << char_data->get_name()
|
|
<< ".\n";
|
|
|
|
} else if (node_a != (EggJointData *)NULL) {
|
|
nout << "Joint " << p._a << " already exists in "
|
|
<< char_data->get_name() << ".\n";
|
|
|
|
} else {
|
|
nout << "Creating new joint " << p._a << " in "
|
|
<< char_data->get_name() << ".\n";
|
|
node_a = char_data->make_new_joint(p._a, node_b);
|
|
did_anything = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now get the user reparents.
|
|
for (spi = _reparent_joints.begin(); spi != _reparent_joints.end(); ++spi) {
|
|
const StringPair &p = (*spi);
|
|
|
|
for (int ci = 0; ci < num_characters; ci++) {
|
|
EggCharacterData *char_data = _collection->get_character(ci);
|
|
EggJointData *node_a = char_data->find_joint(p._a);
|
|
EggJointData *node_b = char_data->get_root_joint();
|
|
if (!p._b.empty()) {
|
|
node_b = char_data->find_joint(p._b);
|
|
}
|
|
|
|
if (node_b == (EggJointData *)NULL) {
|
|
nout << "No joint named " << p._b << " in " << char_data->get_name()
|
|
<< ".\n";
|
|
} else if (node_a == (EggJointData *)NULL) {
|
|
nout << "No joint named " << p._a << " in " << char_data->get_name()
|
|
<< ".\n";
|
|
} else {
|
|
node_a->reparent_to(node_b);
|
|
did_anything = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_optimal_hierarchy) {
|
|
did_anything = true;
|
|
for (int ci = 0; ci < num_characters; ci++) {
|
|
EggCharacterData *char_data = _collection->get_character(ci);
|
|
nout << "Computing optimal hierarchy for "
|
|
<< char_data->get_name() << ".\n";
|
|
char_data->choose_optimal_hierarchy();
|
|
nout << "Done computing optimal hierarchy for "
|
|
<< char_data->get_name() << ".\n";
|
|
}
|
|
}
|
|
|
|
return did_anything;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: EggOptchar::zero_channels
|
|
// Access: Private
|
|
// Description: Zeroes out the channels specified by the user on the
|
|
// command line.
|
|
//
|
|
// Returns true if any operation was performed, false
|
|
// otherwise.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool EggOptchar::
|
|
zero_channels() {
|
|
bool did_anything = false;
|
|
int num_characters = _collection->get_num_characters();
|
|
|
|
StringPairs::const_iterator spi;
|
|
for (spi = _zero_channels.begin(); spi != _zero_channels.end(); ++spi) {
|
|
const StringPair &p = (*spi);
|
|
|
|
for (int ci = 0; ci < num_characters; ci++) {
|
|
EggCharacterData *char_data = _collection->get_character(ci);
|
|
EggJointData *joint_data = char_data->find_joint(p._a);
|
|
|
|
if (joint_data == (EggJointData *)NULL) {
|
|
nout << "No joint named " << p._a << " in " << char_data->get_name()
|
|
<< ".\n";
|
|
} else {
|
|
joint_data->zero_channels(p._b);
|
|
did_anything = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return did_anything;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: EggOptchar::quantize_channels
|
|
// Access: Private
|
|
// Description: Quantizes the channels specified by the user on the
|
|
// command line.
|
|
//
|
|
// Returns true if any operation was performed, false
|
|
// otherwise.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool EggOptchar::
|
|
quantize_channels() {
|
|
bool did_anything = false;
|
|
int num_characters = _collection->get_num_characters();
|
|
|
|
DoubleStrings::const_iterator spi;
|
|
for (spi = _quantize_anims.begin(); spi != _quantize_anims.end(); ++spi) {
|
|
const DoubleString &p = (*spi);
|
|
|
|
for (int ci = 0; ci < num_characters; ci++) {
|
|
EggCharacterData *char_data = _collection->get_character(ci);
|
|
EggJointData *joint_data = char_data->get_root_joint();
|
|
|
|
if (joint_data != (EggJointData *)NULL) {
|
|
joint_data->quantize_channels(p._b, p._a);
|
|
did_anything = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return did_anything;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: EggOptchar::analyze_joints
|
|
// Access: Private
|
|
// Description: Recursively walks the joint hierarchy for a
|
|
// particular character, indentifying properties of each
|
|
// joint.
|
|
////////////////////////////////////////////////////////////////////
|
|
void EggOptchar::
|
|
analyze_joints(EggJointData *joint_data, int level) {
|
|
PT(EggOptcharUserData) user_data = new EggOptcharUserData;
|
|
joint_data->set_user_data(user_data);
|
|
|
|
if (level == 1) {
|
|
// The child joints of the root joint are deemed "top" joints.
|
|
// These may not be removed unless they are empty (because their
|
|
// vertices have no joint to be moved into).
|
|
user_data->_flags |= EggOptcharUserData::F_top;
|
|
}
|
|
|
|
// Analyze the table of matrices for this joint, checking to see if
|
|
// they're all the same across all frames, or if any of them are
|
|
// different; also look for empty joints (that control no vertices).
|
|
int num_mats = 0;
|
|
bool different_mat = false;
|
|
bool has_vertices = false;
|
|
|
|
int num_models = joint_data->get_num_models();
|
|
int i;
|
|
for (i = 0; i < num_models; i++) {
|
|
if (joint_data->has_model(i)) {
|
|
EggBackPointer *model = joint_data->get_model(i);
|
|
if (model->has_vertices()) {
|
|
has_vertices = true;
|
|
}
|
|
|
|
int num_frames = joint_data->get_num_frames(i);
|
|
|
|
int f;
|
|
for (f = 0; f < num_frames && !different_mat; f++) {
|
|
LMatrix4d mat = joint_data->get_frame(i, f);
|
|
num_mats++;
|
|
if (num_mats == 1) {
|
|
// This is the first matrix.
|
|
user_data->_static_mat = mat;
|
|
|
|
} else {
|
|
// This is a second or later matrix.
|
|
if (!mat.almost_equal(user_data->_static_mat, 0.0001)) {
|
|
// It's different than the first one.
|
|
different_mat = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!different_mat) {
|
|
// All the mats are the same for this joint.
|
|
user_data->_flags |= EggOptcharUserData::F_static;
|
|
|
|
if (num_mats == 0 ||
|
|
user_data->_static_mat.almost_equal(LMatrix4d::ident_mat(), 0.0001)) {
|
|
// It's not only static, but it's the identity matrix.
|
|
user_data->_flags |= EggOptcharUserData::F_identity;
|
|
}
|
|
}
|
|
|
|
if (!has_vertices) {
|
|
// There are no vertices in this joint.
|
|
user_data->_flags |= EggOptcharUserData::F_empty;
|
|
}
|
|
|
|
int num_children = joint_data->get_num_children();
|
|
for (i = 0; i < num_children; i++) {
|
|
analyze_joints(joint_data->get_child(i), level + 1);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: EggOptchar::analyze_sliders
|
|
// Access: Private
|
|
// Description: Linearly walks the slider list for a particular
|
|
// character, indentifying properties of each slider.
|
|
////////////////////////////////////////////////////////////////////
|
|
void EggOptchar::
|
|
analyze_sliders(EggCharacterData *char_data) {
|
|
int num_sliders = char_data->get_num_sliders();
|
|
for (int si = 0; si < num_sliders; si++) {
|
|
EggSliderData *slider_data = char_data->get_slider(si);
|
|
|
|
PT(EggOptcharUserData) user_data = new EggOptcharUserData;
|
|
slider_data->set_user_data(user_data);
|
|
|
|
// Analyze the table of values for this slider, checking to see if
|
|
// they're all the same across all frames, or if any of them are
|
|
// different; also look for empty sliders (that control no vertices).
|
|
int num_values = 0;
|
|
bool different_value = false;
|
|
bool has_vertices = false;
|
|
|
|
int num_models = slider_data->get_num_models();
|
|
for (int i = 0; i < num_models; i++) {
|
|
if (slider_data->has_model(i)) {
|
|
EggBackPointer *model = slider_data->get_model(i);
|
|
if (model->has_vertices()) {
|
|
has_vertices = true;
|
|
}
|
|
|
|
int num_frames = slider_data->get_num_frames(i);
|
|
|
|
int f;
|
|
for (f = 0; f < num_frames && !different_value; f++) {
|
|
double value = slider_data->get_frame(i, f);
|
|
num_values++;
|
|
if (num_values == 1) {
|
|
// This is the first value.
|
|
user_data->_static_value = value;
|
|
|
|
} else {
|
|
// This is a second or later value.
|
|
if (!IS_THRESHOLD_EQUAL(value, user_data->_static_value, 0.0001)) {
|
|
// It's different than the first one.
|
|
different_value = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!different_value) {
|
|
// All the values are the same for this slider.
|
|
user_data->_flags |= EggOptcharUserData::F_static;
|
|
|
|
if (num_values == 0 || IS_THRESHOLD_ZERO(user_data->_static_value, 0.0001)) {
|
|
// It's not only static, but it's the identity value.
|
|
user_data->_flags |= EggOptcharUserData::F_identity;
|
|
}
|
|
}
|
|
|
|
if (!has_vertices) {
|
|
// There are no vertices in this slider.
|
|
user_data->_flags |= EggOptcharUserData::F_empty;
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: EggOptchar::list_joints
|
|
// Access: Private
|
|
// Description: Outputs a list of the joint hierarchy.
|
|
////////////////////////////////////////////////////////////////////
|
|
void EggOptchar::
|
|
list_joints(EggJointData *joint_data, int indent_level, bool verbose) {
|
|
// Don't list the root joint, which is artificially created when the
|
|
// character is loaded. Instead, list each child as it is
|
|
// encountered.
|
|
|
|
int num_children = joint_data->get_num_children();
|
|
for (int i = 0; i < num_children; i++) {
|
|
EggJointData *child_data = joint_data->get_child(i);
|
|
describe_component(child_data, indent_level, verbose);
|
|
|
|
list_joints(child_data, indent_level + 2, verbose);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: EggOptchar::list_joints_p
|
|
// Access: Private
|
|
// Description: Outputs a list of the joint hierarchy as a series of
|
|
// -p joint,parent commands.
|
|
////////////////////////////////////////////////////////////////////
|
|
void EggOptchar::
|
|
list_joints_p(EggJointData *joint_data, int &col) {
|
|
// As above, don't list the root joint.
|
|
|
|
int num_children = joint_data->get_num_children();
|
|
static const int max_col = 72;
|
|
|
|
for (int i = 0; i < num_children; i++) {
|
|
EggJointData *child_data = joint_data->get_child(i);
|
|
// We send output to cout instead of nout to avoid the
|
|
// word-wrapping, and also to allow the user to redirect this
|
|
// easily to a file.
|
|
|
|
string text = string(" -p ") + child_data->get_name() +
|
|
string(",") + joint_data->get_name();
|
|
if (col == 0) {
|
|
cout << " " << text;
|
|
col = 4 + text.length();
|
|
} else {
|
|
col += text.length();
|
|
if (col >= max_col) {
|
|
cout << " \\\n " << text;
|
|
col = 4 + text.length();
|
|
} else {
|
|
cout << text;
|
|
}
|
|
}
|
|
|
|
list_joints_p(child_data, col);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: EggOptchar::list_scalars
|
|
// Access: Private
|
|
// Description: Outputs a list of the scalars.
|
|
////////////////////////////////////////////////////////////////////
|
|
void EggOptchar::
|
|
list_scalars(EggCharacterData *char_data, bool verbose) {
|
|
int num_sliders = char_data->get_num_sliders();
|
|
for (int si = 0; si < num_sliders; si++) {
|
|
EggSliderData *slider_data = char_data->get_slider(si);
|
|
describe_component(slider_data, 0, verbose);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: EggOptchar::describe_component
|
|
// Access: Private
|
|
// Description: Describes one particular slider or joint.
|
|
////////////////////////////////////////////////////////////////////
|
|
void EggOptchar::
|
|
describe_component(EggComponentData *comp_data, int indent_level,
|
|
bool verbose) {
|
|
// We use cout instead of nout so the user can easily redirect this
|
|
// to a file.
|
|
indent(cout, indent_level)
|
|
<< comp_data->get_name();
|
|
|
|
if (verbose) {
|
|
EggOptcharUserData *user_data =
|
|
DCAST(EggOptcharUserData, comp_data->get_user_data());
|
|
if (user_data->is_identity()) {
|
|
cout << " (identity)";
|
|
} else if (user_data->is_static()) {
|
|
cout << " (static)";
|
|
}
|
|
if (user_data->is_empty()) {
|
|
cout << " (empty)";
|
|
}
|
|
if (user_data->is_top()) {
|
|
cout << " (top)";
|
|
}
|
|
}
|
|
cout << "\n";
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: EggOptchar::do_reparent
|
|
// Access: Private
|
|
// Description: Performs all of the queued up reparenting operations.
|
|
////////////////////////////////////////////////////////////////////
|
|
void EggOptchar::
|
|
do_reparent() {
|
|
bool all_ok = true;
|
|
|
|
int num_characters = _collection->get_num_characters();
|
|
for (int ci = 0; ci < num_characters; ci++) {
|
|
EggCharacterData *char_data = _collection->get_character(ci);
|
|
if (!char_data->do_reparent()) {
|
|
all_ok = false;
|
|
}
|
|
}
|
|
|
|
if (!all_ok) {
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: EggOptchar::quantize_vertices
|
|
// Access: Private
|
|
// Description: Walks through all of the loaded egg files, looking
|
|
// for vertices whose joint memberships are then
|
|
// quantized according to _vref_quantum.
|
|
////////////////////////////////////////////////////////////////////
|
|
void EggOptchar::
|
|
quantize_vertices() {
|
|
Eggs::iterator ei;
|
|
for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) {
|
|
quantize_vertices(*ei);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: EggOptchar::quantize_vertices
|
|
// Access: Private
|
|
// Description: Recursively walks through the indicated egg
|
|
// hierarchy, looking for vertices whose joint
|
|
// memberships are then quantized according to
|
|
// _vref_quantum.
|
|
////////////////////////////////////////////////////////////////////
|
|
void EggOptchar::
|
|
quantize_vertices(EggNode *egg_node) {
|
|
if (egg_node->is_of_type(EggVertexPool::get_class_type())) {
|
|
EggVertexPool *vpool = DCAST(EggVertexPool, egg_node);
|
|
EggVertexPool::iterator vi;
|
|
for (vi = vpool->begin(); vi != vpool->end(); ++vi) {
|
|
quantize_vertex(*vi);
|
|
}
|
|
|
|
} else if (egg_node->is_of_type(EggGroupNode::get_class_type())) {
|
|
EggGroupNode *group = DCAST(EggGroupNode, egg_node);
|
|
EggGroupNode::iterator ci;
|
|
for (ci = group->begin(); ci != group->end(); ++ci) {
|
|
quantize_vertices(*ci);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: EggOptchar::quantize_vertex
|
|
// Access: Private
|
|
// Description: Quantizes the indicated vertex's joint membership.
|
|
////////////////////////////////////////////////////////////////////
|
|
void EggOptchar::
|
|
quantize_vertex(EggVertex *egg_vertex) {
|
|
if (egg_vertex->gref_size() == 0) {
|
|
// Never mind on this vertex.
|
|
return;
|
|
}
|
|
|
|
// First, get a copy of the existing membership.
|
|
VertexMemberships memberships;
|
|
EggVertex::GroupRef::const_iterator gi;
|
|
double net_membership = 0.0;
|
|
for (gi = egg_vertex->gref_begin(); gi != egg_vertex->gref_end(); ++gi) {
|
|
EggGroup *group = (*gi);
|
|
double membership = group->get_vertex_membership(egg_vertex);
|
|
memberships.push_back(VertexMembership(group, membership));
|
|
net_membership += membership;
|
|
}
|
|
nassertv(net_membership != 0.0);
|
|
|
|
// Now normalize all the memberships so the net membership is 1.0,
|
|
// and then quantize the result (if the user so requested).
|
|
double factor = 1.0 / net_membership;
|
|
net_membership = 0.0;
|
|
VertexMemberships::iterator mi;
|
|
VertexMemberships::iterator largest = memberships.begin();
|
|
|
|
for (mi = memberships.begin(); mi != memberships.end(); ++mi) {
|
|
if ((*largest) < (*mi)) {
|
|
// Remember the largest membership value, so we can readjust it
|
|
// at the end.
|
|
largest = mi;
|
|
}
|
|
|
|
double value = (*mi)._membership * factor;
|
|
if (_vref_quantum != 0.0) {
|
|
value = floor(value / _vref_quantum + 0.5) * _vref_quantum;
|
|
}
|
|
(*mi)._membership = value;
|
|
|
|
net_membership += value;
|
|
}
|
|
|
|
// The the largest membership value gets corrected again by the
|
|
// roundoff error.
|
|
(*largest)._membership += 1.0 - net_membership;
|
|
|
|
// Finally, walk back through and apply these computed values to the
|
|
// vertex.
|
|
for (mi = memberships.begin(); mi != memberships.end(); ++mi) {
|
|
(*mi)._group->set_vertex_membership(egg_vertex, (*mi)._membership);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: EggOptchar::do_flag_groups
|
|
// Access: Private
|
|
// Description: Recursively walks the indicated egg hierarchy,
|
|
// looking for groups that match one of the group names
|
|
// in _flag_groups, and renaming geometry appropriately.
|
|
////////////////////////////////////////////////////////////////////
|
|
void EggOptchar::
|
|
do_flag_groups(EggGroupNode *egg_group) {
|
|
bool matched = false;
|
|
string name;
|
|
FlagGroups::const_iterator fi;
|
|
for (fi = _flag_groups.begin();
|
|
fi != _flag_groups.end() && !matched;
|
|
++fi) {
|
|
const FlagGroupsEntry &entry = (*fi);
|
|
Globs::const_iterator si;
|
|
for (si = entry._groups.begin();
|
|
si != entry._groups.end() && !matched;
|
|
++si) {
|
|
if ((*si).matches(egg_group->get_name())) {
|
|
matched = true;
|
|
name = entry._name;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (matched) {
|
|
// Ok, this group matched one of the user's command-line renames.
|
|
// Rename all the primitives in this group and below to the
|
|
// indicated name; this will expose the primitives through the
|
|
// character loader.
|
|
rename_primitives(egg_group, name);
|
|
}
|
|
|
|
// Now recurse on children.
|
|
EggGroupNode::iterator gi;
|
|
for (gi = egg_group->begin(); gi != egg_group->end(); ++gi) {
|
|
EggNode *child = (*gi);
|
|
if (child->is_of_type(EggGroupNode::get_class_type())) {
|
|
EggGroupNode *group = DCAST(EggGroupNode, child);
|
|
do_flag_groups(group);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: EggOptchar::rename_primitives
|
|
// Access: Private
|
|
// Description: Recursively walks the indicated egg hierarchy,
|
|
// renaming geometry to the indicated name.
|
|
////////////////////////////////////////////////////////////////////
|
|
void EggOptchar::
|
|
rename_primitives(EggGroupNode *egg_group, const string &name) {
|
|
EggGroupNode::iterator gi;
|
|
for (gi = egg_group->begin(); gi != egg_group->end(); ++gi) {
|
|
EggNode *child = (*gi);
|
|
|
|
if (child->is_of_type(EggGroupNode::get_class_type())) {
|
|
EggGroupNode *group = DCAST(EggGroupNode, child);
|
|
rename_primitives(group, name);
|
|
|
|
} else if (child->is_of_type(EggPrimitive::get_class_type())) {
|
|
child->set_name(name);
|
|
}
|
|
}
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
// A call to pystub() to force libpystub.so to be linked in.
|
|
pystub();
|
|
|
|
EggOptchar prog;
|
|
prog.parse_command_line(argc, argv);
|
|
prog.run();
|
|
return 0;
|
|
}
|