open_toontown_panda3d/pandatool/src/eggcharbase/eggJointData.cxx

792 lines
27 KiB
C++

// Filename: eggJointData.cxx
// Created by: drose (23Feb01)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001, 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://www.panda3d.org/license.txt .
//
// To contact the maintainers of this program write to
// panda3d@yahoogroups.com .
//
////////////////////////////////////////////////////////////////////
#include "eggJointData.h"
#include "eggJointNodePointer.h"
#include "eggMatrixTablePointer.h"
#include "dcast.h"
#include "eggGroup.h"
#include "eggTable.h"
#include "indent.h"
#include "fftCompressor.h"
#include "zStream.h"
TypeHandle EggJointData::_type_handle;
////////////////////////////////////////////////////////////////////
// Function: EggJointData::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
EggJointData::
EggJointData(EggCharacterCollection *collection,
EggCharacterData *char_data) :
EggComponentData(collection, char_data)
{
_parent = (EggJointData *)NULL;
_new_parent = (EggJointData *)NULL;
_has_rest_frame = false;
_rest_frames_differ = false;
}
////////////////////////////////////////////////////////////////////
// Function: EggJointData::get_frame
// Access: Public
// Description: Returns the local transform matrix corresponding to
// this joint position in the nth frame in the indicated
// model.
////////////////////////////////////////////////////////////////////
LMatrix4d EggJointData::
get_frame(int model_index, int n) const {
EggBackPointer *back = get_model(model_index);
if (back == (EggBackPointer *)NULL) {
return LMatrix4d::ident_mat();
}
EggJointPointer *joint;
DCAST_INTO_R(joint, back, LMatrix4d::ident_mat());
return joint->get_frame(n);
}
////////////////////////////////////////////////////////////////////
// Function: EggJointData::get_net_frame
// Access: Public
// Description: Returns the complete transform from the root
// corresponding to this joint position in the nth frame
// in the indicated model.
////////////////////////////////////////////////////////////////////
LMatrix4d EggJointData::
get_net_frame(int model_index, int n) const {
EggBackPointer *back = get_model(model_index);
if (back == (EggBackPointer *)NULL) {
return LMatrix4d::ident_mat();
}
EggJointPointer *joint;
DCAST_INTO_R(joint, back, LMatrix4d::ident_mat());
if (joint->get_num_net_frames() < n) {
// Recursively get the previous frame's net, so we have a place to
// stuff this frame's value.
get_net_frame(model_index, n - 1);
}
if (joint->get_num_net_frames() == n) {
// Compute this frame's net, and stuff it in.
LMatrix4d mat = get_frame(model_index, n);
if (_parent != (EggJointData *)NULL) {
mat = mat * _parent->get_net_frame(model_index, n);
}
joint->add_net_frame(mat);
}
return joint->get_net_frame(n);
}
////////////////////////////////////////////////////////////////////
// Function: EggJointData::get_net_frame_inv
// Access: Public
// Description: Returns the inverse of get_net_frame().
////////////////////////////////////////////////////////////////////
LMatrix4d EggJointData::
get_net_frame_inv(int model_index, int n) const {
EggBackPointer *back = get_model(model_index);
if (back == (EggBackPointer *)NULL) {
return LMatrix4d::ident_mat();
}
EggJointPointer *joint;
DCAST_INTO_R(joint, back, LMatrix4d::ident_mat());
if (joint->get_num_net_frame_invs() < n) {
// Recursively get the previous frame's net, so we have a place to
// stuff this frame's value.
get_net_frame_inv(model_index, n - 1);
}
if (joint->get_num_net_frame_invs() == n) {
// Compute this frame's net inverse, and stuff it in.
LMatrix4d mat = get_net_frame(model_index, n);
mat.invert_in_place();
joint->add_net_frame_inv(mat);
}
return joint->get_net_frame_inv(n);
}
////////////////////////////////////////////////////////////////////
// Function: EggJointData::force_initial_rest_frame
// Access: Public
// Description: Forces all of the joints to have the same rest frame
// value as the first joint read in. This is a drastic
// way to repair models whose rest frame values are
// completely bogus, but should not be performed on
// models that are otherwise correct.
////////////////////////////////////////////////////////////////////
void EggJointData::
force_initial_rest_frame() {
if (!has_rest_frame()) {
return;
}
int num_models = get_num_models();
for (int model_index = 0; model_index < num_models; model_index++) {
if (has_model(model_index)) {
EggJointPointer *joint;
DCAST_INTO_V(joint, get_model(model_index));
if (joint->is_of_type(EggJointNodePointer::get_class_type())) {
joint->set_frame(0, get_rest_frame());
}
}
}
_rest_frames_differ = false;
}
////////////////////////////////////////////////////////////////////
// Function: EggJointData::move_vertices_to
// Access: Public
// Description: Moves the vertices assigned to this joint into the
// indicated joint, without changing their weight
// assignments.
////////////////////////////////////////////////////////////////////
void EggJointData::
move_vertices_to(EggJointData *new_owner) {
int num_models = get_num_models();
if (new_owner == (EggJointData *)NULL) {
for (int model_index = 0; model_index < num_models; model_index++) {
if (has_model(model_index)) {
EggJointPointer *joint;
DCAST_INTO_V(joint, get_model(model_index));
joint->move_vertices_to((EggJointPointer *)NULL);
}
}
} else {
for (int model_index = 0; model_index < num_models; model_index++) {
if (has_model(model_index) && new_owner->has_model(model_index)) {
EggJointPointer *joint, *new_joint;
DCAST_INTO_V(joint, get_model(model_index));
DCAST_INTO_V(new_joint, new_owner->get_model(model_index));
joint->move_vertices_to(new_joint);
}
}
}
}
////////////////////////////////////////////////////////////////////
// Function: EggJointData::score_reparent_to
// Access: Public
// Description: Computes a score >= 0 reflecting the similarity of
// the current joint's animation (in world space) to
// that of the indicated potential parent joint (in
// world space). The lower the number, the more similar
// the motion, and the more suitable is the proposed
// parent-child relationship. Returns -1 if there is an
// error.
////////////////////////////////////////////////////////////////////
int EggJointData::
score_reparent_to(EggJointData *new_parent) {
if (!FFTCompressor::is_compression_available()) {
// If we don't have compression compiled in, we can't meaningfully
// score the joints.
return -1;
}
// First, build up a big array of the new transforms this joint
// would receive in all frames of all models, were it reparented to
// the indicated joint.
vector_float i, j, k, a, b, c, x, y, z;
vector_LVecBase3f hprs;
int num_rows = 0;
int num_models = get_num_models();
for (int model_index = 0; model_index < num_models; model_index++) {
EggBackPointer *back = get_model(model_index);
if (back != (EggBackPointer *)NULL) {
EggJointPointer *joint;
DCAST_INTO_R(joint, back, false);
int num_frames = get_num_frames(model_index);
for (int n = 0; n < num_frames; n++) {
LMatrix4d transform;
if (_parent == new_parent) {
// We already have this parent.
transform = LMatrix4d::ident_mat();
} else if (_parent == (EggJointData *)NULL) {
// We are moving from outside the joint hierarchy to within it.
transform = new_parent->get_net_frame_inv(model_index, n);
} else if (new_parent == (EggJointData *)NULL) {
// We are moving from within the hierarchy to outside it.
transform = _parent->get_net_frame(model_index, n);
} else {
// We are changing parents within the hierarchy.
transform =
_parent->get_net_frame(model_index, n) *
new_parent->get_net_frame_inv(model_index, n);
}
transform = joint->get_frame(n) * transform;
LVecBase3d scale, shear, hpr, translate;
if (!decompose_matrix(transform, scale, shear, hpr, translate)) {
// Invalid transform.
return -1;
}
i.push_back(scale[0]);
j.push_back(scale[1]);
k.push_back(scale[2]);
a.push_back(shear[0]);
b.push_back(shear[1]);
c.push_back(shear[2]);
hprs.push_back(LCAST(float, hpr));
x.push_back(translate[0]);
y.push_back(translate[1]);
z.push_back(translate[2]);
num_rows++;
}
}
}
if (num_rows == 0) {
// No data, no score.
return -1;
}
// Now, we derive a score, by the simple expedient of using the
// FFTCompressor to compress the generated transforms, and measuring
// the length of the resulting bitstream.
FFTCompressor compressor;
Datagram dg;
compressor.write_reals(dg, &i[0], num_rows);
compressor.write_reals(dg, &j[0], num_rows);
compressor.write_reals(dg, &k[0], num_rows);
compressor.write_reals(dg, &a[0], num_rows);
compressor.write_reals(dg, &b[0], num_rows);
compressor.write_reals(dg, &c[0], num_rows);
compressor.write_hprs(dg, &hprs[0], num_rows);
compressor.write_reals(dg, &x[0], num_rows);
compressor.write_reals(dg, &y[0], num_rows);
compressor.write_reals(dg, &z[0], num_rows);
#ifndef HAVE_ZLIB
return dg.get_length();
#else
// The FFTCompressor does minimal run-length encoding, but to really
// get an accurate measure we should zlib-compress the resulting
// stream.
ostringstream sstr;
OCompressStream zstr(&sstr, false);
zstr.write((const char *)dg.get_data(), dg.get_length());
zstr.flush();
return sstr.str().length();
#endif
}
////////////////////////////////////////////////////////////////////
// Function: EggJointData::do_rebuild
// Access: Public
// Description: Calls do_rebuild() on all models, and recursively on
// all joints at this node and below. Returns true if
// all models returned true, false otherwise.
////////////////////////////////////////////////////////////////////
bool EggJointData::
do_rebuild() {
bool all_ok = true;
BackPointers::iterator bpi;
for (bpi = _back_pointers.begin(); bpi != _back_pointers.end(); ++bpi) {
EggBackPointer *back = (*bpi);
if (back != (EggBackPointer *)NULL) {
EggJointPointer *joint;
DCAST_INTO_R(joint, back, false);
if (!joint->do_rebuild()) {
all_ok = false;
}
}
}
Children::iterator ci;
for (ci = _children.begin(); ci != _children.end(); ++ci) {
EggJointData *child = (*ci);
if (!child->do_rebuild()) {
all_ok = false;
}
}
return all_ok;
}
////////////////////////////////////////////////////////////////////
// Function: EggJointData::optimize
// Access: Public
// Description: Calls optimize() on all models, and recursively on
// all joints at this node and below.
////////////////////////////////////////////////////////////////////
void EggJointData::
optimize() {
BackPointers::iterator bpi;
for (bpi = _back_pointers.begin(); bpi != _back_pointers.end(); ++bpi) {
EggBackPointer *back = (*bpi);
if (back != (EggBackPointer *)NULL) {
EggJointPointer *joint;
DCAST_INTO_V(joint, back);
joint->optimize();
}
}
Children::iterator ci;
for (ci = _children.begin(); ci != _children.end(); ++ci) {
EggJointData *child = (*ci);
child->optimize();
}
}
////////////////////////////////////////////////////////////////////
// Function: EggJointData::expose
// Access: Public
// Description: Calls expose() on all models for this joint, but does
// not recurse downwards.
////////////////////////////////////////////////////////////////////
void EggJointData::
expose(EggGroup::DCSType dcs_type) {
BackPointers::iterator bpi;
for (bpi = _back_pointers.begin(); bpi != _back_pointers.end(); ++bpi) {
EggBackPointer *back = (*bpi);
if (back != (EggBackPointer *)NULL) {
EggJointPointer *joint;
DCAST_INTO_V(joint, back);
joint->expose(dcs_type);
}
}
}
////////////////////////////////////////////////////////////////////
// Function: EggJointData::zero_channels
// Access: Public
// Description: Calls zero_channels() on all models for this joint,
// but does not recurse downwards.
////////////////////////////////////////////////////////////////////
void EggJointData::
zero_channels(const string &components) {
BackPointers::iterator bpi;
for (bpi = _back_pointers.begin(); bpi != _back_pointers.end(); ++bpi) {
EggBackPointer *back = (*bpi);
if (back != (EggBackPointer *)NULL) {
EggJointPointer *joint;
DCAST_INTO_V(joint, back);
joint->zero_channels(components);
}
}
}
////////////////////////////////////////////////////////////////////
// Function: EggJointData::add_back_pointer
// Access: Public, Virtual
// Description: Adds the indicated model joint or anim table to the
// data.
////////////////////////////////////////////////////////////////////
void EggJointData::
add_back_pointer(int model_index, EggObject *egg_object) {
nassertv(egg_object != (EggObject *)NULL);
if (egg_object->is_of_type(EggGroup::get_class_type())) {
// It must be a <Joint>.
EggJointNodePointer *joint = new EggJointNodePointer(egg_object);
set_model(model_index, joint);
if (!_has_rest_frame) {
_rest_frame = joint->get_frame(0);
_has_rest_frame = true;
} else {
// If this new node doesn't come within an acceptable tolerance
// of our first reading of this joint's rest frame, set a
// warning flag.
if (!_rest_frame.almost_equal(joint->get_frame(0), 0.0001)) {
_rest_frames_differ = true;
}
}
} else if (egg_object->is_of_type(EggTable::get_class_type())) {
// It's a <Table> with an "xform" child beneath it.
EggMatrixTablePointer *xform = new EggMatrixTablePointer(egg_object);
set_model(model_index, xform);
} else {
nout << "Invalid object added to joint for back pointer.\n";
}
}
////////////////////////////////////////////////////////////////////
// Function: EggJointData::write
// Access: Public, Virtual
// Description:
////////////////////////////////////////////////////////////////////
void EggJointData::
write(ostream &out, int indent_level) const {
indent(out, indent_level)
<< "Joint " << get_name()
<< " (models:";
int num_models = get_num_models();
for (int model_index = 0; model_index < num_models; model_index++) {
if (has_model(model_index)) {
out << " " << model_index;
}
}
out << ") {\n";
Children::const_iterator ci;
for (ci = _children.begin(); ci != _children.end(); ++ci) {
(*ci)->write(out, indent_level + 2);
}
indent(out, indent_level) << "}\n";
}
////////////////////////////////////////////////////////////////////
// Function: EggJointData::do_begin_reparent
// Access: Protected
// Description: Clears out the _children vector in preparation for
// refilling it from the _new_parent information.
////////////////////////////////////////////////////////////////////
void EggJointData::
do_begin_reparent() {
_got_new_parent_depth = false;
_children.clear();
int num_models = get_num_models();
for (int model_index = 0; model_index < num_models; model_index++) {
if (has_model(model_index)) {
EggJointPointer *joint;
DCAST_INTO_V(joint, get_model(model_index));
joint->begin_rebuild();
}
}
}
////////////////////////////////////////////////////////////////////
// Function: EggJointData::calc_new_parent_depth
// Access: Protected
// Description: Calculates the number of joints above this joint in its
// intended position, as specified by a recent call to
// reparent_to(), and also checks for a cycle in the new
// parent chain. Returns true if a cycle is detected,
// and false otherwise. If a cycle is not detected,
// _new_parent_depth can be consulted for the depth in
// the new hierarchy.
//
// This is used by EggCharacterData::do_reparent() to
// determine the order in which to apply the reparent
// operations. It should be called after
// do_begin_reparent().
////////////////////////////////////////////////////////////////////
bool EggJointData::
calc_new_parent_depth(pset<EggJointData *> &chain) {
if (_got_new_parent_depth) {
return false;
}
if (_new_parent == (EggJointData *)NULL) {
// Here's the top of the new hierarchy.
_got_new_parent_depth = true;
_new_parent_depth = 0;
return false;
}
if (!chain.insert(this).second) {
// We've already visited this joint; that means there's a cycle.
return true;
}
bool cycle = _new_parent->calc_new_parent_depth(chain);
_new_parent_depth = _new_parent->_new_parent_depth + 1;
return cycle;
}
////////////////////////////////////////////////////////////////////
// Function: EggJointData::do_begin_compute_reparent
// Access: Protected
// Description: Eliminates any cached values before beginning a walk
// through all the joints for do_compute_reparent(), for
// a given model/frame.
////////////////////////////////////////////////////////////////////
void EggJointData::
do_begin_compute_reparent() {
_got_new_net_frame = false;
_got_new_net_frame_inv = false;
_computed_reparent = false;
}
////////////////////////////////////////////////////////////////////
// Function: EggJointData::do_compute_reparent
// Access: Protected
// Description: Prepares the reparent operation by computing a new
// transform for each frame of each model, designed to
// keep the net transform the same when the joint is
// moved to its new parent. Returns true on success,
// false on failure.
////////////////////////////////////////////////////////////////////
bool EggJointData::
do_compute_reparent(int model_index, int n) {
if (_computed_reparent) {
// We've already done this joint. This is possible because we
// have to recursively compute joints upwards, so we might visit
// the same joint more than once.
return _computed_ok;
}
_computed_reparent = true;
if (_parent == _new_parent) {
// Trivial (and most common) case: we are not moving the joint.
// No recomputation necessary.
_computed_ok = true;
return true;
}
EggBackPointer *back = get_model(model_index);
if (back == (EggBackPointer *)NULL) {
// This joint doesn't have any data to modify.
_computed_ok = true;
return true;
}
EggJointPointer *joint;
DCAST_INTO_R(joint, back, false);
LMatrix4d transform;
if (_parent == (EggJointData *)NULL) {
// We are moving from outside the joint hierarchy to within it.
transform = _new_parent->get_new_net_frame_inv(model_index, n);
} else if (_new_parent == (EggJointData *)NULL) {
// We are moving from within the hierarchy to outside it.
transform = _parent->get_net_frame(model_index, n);
} else {
// We are changing parents within the hierarchy.
transform =
_parent->get_net_frame(model_index, n) *
_new_parent->get_new_net_frame_inv(model_index, n);
}
nassertr(n == joint->get_num_rebuild_frames(), false);
_computed_ok = joint->add_rebuild_frame(joint->get_frame(n) * transform);
return _computed_ok;
}
////////////////////////////////////////////////////////////////////
// Function: EggJointData::do_finish_reparent
// Access: Protected
// Description: Performs the actual reparenting operation
// by removing all of the old children and replacing
// them with the set of new children. Returns true on
// success, false on failure.
////////////////////////////////////////////////////////////////////
bool EggJointData::
do_finish_reparent() {
bool all_ok = true;
int num_models = get_num_models();
for (int model_index = 0; model_index < num_models; model_index++) {
EggJointPointer *parent_joint = NULL;
if (_new_parent != NULL && _new_parent->has_model(model_index)) {
DCAST_INTO_R(parent_joint, _new_parent->get_model(model_index), false);
}
if (has_model(model_index)) {
EggJointPointer *joint;
DCAST_INTO_R(joint, get_model(model_index), false);
joint->do_finish_reparent(parent_joint);
joint->clear_net_frames();
if (!joint->do_rebuild()) {
all_ok = false;
}
}
}
_parent = _new_parent;
if (_parent != (EggJointData *)NULL) {
_parent->_children.push_back(this);
}
return all_ok;
}
////////////////////////////////////////////////////////////////////
// Function: EggJointData::make_new_joint
// Access: Private
// Description: Creates a new joint as a child of this joint and
// returns it. This is intended to be called only from
// EggCharacterData::make_new_joint().
////////////////////////////////////////////////////////////////////
EggJointData *EggJointData::
make_new_joint(const string &name) {
EggJointData *child = new EggJointData(_collection, _char_data);
child->set_name(name);
child->_parent = this;
child->_new_parent = this;
_children.push_back(child);
// Also create new back pointers in each of the models.
int num_models = get_num_models();
for (int i = 0; i < num_models; i++) {
if (has_model(i)) {
EggJointPointer *joint;
DCAST_INTO_R(joint, get_model(i), NULL);
EggJointPointer *new_joint = joint->make_new_joint(name);
child->set_model(i, new_joint);
}
}
return child;
}
////////////////////////////////////////////////////////////////////
// Function: EggJointData::find_joint_exact
// Access: Private
// Description: The recursive implementation of find_joint, this
// flavor searches recursively for an exact match of the
// preferred joint name.
////////////////////////////////////////////////////////////////////
EggJointData *EggJointData::
find_joint_exact(const string &name) {
Children::const_iterator ci;
for (ci = _children.begin(); ci != _children.end(); ++ci) {
EggJointData *child = (*ci);
if (child->get_name() == name) {
return child;
}
EggJointData *result = child->find_joint_exact(name);
if (result != (EggJointData *)NULL) {
return result;
}
}
return (EggJointData *)NULL;
}
////////////////////////////////////////////////////////////////////
// Function: EggJointData::find_joint_matches
// Access: Private
// Description: The recursive implementation of find_joint, this
// flavor searches recursively for any acceptable match.
////////////////////////////////////////////////////////////////////
EggJointData *EggJointData::
find_joint_matches(const string &name) {
Children::const_iterator ci;
for (ci = _children.begin(); ci != _children.end(); ++ci) {
EggJointData *child = (*ci);
if (child->matches_name(name)) {
return child;
}
EggJointData *result = child->find_joint_matches(name);
if (result != (EggJointData *)NULL) {
return result;
}
}
return (EggJointData *)NULL;
}
////////////////////////////////////////////////////////////////////
// Function: EggJointData::is_new_ancestor
// Access: Protected
// Description: Returns true if this joint is an ancestor of the
// indicated joint, in the "new" hierarchy (that is, the
// one defined by _new_parent, as set by reparent_to()
// before do_finish_reparent() is called).
////////////////////////////////////////////////////////////////////
bool EggJointData::
is_new_ancestor(EggJointData *child) const {
if (child == this) {
return true;
}
if (child->_new_parent == (EggJointData *)NULL) {
return false;
}
return is_new_ancestor(child->_new_parent);
}
////////////////////////////////////////////////////////////////////
// Function: EggJointData::get_new_net_frame
// Access: Private
// Description: Similar to get_net_frame(), but computed for the
// prospective new parentage of the node, before
// do_finish_reparent() is called. This is generally
// useful only when called within do_compute_reparent().
////////////////////////////////////////////////////////////////////
const LMatrix4d &EggJointData::
get_new_net_frame(int model_index, int n) {
if (!_got_new_net_frame) {
_new_net_frame = get_new_frame(model_index, n);
if (_new_parent != (EggJointData *)NULL) {
_new_net_frame = _new_net_frame * _new_parent->get_new_net_frame(model_index, n);
}
_got_new_net_frame = true;
}
return _new_net_frame;
}
////////////////////////////////////////////////////////////////////
// Function: EggJointData::get_new_net_frame_inv
// Access: Private
// Description: Returns the inverse of get_new_net_frame().
////////////////////////////////////////////////////////////////////
const LMatrix4d &EggJointData::
get_new_net_frame_inv(int model_index, int n) {
if (!_got_new_net_frame_inv) {
_new_net_frame_inv.invert_from(get_new_frame(model_index, n));
if (_new_parent != (EggJointData *)NULL) {
_new_net_frame_inv = _new_parent->get_new_net_frame_inv(model_index, n) * _new_net_frame_inv;
}
_got_new_net_frame_inv = true;
}
return _new_net_frame_inv;
}
////////////////////////////////////////////////////////////////////
// Function: EggJointData::get_new_frame
// Access: Private
// Description: Returns the local transform matrix corresponding to
// this joint position in the nth frame in the indicated
// model, as it will be when do_finish_reparent() is
// called.
////////////////////////////////////////////////////////////////////
LMatrix4d EggJointData::
get_new_frame(int model_index, int n) {
do_compute_reparent(model_index, n);
EggBackPointer *back = get_model(model_index);
if (back == (EggBackPointer *)NULL) {
return LMatrix4d::ident_mat();
}
EggJointPointer *joint;
DCAST_INTO_R(joint, back, LMatrix4d::ident_mat());
if (joint->get_num_rebuild_frames() > 0) {
return joint->get_rebuild_frame(n);
} else {
return joint->get_frame(n);
}
}