Panda3D
eggTopstrip.cxx
1 // Filename: eggTopstrip.cxx
2 // Created by: drose (23Feb01)
3 //
4 ////////////////////////////////////////////////////////////////////
5 //
6 // PANDA 3D SOFTWARE
7 // Copyright (c) Carnegie Mellon University. All rights reserved.
8 //
9 // All use of this software is subject to the terms of the revised BSD
10 // license. You should have received a copy of this license along
11 // with this source code in a file named "LICENSE."
12 //
13 ////////////////////////////////////////////////////////////////////
14 
15 #include "eggTopstrip.h"
16 
17 #include "dcast.h"
18 #include "eggJointData.h"
19 #include "eggCharacterCollection.h"
20 #include "eggCharacterData.h"
21 #include "eggCharacterDb.h"
22 #include "eggJointPointer.h"
23 #include "eggTable.h"
24 #include "compose_matrix.h"
25 #include "pystub.h"
26 
27 ////////////////////////////////////////////////////////////////////
28 // Function: EggTopstrip::Constructor
29 // Access: Public
30 // Description:
31 ////////////////////////////////////////////////////////////////////
32 EggTopstrip::
33 EggTopstrip() {
34  add_path_replace_options();
35  add_path_store_options();
36 
37  set_program_brief("unapplies animation from a joint in an .egg file");
38  set_program_description
39  ("egg-topstrip reads a character model and its associated animation "
40  "files, and unapplies the animation from one of the top joints. "
41  "This effectively freezes that particular joint, and makes the rest "
42  "of the character relative to that joint.\n\n"
43 
44  "This is a particularly useful thing to do to generate character "
45  "models that can stack one on top of the other in a sensible way.");
46 
47  add_option
48  ("t", "name", 0,
49  "Specify the name of the 'top' joint, from which to draw the "
50  "animation channels which will be applied to the entire animation.",
51  &EggTopstrip::dispatch_string, NULL, &_top_joint_name);
52 
53  add_option
54  ("i", "", 0,
55  "Invert the matrix before applying. This causes a subtractive "
56  "effect. This is the default unless -r is specified.",
57  &EggTopstrip::dispatch_true, &_got_invert_transform, &_invert_transform);
58 
59  add_option
60  ("n", "", 0,
61  "Do not invert the matrix before applying. This causes an "
62  "additive effect.",
63  &EggTopstrip::dispatch_false, &_got_invert_transform, &_invert_transform);
64 
65  add_option
66  ("s", "[ijkphrxyz]", 0,
67  "Specify the components of the transform that are to be applied. Use "
68  "any combination of the nine token letters: i, j, k represent the "
69  "three scale axes; h, p, r represent rotation; and x, y, z represent "
70  "translation. The default is everything: -s ijkphrxyz.",
71  &EggTopstrip::dispatch_string, NULL, &_transform_channels);
72 
73  add_option
74  ("r", "file.egg", 0,
75  "Read the animation channel from the indicated egg file. If this "
76  "is not specified, each egg file will supply its own animation channel.",
77  &EggTopstrip::dispatch_filename, NULL, &_channel_filename);
78 
79  _invert_transform = true;
80  _transform_channels = "ijkphrxyz";
81 }
82 
83 ////////////////////////////////////////////////////////////////////
84 // Function: EggTopstrip::run
85 // Access: Public
86 // Description:
87 ////////////////////////////////////////////////////////////////////
88 void EggTopstrip::
89 run() {
90  nassertv(_collection != (EggCharacterCollection *)NULL);
91  nassertv(_collection->get_num_eggs() > 0);
92 
94 
95  // Get the number of characters first, in case adding the
96  // _channel_egg changes this.
97  int num_characters = _collection->get_num_characters();
98 
99  // Determine which model and character we'll be pulling the
100  // animation channels from.
101  int from_model = -1;
102 
103  if (!_channel_filename.empty()) {
104  // Read in the extra egg file that we use for extracting the
105  // channels out.
106  PT(EggData) channel_egg = read_egg(_channel_filename);
107  if (channel_egg == (EggData *)NULL) {
108  nout << "Cannot read " << _channel_filename << "\n";
109  exit(1);
110  }
111  int channel_egg_index = _collection->add_egg(channel_egg);
112  if (channel_egg_index < 0) {
113  nout << _channel_filename
114  << " does not contain a character model or animation channel.\n";
115  exit(1);
116  }
117 
118  from_model = _collection->get_first_model_index(channel_egg_index);
119 
120  if (!_got_invert_transform) {
121  // With -r, the default is not to invert the transform.
122  _invert_transform = false;
123  }
124  }
125 
126  // Now process each character.
127  EggCharacterDb db;
128 
129  int ci;
130  for (ci = 0; ci < num_characters; ci++) {
131  EggCharacterData *char_data = _collection->get_character(ci);
132  nout << "Processing " << char_data->get_name() << "\n";
133 
134  EggJointData *root_joint = char_data->get_root_joint();
135 
136  // We'll read the transform to apply from this character, which
137  // will be the same character unless -r was specified.
138  EggCharacterData *from_char = char_data;
139  if (from_model != -1) {
140  from_char = _collection->get_character_by_model_index(from_model);
141  }
142 
143  // Determine which joint we'll use to extract the transform to
144  // apply.
145  EggJointData *top_joint = (EggJointData *)NULL;
146  if (_top_joint_name.empty()) {
147  // The default top joint name is the alphabetically first joint
148  // in the top level.
149  if (root_joint->get_num_children() == 0) {
150  nout << "Character " << from_char->get_name() << " has no joints.\n";
151  exit(1);
152  }
153  top_joint = root_joint->get_child(0);
154  } else {
155  top_joint = from_char->find_joint(_top_joint_name);
156  if (top_joint == (EggJointData *)NULL) {
157  nout << "Character " << from_char->get_name()
158  << " has no joint named " << _top_joint_name << "\n";
159  exit(1);
160  }
161  }
162 
163  // First, transform all the joints.
164  int num_children = root_joint->get_num_children();
165  for (int i = 0; i < num_children; i++) {
166  EggJointData *joint_data = root_joint->get_child(i);
167  strip_anim(char_data, joint_data, from_model, from_char, top_joint, db);
168  }
169 
170  // We also need to transform the vertices for any models involved
171  // here.
172  int num_models = char_data->get_num_models();
173  for (int m = 0; m < num_models; m++) {
174  EggNode *node = char_data->get_model_root(m);
175  if (!node->is_of_type(EggTable::get_class_type())) {
176  strip_anim_vertices(node, char_data->get_model_index(m),
177  from_model, top_joint, db);
178  }
179  }
180  }
181 
182  // Now, trigger the actual rebuilding of all the joint data.
183  for (ci = 0; ci < num_characters; ci++) {
184  EggCharacterData *char_data = _collection->get_character(ci);
185  char_data->get_root_joint()->do_rebuild_all(db);
186  }
187 
188  write_eggs();
189 }
190 
191 ////////////////////////////////////////////////////////////////////
192 // Function: EggTopstrip::check_transform_channels
193 // Access: Public
194 // Description: Checks the _transform_channels string to ensure that
195 // it contains only the expected nine letters, or a
196 // subset.
197 ////////////////////////////////////////////////////////////////////
198 void EggTopstrip::
200  static string expected = "ijkphrxyz";
201  static const int num_channels = 9;
202  bool has_each[num_channels];
203  memset(has_each, 0, num_channels * sizeof(bool));
204 
205  for (size_t p = 0; p < _transform_channels.size(); p++) {
206  int i = expected.find(_transform_channels[p]);
207  if (i == (int)string::npos) {
208  nout << "Invalid letter for -s: " << _transform_channels[p] << "\n";
209  exit(1);
210  }
211  nassertv(i < num_channels);
212  has_each[i] = true;
213  }
214 
215  _transform_channels = "";
216  for (int i = 0; i < num_channels; i++) {
217  if (has_each[i]) {
218  _transform_channels += expected[i];
219  }
220  }
221 
222  if (_transform_channels.empty()) {
223  nout << "No transform specified for -s.\n";
224  exit(1);
225  }
226 }
227 
228 
229 ////////////////////////////////////////////////////////////////////
230 // Function: EggTopstrip::strip_anim
231 // Access: Public
232 // Description: Applies the channels from joint _top_joint
233 // in model from_model to the joint referenced by
234 // joint_data.
235 ////////////////////////////////////////////////////////////////////
236 void EggTopstrip::
237 strip_anim(EggCharacterData *char_data, EggJointData *joint_data,
238  int from_model, EggCharacterData *from_char,
239  EggJointData *top_joint, EggCharacterDb &db) {
240  int num_models = joint_data->get_num_models();
241  for (int i = 0; i < num_models; i++) {
242  int model = (from_model < 0) ? i : from_model;
243  if (joint_data->has_model(i)) {
244  if (!top_joint->has_model(model)) {
245  nout << "Warning: Joint " << top_joint->get_name()
246  << " is not defined in all models.\n";
247  return;
248  }
249 
250  int num_into_frames = char_data->get_num_frames(i);
251  int num_from_frames = from_char->get_num_frames(model);
252 
253  int num_frames = max(num_into_frames, num_from_frames);
254 
255  EggBackPointer *back = joint_data->get_model(i);
256  nassertv(back != (EggBackPointer *)NULL);
257  EggJointPointer *joint;
258  DCAST_INTO_V(joint, back);
259 
260  // Compute and apply the new transforms.
261 
262  int f;
263  for (f = 0; f < num_frames; f++) {
264  LMatrix4d into = joint_data->get_frame(i, f % num_into_frames);
265  LMatrix4d from = top_joint->get_net_frame(model, f % num_from_frames, db);
266 
267  adjust_transform(from);
268 
269  db.set_matrix(joint, EggCharacterDb::TT_rebuild_frame,
270  f, into * from);
271  }
272  }
273  }
274 }
275 
276 ////////////////////////////////////////////////////////////////////
277 // Function: EggTopstrip::strip_anim_vertices
278 // Access: Public
279 // Description: Applies the channels from joint _top_joint
280 // in model from_model to the vertices at egg_node.
281 ////////////////////////////////////////////////////////////////////
282 void EggTopstrip::
283 strip_anim_vertices(EggNode *egg_node, int into_model, int from_model,
284  EggJointData *top_joint, EggCharacterDb &db) {
285  int model = (from_model < 0) ? into_model : from_model;
286  if (!top_joint->has_model(model)) {
287  nout << "Warning: Joint " << top_joint->get_name()
288  << " is not defined in all models.\n";
289  return;
290  }
291 
292  LMatrix4d from = top_joint->get_net_frame(model, 0, db);
293  adjust_transform(from);
294 
295  egg_node->transform_vertices_only(from);
296 }
297 
298 
299 ////////////////////////////////////////////////////////////////////
300 // Function: EggTopstrip::adjust_transform
301 // Access: Public
302 // Description: Adjust the transform extracted from the "top" joint
303 // according to the -s and -i/-n options, prior to
304 // applying it to the skeleton.
305 ////////////////////////////////////////////////////////////////////
306 void EggTopstrip::
308  if (_transform_channels.length() != 9) {
309  // Decompose and recompose the matrix, so we can eliminate the
310  // parts the user doesn't want.
311 
312  LVecBase3d scale, hpr, translate;
313  bool result = decompose_matrix(mat, scale, hpr, translate, _coordinate_system);
314  if (!result) {
315  nout << "Warning: skew transform in animation.\n";
316  } else {
317  LVecBase3d new_scale(1.0, 1.0, 1.0);
318  LVecBase3d new_hpr(0.0, 0.0, 0.0);
319  LVecBase3d new_translate(0.0, 0.0, 0.0);
320 
321  for (size_t i = 0; i < _transform_channels.size(); i++) {
322  switch (_transform_channels[i]) {
323  case 'i':
324  new_scale[0] = scale[0];
325  break;
326  case 'j':
327  new_scale[1] = scale[1];
328  break;
329  case 'k':
330  new_scale[2] = scale[2];
331  break;
332 
333  case 'h':
334  new_hpr[0] = hpr[0];
335  break;
336  case 'p':
337  new_hpr[1] = hpr[1];
338  break;
339  case 'r':
340  new_hpr[2] = hpr[2];
341  break;
342 
343  case 'x':
344  new_translate[0] = translate[0];
345  break;
346  case 'y':
347  new_translate[1] = translate[1];
348  break;
349  case 'z':
350  new_translate[2] = translate[2];
351  break;
352  }
353  }
354 
355  compose_matrix(mat, new_scale, new_hpr, new_translate, _coordinate_system);
356  }
357  }
358  if (_invert_transform) {
359  mat.invert_in_place();
360  }
361 }
362 
363 
364 int main(int argc, char *argv[]) {
365  // A call to pystub() to force libpystub.so to be linked in.
366  pystub();
367 
368  EggTopstrip prog;
369  prog.parse_command_line(argc, argv);
370  prog.run();
371  return 0;
372 }
EggJointData * find_joint(const string &name) const
Returns the first joint found with the indicated name, or NULL if no joint has that name...
This is a 4-by-4 transform matrix.
Definition: lmatrix.h:4716
EggJointData * get_root_joint() const
Returns the root joint of the character hierarchy.
int get_num_frames(int model_index) const
Returns the number of frames of animation of the indicated model.
virtual void parse_command_line(int argc, char **argv)
Dispatches on each of the options on the command line, and passes the remaining parameters to handle_...
int get_num_models() const
Returns the maximum number of back pointers this component may have.
int get_num_models() const
Returns the total number of models associated with this character.
EggBackPointer * get_model(int model_index) const
Returns the back pointer to an egg file for the indicated model if it exists, or NULL if it does not...
This is the primary interface into all the egg data, and the root of the egg file structure...
Definition: eggData.h:41
void strip_anim_vertices(EggNode *egg_node, int into_model, int from_model, EggJointData *top_joint, EggCharacterDb &db)
Applies the channels from joint _top_joint in model from_model to the vertices at egg_node...
This class is used during joint optimization or restructuring to store the table of interim joint com...
bool invert_in_place()
Inverts the current matrix.
Definition: lmatrix.h:6553
void set_matrix(const EggJointPointer *joint, TableType type, int frame, const LMatrix4d &mat)
Stores the matrix for the indicated joint, type, and frame in the database.
void transform_vertices_only(const LMatrix4d &mat)
Applies the indicated transformation only to vertices that appear in global space within vertex pools...
Definition: eggNode.I:333
LMatrix4d get_net_frame(int model_index, int n, EggCharacterDb &db) const
Returns the complete transform from the root corresponding to this joint position in the nth frame in...
This is a base class for EggJointNodePointer and EggMatrixTablePointer.
Represents a set of characters, as read and collected from possibly several model and/or animation eg...
This is the base class for all three-component vectors and points.
Definition: lvecBase3.h:1471
bool has_model(int model_index) const
Returns true if the component has a back pointer to an egg file somewhere for the indicated model...
EggNode * get_model_root(int n) const
Returns the model_root of the nth model associated with this character.
Represents a single character, as read and collected from several models and animation files...
Reads a character model and/or animations and strips out the animation from one of the top joints fro...
Definition: eggTopstrip.h:38
void check_transform_channels()
Checks the _transform_channels string to ensure that it contains only the expected nine letters...
This is one node of a hierarchy of EggJointData nodes, each of which represents a single joint of the...
Definition: eggJointData.h:34
void adjust_transform(LMatrix4d &mat) const
Adjust the transform extracted from the "top" joint according to the -s and -i/-n options...
A base class for things that may be directly added into the egg hierarchy.
Definition: eggNode.h:38
LMatrix4d get_frame(int model_index, int n) const
Returns the local transform matrix corresponding to this joint position in the nth frame in the indic...
bool is_of_type(TypeHandle handle) const
Returns true if the current object is or derives from the indicated type.
Definition: typedObject.I:63
bool do_rebuild_all(EggCharacterDb &db)
Calls do_rebuild() on all models, and recursively on all joints at this node and below.
This stores a pointer from an EggJointData or EggSliderData object back to the referencing data in an...
void strip_anim(EggCharacterData *char_data, EggJointData *joint_data, int from_model, EggCharacterData *from_char, EggJointData *top_joint, EggCharacterDb &db)
Applies the channels from joint _top_joint in model from_model to the joint referenced by joint_data...
int get_model_index(int n) const
Returns the model_index of the nth model associated with this character.