add -optimal to egg-optchar

This commit is contained in:
David Rose 2003-08-31 01:40:38 +00:00
parent 89659d9e78
commit 0c2a4f1550
10 changed files with 448 additions and 27 deletions

View File

@ -32,6 +32,7 @@
#include "dcast.h"
#include "pset.h"
#include "compose_matrix.h"
#include "fftCompressor.h"
#include <algorithm>
@ -119,6 +120,21 @@ EggOptchar() {
"is not affected (the effect is similar to NodePath::wrt_reparent_to).",
&EggOptchar::dispatch_vector_string_pair, NULL, &_reparent_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 "
@ -127,6 +143,7 @@ EggOptchar() {
"values.",
&EggOptchar::dispatch_double, NULL, &_vref_quantum);
_optimal_hierarchy = false;
_vref_quantum = 0.01;
}
@ -174,7 +191,8 @@ run() {
for (ci = 0; ci < num_characters; ci++) {
EggCharacterData *char_data = _collection->get_character(ci);
nout << "Character: " << char_data->get_name() << "\n";
list_joints_p(char_data->get_root_joint());
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";
@ -390,10 +408,14 @@ determine_removed_components() {
for (int ci = 0; ci < num_characters; ci++) {
EggCharacterData *char_data = _collection->get_character(ci);
int num_components = char_data->get_num_components();
cerr << 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 (_keep_all || keep_names.find(name) != keep_names.end()) {
@ -469,9 +491,11 @@ move_vertices() {
joint_data->move_vertices_to(best_joint);
// Now we can't remove the joint.
EggOptcharUserData *best_user_data =
DCAST(EggOptcharUserData, best_joint->get_user_data());
best_user_data->_flags &= ~(EggOptcharUserData::F_empty | EggOptcharUserData::F_remove);
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);
}
}
}
}
@ -488,6 +512,7 @@ move_vertices() {
////////////////////////////////////////////////////////////////////
bool EggOptchar::
process_joints() {
cerr << "process_joints\n";
bool removed_any = false;
int num_characters = _collection->get_num_characters();
for (int ci = 0; ci < num_characters; ci++) {
@ -594,14 +619,16 @@ find_best_parent(EggJointData *joint_data) const {
////////////////////////////////////////////////////////////////////
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.
if (joint_data->get_parent() != (EggJointData *)NULL) {
return find_best_vertex_joint(joint_data->get_parent());
}
return find_best_vertex_joint(joint_data->get_parent());
}
// This is the one!
@ -618,6 +645,7 @@ find_best_vertex_joint(EggJointData *joint_data) const {
bool EggOptchar::
apply_user_reparents() {
bool did_anything = false;
int num_characters = _collection->get_num_characters();
StringPairs::const_iterator spi;
@ -645,6 +673,18 @@ apply_user_reparents() {
}
}
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;
}
@ -849,19 +889,34 @@ list_joints(EggJointData *joint_data, int indent_level, bool verbose) {
// -p joint,parent commands.
////////////////////////////////////////////////////////////////////
void EggOptchar::
list_joints_p(EggJointData *joint_data) {
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.
cout << " -p " << child_data->get_name() << ","
<< joint_data->get_name();
list_joints_p(child_data);
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);
}
}

View File

@ -67,7 +67,7 @@ private:
void analyze_joints(EggJointData *joint_data);
void analyze_sliders(EggCharacterData *char_data);
void list_joints(EggJointData *joint_data, int indent_level, bool verbose);
void list_joints_p(EggJointData *joint_data);
void list_joints_p(EggJointData *joint_data, int &col);
void list_scalars(EggCharacterData *char_data, bool verbose);
void describe_component(EggComponentData *comp_data, int indent_level,
bool verbose);
@ -108,6 +108,7 @@ private:
typedef pvector<FlagGroupsEntry> FlagGroups;
FlagGroups _flag_groups;
bool _optimal_hierarchy;
double _vref_quantum;
};

View File

@ -4,6 +4,7 @@
eggbase progbase
#define OTHER_LIBS \
egg:c panda:m
#define USE_PACKAGES zlib
#define COMBINED_SOURCES $[TARGET]_composite1.cxx

View File

@ -238,6 +238,61 @@ do_reparent() {
return invalid_set.empty();
}
////////////////////////////////////////////////////////////////////
// Function: EggCharacterData::choose_optimal_hierarchy
// Access: Public
// Description: 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() {
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);
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);
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);
if (score >= 0 && (best_score < 0 || score < best_score)) {
best_parent = possible_parent;
best_score = score;
}
}
if (best_parent != (EggJointData *)NULL &&
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);
}
}
}
////////////////////////////////////////////////////////////////////
// Function: EggCharacterData::find_slider
// Access: Public

