open_toontown_panda3d/pandatool/src/eggcharbase/eggCharacterData.cxx

411 lines
13 KiB
C++

/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file eggCharacterData.cxx
* @author drose
* @date 2001-02-23
*/
#include "eggCharacterData.h"
#include "eggCharacterCollection.h"
#include "eggCharacterDb.h"
#include "eggJointData.h"
#include "eggSliderData.h"
#include "indent.h"
#include <algorithm>
// An STL function object to sort the joint list in order from highest to
// lowest in the new hierarchy. Used in do_reparent().
class OrderJointsByNewDepth {
public:
bool operator()(const EggJointData *a, const EggJointData *b) const {
return a->_new_parent_depth < b->_new_parent_depth;
}
};
/**
*
*/
EggCharacterData::
EggCharacterData(EggCharacterCollection *collection) :
_component_names("_", "joint_")
{
_collection = collection;
_root_joint = _collection->make_joint_data(this);
// The fictitious root joint is not added to the _components list.
}
/**
*
*/
EggCharacterData::
~EggCharacterData() {
delete _root_joint;
Sliders::iterator si;
for (si = _sliders.begin(); si != _sliders.end(); ++si) {
EggSliderData *slider = (*si);
delete slider;
}
}
/**
* Renames all of the models in the character data to the indicated name.
* This is the name that is used to identify unique skeleton hierarchies; if
* you set two different models to the same name, they will be loaded together
* as if they are expected to have the same skeleton hierarchy.
*/
void EggCharacterData::
rename_char(const std::string &name) {
Models::iterator mi;
for (mi = _models.begin(); mi != _models.end(); ++mi) {
(*mi)._model_root->set_name(name);
}
set_name(name);
}
/**
* Indicates that the given model_index (with the indicated model_root) is
* associated with this character. This is normally called by the
* EggCharacterCollection class as new models are discovered.
*
* A "model" here is either a character model (or one LOD of a character
* model), or a character animation file: in either case, a hierarchy of
* joints.
*/
void EggCharacterData::
add_model(int model_index, EggNode *model_root, EggData *egg_data) {
Model m;
m._model_index = model_index;
m._model_root = model_root;
m._egg_data = egg_data;
_models.push_back(m);
}
/**
* Returns the number of frames of animation of the indicated model. This is
* more reliable than asking a particular joint or slider of the animation for
* its number of frames, since a particular joint may have only 1 frame (if it
* is unanimated), even though the overall animation has many frames.
*/
int EggCharacterData::
get_num_frames(int model_index) const {
int max_num_frames = 0;
Components::const_iterator ci;
for (ci = _components.begin(); ci != _components.end(); ++ci) {
EggComponentData *component = (*ci);
int num_frames = component->get_num_frames(model_index);
if (num_frames > 1) {
// We have a winner. Assume all other components will be similar.
return num_frames;
}
max_num_frames = std::max(max_num_frames, num_frames);
}
// Every component had either 1 frame or 0 frames. Return the maximum of
// these.
return max_num_frames;
}
/**
* Returns the stated frame rate of the specified model. Similar to
* get_num_frames().
*/
double EggCharacterData::
get_frame_rate(int model_index) const {
Components::const_iterator ci;
for (ci = _components.begin(); ci != _components.end(); ++ci) {
EggComponentData *component = (*ci);
double frame_rate = component->get_frame_rate(model_index);
if (frame_rate != 0.0) {
// We have a winner. Assume all other components will be similar.
return frame_rate;
}
}
return 0.0;
}
/**
* Walks through each component and ensures that all have the same number of
* frames of animation (except for those that contain 0 or 1 frames, of
* course). Returns true if all are valid, false if there is a discreprency
* (in which case the shorter component are extended).
*/
bool EggCharacterData::
check_num_frames(int model_index) {
int max_num_frames = 0;
bool any_violations = false;
Components::const_iterator ci;
for (ci = _components.begin(); ci != _components.end(); ++ci) {
EggComponentData *component = (*ci);
int num_frames = component->get_num_frames(model_index);
if (num_frames > 1 && max_num_frames > 1 &&
max_num_frames != num_frames) {
// If we have two different opinions about the number of frames (other
// than 0 or 1), we have a discrepency. This is an error condition.
any_violations = true;
}
max_num_frames = std::max(max_num_frames, num_frames);
}
if (any_violations) {
// Now go back through and force all components to the appropriate length.
for (ci = _components.begin(); ci != _components.end(); ++ci) {
EggComponentData *component = (*ci);
int num_frames = component->get_num_frames(model_index);
if (num_frames > 1 && max_num_frames != num_frames) {
component->extend_to(model_index, max_num_frames);
}
}
}
return !any_violations;
}
/**
* Begins the process of restructuring the joint hierarchy according to the
* previous calls to reparent_to() on various joints. This will reparent the
* joint hierachy in all models as requested, while adjusting the transforms
* as appropriate so that each joint retains the same net transform across all
* frames that it had before the operation. Returns true on success, false on
* failure.
*/
bool EggCharacterData::
do_reparent() {
typedef pset<EggJointData *> InvalidSet;
InvalidSet invalid_set;
// To begin, make sure the list of new_children is accurate.
Joints::const_iterator ji;
for (ji = _joints.begin(); ji != _joints.end(); ++ji) {
EggJointData *joint_data = (*ji);
joint_data->do_begin_reparent();
}
// We also need to clear the children on the root joint, but the root joint
// doesn't get any of the other operations (including finish_reparent)
// applied to it.
_root_joint->do_begin_reparent();
// Now, check for cycles in the new parenting hierarchy, and also sort the
// joints in order from top to bottom in the new hierarchy.
for (ji = _joints.begin(); ji != _joints.end(); ++ji) {
EggJointData *joint_data = (*ji);
pset<EggJointData *> chain;
if (joint_data->calc_new_parent_depth(chain)) {
nout << "Cycle detected in parent chain for " << joint_data->get_name()
<< "!\n";
return false;
}
}
sort(_joints.begin(), _joints.end(), OrderJointsByNewDepth());
// Now compute the new transforms for the joints' new positions. This is
// done recursively through the new parent hierarchy, so we can take
// advantage of caching the net value for a particular frame.
Models::const_iterator mi;
for (mi = _models.begin(); mi != _models.end(); ++mi) {
EggCharacterDb db;
int model_index = (*mi)._model_index;
int num_frames = get_num_frames(model_index);
nout << " computing " << (mi - _models.begin()) + 1
<< " of " << _models.size()
<< ": " << (*mi)._egg_data->get_egg_filename()
<< " (" << num_frames << " frames)\n";
for (int f = 0; f < num_frames; f++) {
// First, walk through all the joints and flush the computed net
// transforms from before.
for (ji = _joints.begin(); ji != _joints.end(); ++ji) {
EggJointData *joint_data = (*ji);
joint_data->do_begin_compute_reparent();
}
_root_joint->do_begin_compute_reparent();
// Now go back through and compute the reparented transforms, caching
// net transforms as necessary.
for (ji = _joints.begin(); ji != _joints.end(); ++ji) {
EggJointData *joint_data = (*ji);
if (!joint_data->do_compute_reparent(model_index, f, db)) {
// Oops, we got an invalid transform.
invalid_set.insert(joint_data);
}
}
}
// Finally, apply the computations to the joints.
for (ji = _joints.begin(); ji != _joints.end(); ++ji) {
EggJointData *joint_data = (*ji);
if (!joint_data->do_joint_rebuild(model_index, db)) {
invalid_set.insert(joint_data);
}
}
}
// Now remove all of the old children and add in the new children.
for (ji = _joints.begin(); ji != _joints.end(); ++ji) {
EggJointData *joint_data = (*ji);
joint_data->do_finish_reparent();
}
// Report the set of joints that failed. It really shouldn't be possible
// for any joints to fail, so if you see anything reported here, something
// went wrong at a fundamental level. Perhaps a problem with
// decompose_matrix().
InvalidSet::const_iterator si;
for (si = invalid_set.begin(); si != invalid_set.end(); ++si) {
EggJointData *joint_data = (*si);
// Don't bother reporting joints that no longer have a parent, since we
// don't care about joints that are now outside the hierarchy.
if (joint_data->get_parent() != nullptr) {
nout << "Warning: reparenting " << joint_data->get_name()
<< " to ";
if (joint_data->get_parent() == _root_joint) {
nout << "the root";
} else {
nout << joint_data->get_parent()->get_name();
}
nout << " results in an invalid transform.\n";
}
}
return invalid_set.empty();
}
/**
* Chooses the best possible parent joint for each of the joints in the
* hierarchy, based on the score computed by
* EggJointData::score_reparent_to(). This is a fairly expensive operation
* that involves lots of recomputing of transforms across the hierarchy.
*
* The joints are not actually reparented yet, but the new_parent of each
* joint is set. Call do_reparent() to actually perform the suggested
* reparenting operation.
*/
void EggCharacterData::
choose_optimal_hierarchy() {
EggCharacterDb db;
Joints::const_iterator ji, jj;
for (ji = _joints.begin(); ji != _joints.end(); ++ji) {
EggJointData *joint_data = (*ji);
EggJointData *best_parent = joint_data->get_parent();
int best_score = joint_data->score_reparent_to(best_parent, db);
for (jj = _joints.begin(); jj != _joints.end(); ++jj) {
EggJointData *possible_parent = (*jj);
if (possible_parent != joint_data && possible_parent != best_parent &&
!joint_data->is_new_ancestor(possible_parent)) {
int score = joint_data->score_reparent_to(possible_parent, db);
if (score >= 0 && (best_score < 0 || score < best_score)) {
best_parent = possible_parent;
best_score = score;
}
}
}
// Also consider reparenting the node to the root.
EggJointData *possible_parent = get_root_joint();
if (possible_parent != best_parent) {
int score = joint_data->score_reparent_to(possible_parent, db);
if (score >= 0 && (best_score < 0 || score < best_score)) {
best_parent = possible_parent;
best_score = score;
}
}
if (best_parent != nullptr &&
best_parent != joint_data->_parent) {
nout << "best parent for " << joint_data->get_name() << " is "
<< best_parent->get_name() << "\n";
joint_data->reparent_to(best_parent);
}
}
}
/**
* Returns the slider with the indicated name, or NULL if no slider has that
* name.
*/
EggSliderData *EggCharacterData::
find_slider(const std::string &name) const {
SlidersByName::const_iterator si;
si = _sliders_by_name.find(name);
if (si != _sliders_by_name.end()) {
return (*si).second;
}
return nullptr;
}
/**
* Returns the slider matching the indicated name. If no such slider exists
* already, creates a new one.
*/
EggSliderData *EggCharacterData::
make_slider(const std::string &name) {
SlidersByName::const_iterator si;
si = _sliders_by_name.find(name);
if (si != _sliders_by_name.end()) {
return (*si).second;
}
EggSliderData *slider = _collection->make_slider_data(this);
slider->set_name(name);
_sliders_by_name.insert(SlidersByName::value_type(name, slider));
_sliders.push_back(slider);
_components.push_back(slider);
return slider;
}
/**
* Returns the estimated amount of memory, in megabytes, that will be required
* to perform the do_reparent() operation. This is used mainly be
* EggCharacterDb to decide up front whether to store this data in-RAM or on-
* disk.
*/
size_t EggCharacterData::
estimate_db_size() const {
// Count how much memory we will need to store the interim transforms. This
// is models * joints * frames * 3 * sizeof(LMatrix4d).
size_t mj_frames = 0;
Models::const_iterator mi;
for (mi = _models.begin(); mi != _models.end(); ++mi) {
int model_index = (*mi)._model_index;
size_t num_frames = (size_t)get_num_frames(model_index);
mj_frames += num_frames * _joints.size();
}
// We do this operation a bit carefully, to guard against integer overflow.
size_t mb_needed = ((mj_frames * 3 / 1024) * sizeof(LMatrix4d)) / 1024;
return mb_needed;
}
/**
*
*/
void EggCharacterData::
write(std::ostream &out, int indent_level) const {
indent(out, indent_level)
<< "Character " << get_name() << ":\n";
get_root_joint()->write(out, indent_level + 2);
Sliders::const_iterator si;
for (si = _sliders.begin(); si != _sliders.end(); ++si) {
EggSliderData *slider = (*si);
slider->write(out, indent_level + 2);
}
}