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