open_toontown_panda3d/pandatool/src/eggcharbase/eggCharacterCollection.cxx

747 lines
28 KiB
C++

// Filename: eggCharacterCollection.cxx
// Created by: drose (26Feb01)
//
////////////////////////////////////////////////////////////////////
//
// 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 "eggCharacterCollection.h"
#include "eggCharacterData.h"
#include "eggJointData.h"
#include "eggSliderData.h"
#include "dcast.h"
#include "eggGroup.h"
#include "eggTable.h"
#include "eggPrimitive.h"
#include "eggVertex.h"
#include "eggVertexUV.h"
#include "eggMorphList.h"
#include "eggSAnimData.h"
#include "indirectCompareNames.h"
#include "indent.h"
#include <algorithm>
////////////////////////////////////////////////////////////////////
// Function: EggCharacterCollection::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
EggCharacterCollection::
EggCharacterCollection() {
_next_model_index = 0;
}
////////////////////////////////////////////////////////////////////
// Function: EggCharacterCollection::Destructor
// Access: Public, Virtual
// Description:
////////////////////////////////////////////////////////////////////
EggCharacterCollection::
~EggCharacterCollection() {
Characters::iterator ci;
for (ci = _characters.begin(); ci != _characters.end(); ++ci) {
delete (*ci);
}
}
////////////////////////////////////////////////////////////////////
// Function: EggCharacterCollection::add_egg
// Access: Public
// Description: Adds a new egg file to the list of models and
// animation files for this particular character.
//
// Returns the new egg_index if the file is successfully
// added, or -1 if there is some problem (for instance,
// it does not contain a character model or animation
// table).
//
// If the joint hierarchy does not match the existing
// joint hierarchy, a best match is attempted.
////////////////////////////////////////////////////////////////////
int EggCharacterCollection::
add_egg(EggData *egg) {
_top_egg_nodes.clear();
if (!scan_hierarchy(egg)) {
return -1;
}
int egg_index = _eggs.size();
_eggs.push_back(EggInfo());
EggInfo &egg_info = _eggs.back();
egg_info._egg = egg;
egg_info._first_model_index = 0;
// Now, for each model, add an entry in the egg_info and match the
// joint hierarchy to the known joints.
TopEggNodesByName::iterator tni;
for (tni = _top_egg_nodes.begin(); tni != _top_egg_nodes.end(); ++tni) {
string character_name = (*tni).first;
TopEggNodes &top_nodes = (*tni).second;
EggCharacterData *char_data = make_character(character_name);
EggJointData *root_joint = char_data->get_root_joint();
TopEggNodes::iterator ti;
for (ti = top_nodes.begin(); ti != top_nodes.end(); ++ti) {
EggNode *model_root = (*ti).first;
ModelDescription &desc = (*ti).second;
int model_index = _next_model_index++;
if (egg_info._models.empty()) {
egg_info._first_model_index = model_index;
}
egg_info._models.push_back(model_root);
char_data->add_model(model_index, model_root, egg);
nassertr(model_index == (int)_characters_by_model_index.size(), -1);
_characters_by_model_index.push_back(char_data);
root_joint->add_back_pointer(model_index, desc._root_node);
match_egg_nodes(char_data, root_joint, desc._top_nodes,
egg_index, model_index);
scan_for_morphs(model_root, model_index, char_data);
scan_for_sliders(model_root, model_index, char_data);
}
}
return egg_index;
}
////////////////////////////////////////////////////////////////////
// Function: EggCharacterCollection::get_character_by_name
// Access: Public
// Description: Returns the Character with the indicated name, if it
// exists in the collection, or NULL if it does not.
////////////////////////////////////////////////////////////////////
EggCharacterData *EggCharacterCollection::
get_character_by_name(const string &character_name) const {
Characters::const_iterator ci;
for (ci = _characters.begin(); ci != _characters.end(); ++ci) {
EggCharacterData *char_data = (*ci);
if (char_data->get_name() == character_name) {
return char_data;
}
}
return (EggCharacterData *)NULL;
}
////////////////////////////////////////////////////////////////////
// Function: EggCharacterCollection::make_character_data
// Access: Public, Virtual
// Description: Allocates and returns a new EggCharacterData
// structure. This is primarily intended as a hook so
// derived classes can customize the type of
// EggCharacterData nodes used to represent the
// characters in this collection.
////////////////////////////////////////////////////////////////////
EggCharacterData *EggCharacterCollection::
make_character_data() {
return new EggCharacterData(this);
}
////////////////////////////////////////////////////////////////////
// Function: EggCharacterCollection::make_joint_data
// Access: Public, Virtual
// Description: Allocates and returns a new EggJointData structure
// for the given character. This is primarily intended
// as a hook so derived classes can customize the type
// of EggJointData nodes used to represent the joint
// hierarchy.
////////////////////////////////////////////////////////////////////
EggJointData *EggCharacterCollection::
make_joint_data(EggCharacterData *char_data) {
return new EggJointData(this, char_data);
}
////////////////////////////////////////////////////////////////////
// Function: EggCharacterCollection::make_slider_data
// Access: Public, Virtual
// Description: Allocates and returns a new EggSliderData structure
// for the given character. This is primarily intended
// as a hook so derived classes can customize the type
// of EggSliderData nodes used to represent the slider
// list.
////////////////////////////////////////////////////////////////////
EggSliderData *EggCharacterCollection::
make_slider_data(EggCharacterData *char_data) {
return new EggSliderData(this, char_data);
}
////////////////////////////////////////////////////////////////////
// Function: EggCharacterCollection::make_character
// Access: Protected
// Description: Allocates and returns a new EggCharacterData object
// representing the named character, if there is not
// already a character by that name.
////////////////////////////////////////////////////////////////////
EggCharacterData *EggCharacterCollection::
make_character(const string &character_name) {
// Does the named character exist yet?
Characters::iterator ci;
for (ci = _characters.begin(); ci != _characters.end(); ++ci) {
EggCharacterData *char_data = (*ci);
if (char_data->get_name() == character_name) {
return char_data;
}
}
// Define a new character.
EggCharacterData *char_data = make_character_data();
char_data->set_name(character_name);
_characters.push_back(char_data);
return char_data;
}
////////////////////////////////////////////////////////////////////
// Function: EggCharacterCollection::scan_hierarchy
// Access: Private
// Description: Walks the given egg data's hierarchy, looking for
// either the start of an animation channel or the start
// of a character model. Returns true if either (or
// both) is found, false if the model appears to have
// nothing to do with characters.
//
// Fills up the _top_egg_nodes according to the nodes
// found.
////////////////////////////////////////////////////////////////////
bool EggCharacterCollection::
scan_hierarchy(EggNode *egg_node) {
if (egg_node->is_of_type(EggGroup::get_class_type())) {
EggGroup *group = DCAST(EggGroup, egg_node);
if (group->get_dart_type() != EggGroup::DT_none) {
// A group with a <Dart> flag begins a character model.
scan_for_top_joints(group, group, group->get_name());
return true;
}
} else if (egg_node->is_of_type(EggTable::get_class_type())) {
EggTable *table = DCAST(EggTable, egg_node);
if (table->get_table_type() == EggTable::TT_bundle) {
// A <Bundle> begins an animation table.
scan_for_top_tables(table, table, table->get_name());
return true;
}
}
bool character_found = false;
if (egg_node->is_of_type(EggGroupNode::get_class_type())) {
EggGroupNode *group = DCAST(EggGroupNode, egg_node);
EggGroupNode::iterator gi;
for (gi = group->begin(); gi != group->end(); ++gi) {
if (scan_hierarchy(*gi)) {
character_found = true;
}
}
}
return character_found;
}
////////////////////////////////////////////////////////////////////
// Function: EggCharacterCollection::scan_for_top_joints
// Access: Private
// Description: Once a character model has been found, continue
// scanning the egg hierarchy to look for the topmost
// <Joint> nodes encountered.
////////////////////////////////////////////////////////////////////
void EggCharacterCollection::
scan_for_top_joints(EggNode *egg_node, EggNode *model_root,
const string &character_name) {
if (egg_node->is_of_type(EggGroup::get_class_type())) {
EggGroup *group = DCAST(EggGroup, egg_node);
if (group->has_lod()) {
// This group has an LOD specification; that indicates multiple
// skeleton hierarchies for this character, one for each LOD.
// We call each of these a separate model.
model_root = group;
}
if (group->get_group_type() == EggGroup::GT_joint) {
// A <Joint> node begins a model hierarchy.
ModelDescription &desc = _top_egg_nodes[character_name][model_root];
desc._root_node = model_root;
desc._top_nodes.push_back(group);
return;
}
}
if (egg_node->is_of_type(EggGroupNode::get_class_type())) {
EggGroupNode *group = DCAST(EggGroupNode, egg_node);
EggGroupNode::iterator gi;
for (gi = group->begin(); gi != group->end(); ++gi) {
scan_for_top_joints(*gi, model_root, character_name);
}
}
}
////////////////////////////////////////////////////////////////////
// Function: EggCharacterCollection::scan_for_top_tables
// Access: Private
// Description: Once an animation has been found, continue scanning
// the egg hierarchy to look for the topmost <Table>
// nodes encountered.
////////////////////////////////////////////////////////////////////
void EggCharacterCollection::
scan_for_top_tables(EggTable *bundle, EggNode *model_root,
const string &character_name) {
// We really only need to check the immediate children of the bundle
// for a table node called "<skeleton>".
EggGroupNode::iterator gi;
for (gi = bundle->begin(); gi != bundle->end(); ++gi) {
EggNode *child = (*gi);
if (child->is_of_type(EggTable::get_class_type())) {
EggTable *table = DCAST(EggTable, child);
if (table->get_name() == "<skeleton>") {
// Here it is! Now the immediate children of this node are
// the top tables.
ModelDescription &desc = _top_egg_nodes[character_name][model_root];
desc._root_node = table;
EggGroupNode::iterator cgi;
for (cgi = table->begin(); cgi != table->end(); ++cgi) {
EggNode *grandchild = (*cgi);
if (grandchild->is_of_type(EggTable::get_class_type())) {
desc._top_nodes.push_back(grandchild);
}
}
}
}
}
}
////////////////////////////////////////////////////////////////////
// Function: EggCharacterCollection::scan_for_morphs
// Access: Private
// Description: Go back through a model's hierarchy and look for
// morph targets on the vertices and primitives.
////////////////////////////////////////////////////////////////////
void EggCharacterCollection::
scan_for_morphs(EggNode *egg_node, int model_index,
EggCharacterData *char_data) {
if (egg_node->is_of_type(EggPrimitive::get_class_type())) {
EggPrimitive *prim = DCAST(EggPrimitive, egg_node);
// Check for morphs on the primitive.
add_morph_back_pointers(prim, prim, model_index, char_data);
// Also check for morphs on each of the prim's vertices.
EggPrimitive::const_iterator vi;
for (vi = prim->begin(); vi != prim->end(); ++vi) {
EggVertex *vertex = (*vi);
add_morph_back_pointers(vertex, vertex, model_index, char_data);
add_morph_back_pointers_vertex(vertex, vertex, model_index, char_data);
EggMorphVertexList::const_iterator mvi;
for (mvi = vertex->_dxyzs.begin();
mvi != vertex->_dxyzs.end();
++mvi) {
const EggMorphVertex &morph = (*mvi);
char_data->make_slider(morph.get_name())->add_back_pointer(model_index, vertex);
}
}
}
if (egg_node->is_of_type(EggGroupNode::get_class_type())) {
EggGroupNode *group = DCAST(EggGroupNode, egg_node);
EggGroupNode::iterator gi;
for (gi = group->begin(); gi != group->end(); ++gi) {
scan_for_morphs(*gi, model_index, char_data);
}
}
}
////////////////////////////////////////////////////////////////////
// Function: EggCharacterCollection::scan_for_sliders
// Access: Private
// Description: Go back to the animation tables and look for morph
// slider animation channels.
////////////////////////////////////////////////////////////////////
void EggCharacterCollection::
scan_for_sliders(EggNode *egg_node, int model_index,
EggCharacterData *char_data) {
if (egg_node->is_of_type(EggTable::get_class_type())) {
EggTable *bundle = DCAST(EggTable, egg_node);
// We really only need to check the immediate children of the
// bundle for a table node called "morph". This is a sibling of
// "<skeleton>", which we found a minute ago, but we weren't ready
// to scan for the morph sliders at the time, so we have to look
// again now.
EggGroupNode::iterator gi;
for (gi = bundle->begin(); gi != bundle->end(); ++gi) {
EggNode *child = (*gi);
if (child->is_of_type(EggTable::get_class_type())) {
EggTable *table = DCAST(EggTable, child);
if (table->get_name() == "morph") {
// Here it is! Now the immediate children of this node are
// all the slider channels.
EggGroupNode::iterator cgi;
for (cgi = table->begin(); cgi != table->end(); ++cgi) {
EggNode *grandchild = (*cgi);
if (grandchild->is_of_type(EggSAnimData::get_class_type())) {
char_data->make_slider(grandchild->get_name())->add_back_pointer(model_index, grandchild);
}
}
}
}
}
}
}
////////////////////////////////////////////////////////////////////
// Function: EggCharacterCollection::add_morph_back_pointers
// Access: Private
// Description: Adds the back pointers for the kinds of morphs we
// might find in an EggAttributes object.
////////////////////////////////////////////////////////////////////
void EggCharacterCollection::
add_morph_back_pointers(EggAttributes *attrib, EggObject *egg_object,
int model_index, EggCharacterData *char_data) {
EggMorphNormalList::const_iterator mni;
for (mni = attrib->_dnormals.begin();
mni != attrib->_dnormals.end();
++mni) {
const EggMorphNormal &morph = (*mni);
char_data->make_slider(morph.get_name())->add_back_pointer(model_index, egg_object);
}
EggMorphColorList::const_iterator mci;
for (mci = attrib->_drgbas.begin();
mci != attrib->_drgbas.end();
++mci) {
const EggMorphColor &morph = (*mci);
char_data->make_slider(morph.get_name())->add_back_pointer(model_index, egg_object);
}
}
////////////////////////////////////////////////////////////////////
// Function: EggCharacterCollection::add_morph_back_pointers_vertex
// Access: Private
// Description: Adds the back pointers for the kinds of morphs we
// might find in an EggVertex object.
////////////////////////////////////////////////////////////////////
void EggCharacterCollection::
add_morph_back_pointers_vertex(EggVertex *vertex, EggObject *egg_object,
int model_index, EggCharacterData *char_data) {
EggVertex::const_uv_iterator ui;
for (ui = vertex->uv_begin(); ui != vertex->uv_end(); ++ui) {
EggVertexUV *vert_uv = (*ui);
EggMorphTexCoordList::const_iterator mti;
for (mti = vert_uv->_duvs.begin();
mti != vert_uv->_duvs.end();
++mti) {
const EggMorphTexCoord &morph = (*mti);
char_data->make_slider(morph.get_name())->add_back_pointer(model_index, egg_object);
}
}
}
////////////////////////////////////////////////////////////////////
// Function: EggCharacterCollection::match_egg_nodes
// Access: Private
// Description: Attempts to match up the indicated list of egg_nodes
// with the children of the given joint_data, by name if
// possible.
//
// Also recurses on each matched joint to build up the
// entire joint hierarchy.
////////////////////////////////////////////////////////////////////
void EggCharacterCollection::
match_egg_nodes(EggCharacterData *char_data, EggJointData *joint_data,
EggNodeList &egg_nodes, int egg_index, int model_index) {
// Sort the list of egg_nodes in order by name. This will make the
// matching up by names easier and more reliable.
sort(egg_nodes.begin(), egg_nodes.end(), IndirectCompareNames<Namable>());
if (joint_data->_children.empty()) {
// If the EggJointData has no children yet, we must be the first.
// Gleefully define all the joints.
EggNodeList::iterator ei;
for (ei = egg_nodes.begin(); ei != egg_nodes.end(); ++ei) {
EggNode *egg_node = (*ei);
EggJointData *data = make_joint_data(char_data);
joint_data->_children.push_back(data);
char_data->_joints.push_back(data);
char_data->_components.push_back(data);
data->_parent = joint_data;
data->_new_parent = joint_data;
found_egg_match(char_data, data, egg_node, egg_index, model_index);
}
} else {
// The EggJointData already has children; therefore, we have to
// match our joints up with the already-existing ones.
EggNodeList extra_egg_nodes;
EggJointData::Children extra_data;
EggNodeList::iterator ei;
EggJointData::Children::iterator di;
ei = egg_nodes.begin();
di = joint_data->_children.begin();
while (ei != egg_nodes.end() && di != joint_data->_children.end()) {
EggNode *egg_node = (*ei);
EggJointData *data = (*di);
if (egg_node->get_name() < data->get_name()) {
// Here's a joint in the egg file, unmatched in the data.
extra_egg_nodes.push_back(egg_node);
++ei;
} else if (data->get_name() < egg_node->get_name()) {
// Here's a joint in the data, umatched by the egg file.
extra_data.push_back(data);
++di;
} else {
// Hey, these two match! Hooray!
found_egg_match(char_data, data, egg_node, egg_index, model_index);
++ei;
++di;
}
}
while (ei != egg_nodes.end()) {
EggNode *egg_node = (*ei);
// Here's a joint in the egg file, unmatched in the data.
extra_egg_nodes.push_back(egg_node);
++ei;
}
while (di != joint_data->_children.end()) {
EggJointData *data = (*di);
// Here's a joint in the data, umatched by the egg file.
extra_data.push_back(data);
++di;
}
if (!extra_egg_nodes.empty()) {
// If we have some extra egg_nodes, we have to find a place to
// match them. (If we only had extra data, we don't care.)
// First, check to see if any of the names match any past-used
// name.
EggNodeList more_egg_nodes;
for (ei = extra_egg_nodes.begin(); ei != extra_egg_nodes.end(); ++ei) {
EggNode *egg_node = (*ei);
bool matched = false;
for (di = extra_data.begin();
di != extra_data.end() && !matched;
++di) {
EggJointData *data = (*di);
if (data->matches_name(egg_node->get_name())) {
matched = true;
found_egg_match(char_data, data, egg_node, egg_index, model_index);
extra_data.erase(di);
}
}
if (!matched) {
// This joint name was never seen before.
more_egg_nodes.push_back(egg_node);
}
}
extra_egg_nodes.swap(more_egg_nodes);
}
if (!extra_egg_nodes.empty()) {
// Ok, we've still got to find a home for these remaining
// egg_nodes.
if (extra_egg_nodes.size() == extra_data.size()) {
// Match 'em up one-for-one.
size_t i;
for (i = 0; i < extra_egg_nodes.size(); i++) {
EggNode *egg_node = extra_egg_nodes[i];
EggJointData *data = extra_data[i];
found_egg_match(char_data, data, egg_node, egg_index, model_index);
}
} else {
// Just tack 'em on the end.
EggNodeList::iterator ei;
for (ei = extra_egg_nodes.begin(); ei != extra_egg_nodes.end(); ++ei) {
EggNode *egg_node = (*ei);
EggJointData *data = make_joint_data(char_data);
joint_data->_children.push_back(data);
char_data->_joints.push_back(data);
char_data->_components.push_back(data);
data->_parent = joint_data;
data->_new_parent = joint_data;
found_egg_match(char_data, data, egg_node, egg_index, model_index);
}
}
}
}
// Now sort the generated joint data hierarchy by name, just to be
// sure.
sort(joint_data->_children.begin(), joint_data->_children.end(),
IndirectCompareNames<Namable>());
}
////////////////////////////////////////////////////////////////////
// Function: EggCharacterCollection::found_egg_match
// Access: Private
// Description: Marks a one-to-one association between the indicated
// EggJointData and the indicated EggNode, and then
// recurses below.
////////////////////////////////////////////////////////////////////
void EggCharacterCollection::
found_egg_match(EggCharacterData *char_data, EggJointData *joint_data,
EggNode *egg_node, int egg_index, int model_index) {
if (egg_node->has_name()) {
joint_data->add_name(egg_node->get_name(), char_data->_component_names);
}
egg_node->set_name(joint_data->get_name());
joint_data->add_back_pointer(model_index, egg_node);
if (egg_node->is_of_type(EggGroupNode::get_class_type())) {
EggGroupNode *group_node = DCAST(EggGroupNode, egg_node);
// Now consider all the children of egg_node that are themselves
// joints or tables.
EggNodeList egg_nodes;
// Two approaches: either we are scanning a model with joints, or
// an animation bundle with tables.
if (egg_node->is_of_type(EggGroup::get_class_type())) {
// A model with joints.
EggGroupNode::iterator gi;
for (gi = group_node->begin(); gi != group_node->end(); ++gi) {
EggNode *child = (*gi);
if (child->is_of_type(EggGroup::get_class_type())) {
EggGroup *group = DCAST(EggGroup, child);
if (group->get_group_type() == EggGroup::GT_joint) {
egg_nodes.push_back(group);
}
}
}
} else {
// An animation bundle with tables.
EggGroupNode::iterator gi;
for (gi = group_node->begin(); gi != group_node->end(); ++gi) {
EggNode *child = (*gi);
if (child->is_of_type(EggTable::get_class_type())) {
EggTable *table = DCAST(EggTable, child);
if (!(table->get_name() == "xform")) {
egg_nodes.push_back(table);
}
}
}
}
if (!egg_nodes.empty()) {
match_egg_nodes(char_data, joint_data, egg_nodes,
egg_index, model_index);
}
}
}
////////////////////////////////////////////////////////////////////
// Function: EggCharacterCollection::rename_char
// Access: Public
// Description: Renames the ith character to the indicated name.
// This name must not already be used by another
// character in the collection.
////////////////////////////////////////////////////////////////////
void EggCharacterCollection::
rename_char(int i, const string &name) {
nassertv(i >= 0 && i < (int)_characters.size());
EggCharacterData *char_data = _characters[i];
if (char_data->get_name() != name) {
nassertv(get_character_by_name(name) == (EggCharacterData *)NULL);
char_data->rename_char(name);
}
}
////////////////////////////////////////////////////////////////////
// Function: EggCharacterCollection::write
// Access: Public, Virtual
// Description:
////////////////////////////////////////////////////////////////////
void EggCharacterCollection::
write(ostream &out, int indent_level) const {
Characters::const_iterator ci;
for (ci = _characters.begin(); ci != _characters.end(); ++ci) {
EggCharacterData *char_data = (*ci);
char_data->write(out, indent_level);
}
}
////////////////////////////////////////////////////////////////////
// Function: EggCharacterCollection::check_errors
// Access: Public
// Description: Can be called after the collection has been
// completely filled up with egg files to output any
// messages from warning conditions that have been
// detected, such as inconsistent animation tables.
//
// In addition to reporting this errors, calling this
// function will also ensure that they are all repaired.
// Pass force_initial_rest_frame as true to also force
// rest frames from different models to be the same if
// they are initially different.
////////////////////////////////////////////////////////////////////
void EggCharacterCollection::
check_errors(ostream &out, bool force_initial_rest_frame) {
Characters::const_iterator ci;
for (ci = _characters.begin(); ci != _characters.end(); ++ci) {
EggCharacterData *char_data = (*ci);
int num_joints = char_data->get_num_joints();
for (int j = 0; j < num_joints; j++) {
EggJointData *joint_data = char_data->get_joint(j);
if (joint_data->rest_frames_differ()) {
if (force_initial_rest_frame) {
joint_data->force_initial_rest_frame();
out << "Forced rest frames the same for " << joint_data->get_name()
<< ".\n";
} else {
out << "Warning: rest frames for " << joint_data->get_name()
<< " differ.\n";
}
}
}
int num_models = char_data->get_num_models();
for (int mi = 0; mi < num_models; mi++) {
int model_index = char_data->get_model_index(mi);
if (!char_data->check_num_frames(model_index)) {
out << "Warning: animation from "
<< char_data->get_egg_data(model_index)->get_egg_filename().get_basename()
<< " had an inconsistent number of frames.\n";
}
}
}
}