View File

@ -79,6 +79,7 @@ public:
INLINE int get_num_joints() const;
INLINE EggJointData *get_joint(int n) const;
bool do_reparent();
void choose_optimal_hierarchy();
INLINE int get_num_sliders() const;
INLINE EggSliderData *get_slider(int n) const;

View File

@ -24,6 +24,8 @@
#include "eggGroup.h"
#include "eggTable.h"
#include "indent.h"
#include "fftCompressor.h"
#include "zStream.h"
TypeHandle EggJointData::_type_handle;
@ -73,11 +75,61 @@ get_frame(int model_index, int n) const {
////////////////////////////////////////////////////////////////////
LMatrix4d EggJointData::
get_net_frame(int model_index, int n) const {
LMatrix4d mat = get_frame(model_index, n);
if (_parent != (EggJointData *)NULL) {
mat = mat * _parent->get_net_frame(model_index, n);
EggBackPointer *back = get_model(model_index);
if (back == (EggBackPointer *)NULL) {
return LMatrix4d::ident_mat();
}
return 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);
}
////////////////////////////////////////////////////////////////////
@ -117,16 +169,140 @@ force_initial_rest_frame() {
void EggJointData::
move_vertices_to(EggJointData *new_owner) {
int num_models = get_num_models();
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);
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
// 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
@ -363,12 +539,12 @@ do_compute_reparent(int model_index, int n) {
} else if (_new_parent == (EggJointData *)NULL) {
// We are moving from within the hierarchy to outside it.
transform = _parent->get_new_net_frame(model_index, n);
transform = _parent->get_net_frame(model_index, n);
} else {
// We are changing parents within the hierarchy.
transform =
_parent->get_new_net_frame(model_index, n) *
_parent->get_net_frame(model_index, n) *
_new_parent->get_new_net_frame_inv(model_index, n);
}
@ -401,6 +577,7 @@ do_finish_reparent() {
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;
}
@ -462,6 +639,27 @@ find_joint_matches(const string &name) {
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

View File

@ -45,6 +45,7 @@ public:
LMatrix4d get_frame(int model_index, int n) const;
LMatrix4d get_net_frame(int model_index, int n) const;
LMatrix4d get_net_frame_inv(int model_index, int n) const;
INLINE bool has_rest_frame() const;
INLINE bool rest_frames_differ() const;
@ -53,6 +54,7 @@ public:
INLINE void reparent_to(EggJointData *new_parent);
void move_vertices_to(EggJointData *new_owner);
int score_reparent_to(EggJointData *new_parent);
bool do_rebuild();
void optimize();
@ -72,6 +74,7 @@ private:
EggJointData *find_joint_exact(const string &name);
EggJointData *find_joint_matches(const string &name);
bool is_new_ancestor(EggJointData *child) const;
const LMatrix4d &get_new_net_frame(int model_index, int n);
const LMatrix4d &get_new_net_frame_inv(int model_index, int n);
LMatrix4d get_new_frame(int model_index, int n);

View File

@ -128,10 +128,15 @@ do_finish_reparent(EggJointPointer *new_parent) {
////////////////////////////////////////////////////////////////////
void EggJointNodePointer::
move_vertices_to(EggJointPointer *new_joint) {
EggJointNodePointer *new_node;
DCAST_INTO_V(new_node, new_joint);
if (new_joint == (EggJointPointer *)NULL) {
_joint->unref_all_vertices();
new_node->_joint->steal_vrefs(_joint);
} else {
EggJointNodePointer *new_node;
DCAST_INTO_V(new_node, new_joint);
new_node->_joint->steal_vrefs(_joint);
}
}
////////////////////////////////////////////////////////////////////

View File

@ -39,3 +39,93 @@ get_rebuild_frame(int n) const {
nassertr(n >= 0 && n < (int)_rebuild_frames.size(), LMatrix4d::ident_mat());
return _rebuild_frames[n];
}
////////////////////////////////////////////////////////////////////
// Function: EggJointPointer::clear_net_frames
// Access: Public
// Description: Resets the cache of net frames for this joint.
////////////////////////////////////////////////////////////////////
INLINE void EggJointPointer::
clear_net_frames() {
_net_frames.clear();
}
////////////////////////////////////////////////////////////////////
// Function: EggJointPointer::add_net_frame
// Access: Public, Virtual
// Description: Adds a new frame to the set of net frames. This is
// used to cache the net transform from the root for
// this particular joint.
////////////////////////////////////////////////////////////////////
INLINE void EggJointPointer::
add_net_frame(const LMatrix4d &mat) {
_net_frames.push_back(mat);
}
////////////////////////////////////////////////////////////////////
// Function: EggJointPointer::get_num_net_frames
// Access: Public
// Description: Returns the number of net frames that have been
// added so far.
////////////////////////////////////////////////////////////////////
INLINE int EggJointPointer::
get_num_net_frames() const {
return _net_frames.size();
}
////////////////////////////////////////////////////////////////////
// Function: EggJointPointer::get_net_frame
// Access: Public
// Description: Returns the nth matrix that has been added to the set
// of net frames.
////////////////////////////////////////////////////////////////////
INLINE const LMatrix4d &EggJointPointer::
get_net_frame(int n) const {
nassertr(n >= 0 && n < (int)_net_frames.size(), LMatrix4d::ident_mat());
return _net_frames[n];
}
////////////////////////////////////////////////////////////////////
// Function: EggJointPointer::clear_net_frame_invs
// Access: Public
// Description: Resets the cache of net_inv frames for this joint.
////////////////////////////////////////////////////////////////////
INLINE void EggJointPointer::
clear_net_frame_invs() {
_net_frame_invs.clear();
}
////////////////////////////////////////////////////////////////////
// Function: EggJointPointer::add_net_frame_inv
// Access: Public, Virtual
// Description: Adds a new frame to the set of net_inv frames. This is
// used to cache the inverse net transform from the root
// for this particular joint.
////////////////////////////////////////////////////////////////////
INLINE void EggJointPointer::
add_net_frame_inv(const LMatrix4d &mat) {
_net_frame_invs.push_back(mat);
}
////////////////////////////////////////////////////////////////////
// Function: EggJointPointer::get_num_net_frame_invs
// Access: Public
// Description: Returns the number of net_inv frames that have been
// added so far.
////////////////////////////////////////////////////////////////////
INLINE int EggJointPointer::
get_num_net_frame_invs() const {
return _net_frame_invs.size();
}
////////////////////////////////////////////////////////////////////
// Function: EggJointPointer::get_net_frame_inv
// Access: Public
// Description: Returns the nth matrix that has been added to the set
// of net_inv frames.
////////////////////////////////////////////////////////////////////
INLINE const LMatrix4d &EggJointPointer::
get_net_frame_inv(int n) const {
nassertr(n >= 0 && n < (int)_net_frame_invs.size(), LMatrix4d::ident_mat());
return _net_frame_invs[n];
}

View File

@ -50,6 +50,16 @@ public:
INLINE const LMatrix4d &get_rebuild_frame(int n) const;
virtual bool do_rebuild();
INLINE void clear_net_frames();
INLINE void add_net_frame(const LMatrix4d &mat);
INLINE int get_num_net_frames() const;
INLINE const LMatrix4d &get_net_frame(int n) const;
INLINE void clear_net_frame_invs();
INLINE void add_net_frame_inv(const LMatrix4d &mat);
INLINE int get_num_net_frame_invs() const;
INLINE const LMatrix4d &get_net_frame_inv(int n) const;
virtual void optimize();
virtual void expose(EggGroup::DCSType dcs_type);
virtual void zero_channels(const string &components);
@ -57,6 +67,8 @@ public:
protected:
typedef pvector<LMatrix4d> RebuildFrames;
RebuildFrames _rebuild_frames;
RebuildFrames _net_frames;
RebuildFrames _net_frame_invs;
public:
static TypeHandle get_class_type() {