Remove shear from BT_normalized_linear, add BT_componentwise and BT_componentwise_quat

This commit is contained in:
David Rose 2006-01-19 01:03:12 +00:00
parent a998cf46cd
commit 34c1491fbb
9 changed files with 408 additions and 105 deletions

View File

@ -82,16 +82,17 @@ get_value(int, TYPENAME AnimChannel<SwitchType>::ValueType &) {
#endif #endif
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: AnimChannel::get_value_no_scale // Function: AnimChannel::get_value_no_scale_share
// Access: Public, Virtual // Access: Public, Virtual
// Description: Returns the value associated with the current frame, // Description: Returns the value associated with the current frame,
// with no scale components. This only makes sense for // with no scale or share components. This only makes
// a matrix-type channel, although for fiddly technical // sense for a matrix-type channel, although for fiddly
// reasons the function exists for all channels. // technical reasons the function exists for all
// channels.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
template<class SwitchType> template<class SwitchType>
void AnimChannel<SwitchType>:: void AnimChannel<SwitchType>::
get_value_no_scale(int frame, ValueType &value) { get_value_no_scale_shear(int frame, ValueType &value) {
get_value(frame, value); get_value(frame, value);
} }
@ -104,10 +105,60 @@ get_value_no_scale(int frame, ValueType &value) {
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
template<class SwitchType> template<class SwitchType>
void AnimChannel<SwitchType>:: void AnimChannel<SwitchType>::
get_scale(int, float scale[3]) { get_scale(int, LVecBase3f &scale) {
scale[0] = 1.0f; nassertv(false);
scale[1] = 1.0f; }
scale[2] = 1.0f;
////////////////////////////////////////////////////////////////////
// Function: AnimChannel::get_hpr
// Access: Public, Virtual
// Description: Returns the h, p, and r components associated
// with the current frame. As above, this only makes
// sense for a matrix-type channel.
////////////////////////////////////////////////////////////////////
template<class SwitchType>
void AnimChannel<SwitchType>::
get_hpr(int, LVecBase3f &hpr) {
nassertv(false);
}
////////////////////////////////////////////////////////////////////
// Function: AnimChannel::get_quat
// Access: Public, Virtual
// Description: Returns the rotation component associated with the
// current frame, expressed as a quaternion. As above,
// this only makes sense for a matrix-type channel.
////////////////////////////////////////////////////////////////////
template<class SwitchType>
void AnimChannel<SwitchType>::
get_quat(int, LQuaternionf &quat) {
nassertv(false);
}
////////////////////////////////////////////////////////////////////
// Function: AnimChannel::get_pos
// Access: Public, Virtual
// Description: Returns the x, y, and z translation components
// associated with the current frame. As above, this
// only makes sense for a matrix-type channel.
////////////////////////////////////////////////////////////////////
template<class SwitchType>
void AnimChannel<SwitchType>::
get_pos(int, LVecBase3f &pos) {
nassertv(false);
}
////////////////////////////////////////////////////////////////////
// Function: AnimChannel::get_shear
// Access: Public, Virtual
// Description: Returns the a, b, and c shear components associated
// with the current frame. As above, this only makes
// sense for a matrix-type channel.
////////////////////////////////////////////////////////////////////
template<class SwitchType>
void AnimChannel<SwitchType>::
get_shear(int, LVecBase3f &shear) {
nassertv(false);
} }

View File

@ -47,12 +47,13 @@ public:
PUBLISHED: PUBLISHED:
virtual void get_value(int frame, ValueType &value)=0; virtual void get_value(int frame, ValueType &value)=0;
// These two functions only have meaning for matrix types. // These transform-component methods only have meaning for matrix types.
virtual void get_value_no_scale(int frame, ValueType &value); virtual void get_value_no_scale_shear(int frame, ValueType &value);
virtual void get_scale(int frame, float scale[3]); virtual void get_scale(int frame, LVecBase3f &scale);
// The second parameter above should really by LVector3f instead of virtual void get_hpr(int frame, LVecBase3f &hpr);
// float[3], but there seems to be a compiler bug in EGCS that virtual void get_quat(int frame, LQuaternionf &quat);
// doesn't like that. So we have this kludge for now. virtual void get_pos(int frame, LVecBase3f &pos);
virtual void get_shear(int frame, LVecBase3f &shear);
virtual TypeHandle get_value_type() const; virtual TypeHandle get_value_type() const;

View File

@ -80,14 +80,14 @@ get_value(int, LMatrix4f &mat) {
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: AnimChannelMatrixDynamic::get_value_no_scale // Function: AnimChannelMatrixDynamic::get_value_no_scale_shear
// Access: Public, Virtual // Access: Public, Virtual
// Description: Gets the value of the channel at the indicated frame, // Description: Gets the value of the channel at the indicated frame,
// without any scale information. // without any scale or shear information.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
void AnimChannelMatrixDynamic:: void AnimChannelMatrixDynamic::
get_value_no_scale(int frame, LMatrix4f &mat) { get_value_no_scale_shear(int frame, LMatrix4f &mat) {
if (_value->has_scale()) { if (_value->has_scale() || _value->has_shear()) {
compose_matrix(mat, LVecBase3f(1.0f, 1.0f, 1.0f), compose_matrix(mat, LVecBase3f(1.0f, 1.0f, 1.0f),
_value->get_hpr(), _value->get_pos()); _value->get_hpr(), _value->get_pos());
} else { } else {
@ -101,11 +101,56 @@ get_value_no_scale(int frame, LMatrix4f &mat) {
// Description: Gets the scale value at the indicated frame. // Description: Gets the scale value at the indicated frame.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
void AnimChannelMatrixDynamic:: void AnimChannelMatrixDynamic::
get_scale(int frame, float scale[3]) { get_scale(int frame, LVecBase3f &scale) {
const LVecBase3f &sc = _value->get_scale(); scale = _value->get_scale();
scale[0] = sc[0]; }
scale[1] = sc[1];
scale[2] = sc[2]; ////////////////////////////////////////////////////////////////////
// Function: AnimChannelMatrixDynamic::get_hpr
// Access: Public, Virtual
// Description: Returns the h, p, and r components associated
// with the current frame. As above, this only makes
// sense for a matrix-type channel.
////////////////////////////////////////////////////////////////////
void AnimChannelMatrixDynamic::
get_hpr(int, LVecBase3f &hpr) {
hpr = _value->get_hpr();
}
////////////////////////////////////////////////////////////////////
// Function: AnimChannelMatrixDynamic::get_quat
// Access: Public, Virtual
// Description: Returns the rotation component associated with the
// current frame, expressed as a quaternion. As above,
// this only makes sense for a matrix-type channel.
////////////////////////////////////////////////////////////////////
void AnimChannelMatrixDynamic::
get_quat(int, LQuaternionf &quat) {
quat = _value->get_quat();
}
////////////////////////////////////////////////////////////////////
// Function: AnimChannelMatrixDynamic::get_pos
// Access: Public, Virtual
// Description: Returns the x, y, and z translation components
// associated with the current frame. As above, this
// only makes sense for a matrix-type channel.
////////////////////////////////////////////////////////////////////
void AnimChannelMatrixDynamic::
get_pos(int, LVecBase3f &pos) {
pos = _value->get_pos();
}
////////////////////////////////////////////////////////////////////
// Function: AnimChannelMatrixDynamic::get_shear
// Access: Public, Virtual
// Description: Returns the a, b, and c shear components associated
// with the current frame. As above, this only makes
// sense for a matrix-type channel.
////////////////////////////////////////////////////////////////////
void AnimChannelMatrixDynamic::
get_shear(int, LVecBase3f &shear) {
shear = _value->get_shear();
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////

View File

@ -48,8 +48,13 @@ protected:
public: public:
virtual bool has_changed(int last_frame, int this_frame); virtual bool has_changed(int last_frame, int this_frame);
virtual void get_value(int frame, LMatrix4f &mat); virtual void get_value(int frame, LMatrix4f &mat);
virtual void get_value_no_scale(int frame, LMatrix4f &value);
virtual void get_scale(int frame, float scale[3]); virtual void get_value_no_scale_shear(int frame, LMatrix4f &value);
virtual void get_scale(int frame, LVecBase3f &scale);
virtual void get_hpr(int frame, LVecBase3f &hpr);
virtual void get_quat(int frame, LQuaternionf &quat);
virtual void get_pos(int frame, LVecBase3f &pos);
virtual void get_shear(int frame, LVecBase3f &shear);
PUBLISHED: PUBLISHED:
void set_value(const LMatrix4f &value); void set_value(const LMatrix4f &value);

View File

@ -106,19 +106,22 @@ get_value(int frame, LMatrix4f &mat) {
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: AnimChannelMatrixXfmTable::get_value_no_scale // Function: AnimChannelMatrixXfmTable::get_value_no_scale_shear
// Access: Public, Virtual // Access: Public, Virtual
// Description: Gets the value of the channel at the indicated frame, // Description: Gets the value of the channel at the indicated frame,
// without any scale information. // without any scale or shear information.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
void AnimChannelMatrixXfmTable:: void AnimChannelMatrixXfmTable::
get_value_no_scale(int frame, LMatrix4f &mat) { get_value_no_scale_shear(int frame, LMatrix4f &mat) {
float components[num_matrix_components]; float components[num_matrix_components];
components[0] = 1.0f; components[0] = 1.0f;
components[1] = 1.0f; components[1] = 1.0f;
components[2] = 1.0f; components[2] = 1.0f;
components[3] = 0.0f;
components[4] = 0.0f;
components[5] = 0.0f;
for (int i = 3; i < num_matrix_components; i++) { for (int i = 6; i < num_matrix_components; i++) {
if (_tables[i].empty()) { if (_tables[i].empty()) {
components[i] = get_default_value(i); components[i] = get_default_value(i);
} else { } else {
@ -135,23 +138,99 @@ get_value_no_scale(int frame, LMatrix4f &mat) {
// Description: Gets the scale value at the indicated frame. // Description: Gets the scale value at the indicated frame.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
void AnimChannelMatrixXfmTable:: void AnimChannelMatrixXfmTable::
get_scale(int frame, float scale[3]) { get_scale(int frame, LVecBase3f &scale) {
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
if (_tables[i].empty()) { if (_tables[i].empty()) {
scale[i] = get_default_value(i); scale[i] = 1.0f;
} else { } else {
scale[i] = _tables[i][frame % _tables[i].size()]; scale[i] = _tables[i][frame % _tables[i].size()];
} }
} }
} }
////////////////////////////////////////////////////////////////////
// Function: AnimChannelMatrixXfmTable::get_hpr
// Access: Public, Virtual
// Description: Returns the h, p, and r components associated
// with the current frame. As above, this only makes
// sense for a matrix-type channel.
////////////////////////////////////////////////////////////////////
void AnimChannelMatrixXfmTable::
get_hpr(int frame, LVecBase3f &hpr) {
for (int i = 0; i < 3; i++) {
if (_tables[i + 6].empty()) {
hpr[i] = 0.0f;
} else {
hpr[i] = _tables[i + 6][frame % _tables[i + 6].size()];
}
}
}
////////////////////////////////////////////////////////////////////
// Function: AnimChannelMatrixXfmTable::get_quat
// Access: Public, Virtual
// Description: Returns the rotation component associated with the
// current frame, expressed as a quaternion. As above,
// this only makes sense for a matrix-type channel.
////////////////////////////////////////////////////////////////////
void AnimChannelMatrixXfmTable::
get_quat(int frame, LQuaternionf &quat) {
LVecBase3f hpr;
for (int i = 0; i < 3; i++) {
if (_tables[i + 6].empty()) {
hpr[i] = 0.0f;
} else {
hpr[i] = _tables[i + 6][frame % _tables[i + 6].size()];
}
}
quat.set_hpr(hpr);
}
////////////////////////////////////////////////////////////////////
// Function: AnimChannelMatrixXfmTable::get_pos
// Access: Public, Virtual
// Description: Returns the x, y, and z translation components
// associated with the current frame. As above, this
// only makes sense for a matrix-type channel.
////////////////////////////////////////////////////////////////////
void AnimChannelMatrixXfmTable::
get_pos(int frame, LVecBase3f &pos) {
for (int i = 0; i < 3; i++) {
if (_tables[i + 9].empty()) {
pos[i] = 0.0f;
} else {
pos[i] = _tables[i + 9][frame % _tables[i + 9].size()];
}
}
}
////////////////////////////////////////////////////////////////////
// Function: AnimChannelMatrixXfmTable::get_shear
// Access: Public, Virtual
// Description: Returns the a, b, and c shear components associated
// with the current frame. As above, this only makes
// sense for a matrix-type channel.
////////////////////////////////////////////////////////////////////
void AnimChannelMatrixXfmTable::
get_shear(int frame, LVecBase3f &shear) {
for (int i = 0; i < 3; i++) {
if (_tables[i + 3].empty()) {
shear[i] = 0.0f;
} else {
shear[i] = _tables[i + 3][frame % _tables[i + 3].size()];
}
}
}
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: AnimChannelMatrixXfmTable::set_table // Function: AnimChannelMatrixXfmTable::set_table
// Access: Public // Access: Public
// Description: Assigns the indicated table. table_id is one of 'i', // Description: Assigns the indicated table. table_id is one of 'i',
// 'j', 'k', for scale, 'h', 'p', 'r', for rotation, and // 'j', 'k', for scale, 'a', 'b', 'c' for shear, 'h',
// 'x', 'y', 'z', for translation. The new table must // 'p', 'r', for rotation, and 'x', 'y', 'z', for
// have either zero, one, or get_num_frames() frames. // translation. The new table must have either zero,
// one, or get_num_frames() frames.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
void AnimChannelMatrixXfmTable:: void AnimChannelMatrixXfmTable::
set_table(char table_id, const CPTA_float &table) { set_table(char table_id, const CPTA_float &table) {

View File

@ -47,8 +47,13 @@ public:
virtual bool has_changed(int last_frame, int this_frame); virtual bool has_changed(int last_frame, int this_frame);
virtual void get_value(int frame, LMatrix4f &mat); virtual void get_value(int frame, LMatrix4f &mat);
virtual void get_value_no_scale(int frame, LMatrix4f &value);
virtual void get_scale(int frame, float scale[3]); virtual void get_value_no_scale_shear(int frame, LMatrix4f &value);
virtual void get_scale(int frame, LVecBase3f &scale);
virtual void get_hpr(int frame, LVecBase3f &hpr);
virtual void get_quat(int frame, LQuaternionf &quat);
virtual void get_pos(int frame, LVecBase3f &pos);
virtual void get_shear(int frame, LVecBase3f &shear);
static INLINE bool is_valid_id(char table_id); static INLINE bool is_valid_id(char table_id);

View File

@ -70,74 +70,181 @@ get_blend_value(const PartBundle *root) {
} else { } else {
// A blend of two or more values. // A blend of two or more values.
if (root->get_blend_type() == PartBundle::BT_linear) { switch (root->get_blend_type()) {
// An ordinary, linear blend. case PartBundle::BT_single:
_value = 0.0f; // This one shouldn't be possible.
float net = 0.0f; nassertv(false);
PartBundle::ChannelBlend::const_iterator cbi; case PartBundle::BT_linear:
for (cbi = blend.begin(); cbi != blend.end(); ++cbi) { {
AnimControl *control = (*cbi).first; // An ordinary, linear blend.
float effect = (*cbi).second; _value = 0.0f;
nassertv(effect != 0.0f); float net = 0.0f;
int channel_index = control->get_channel_index(); PartBundle::ChannelBlend::const_iterator cbi;
nassertv(channel_index >= 0 && channel_index < (int)_channels.size()); for (cbi = blend.begin(); cbi != blend.end(); ++cbi) {
ChannelType *channel = DCAST(ChannelType, _channels[channel_index]); AnimControl *control = (*cbi).first;
nassertv(channel != NULL); float effect = (*cbi).second;
nassertv(effect != 0.0f);
ValueType v;
channel->get_value(control->get_frame(), v); int channel_index = control->get_channel_index();
nassertv(channel_index >= 0 && channel_index < (int)_channels.size());
_value += v * effect; ChannelType *channel = DCAST(ChannelType, _channels[channel_index]);
net += effect; nassertv(channel != NULL);
ValueType v;
channel->get_value(control->get_frame(), v);
_value += v * effect;
net += effect;
}
nassertv(net != 0.0f);
_value /= net;
} }
break;
nassertv(net != 0.0f); case PartBundle::BT_normalized_linear:
_value /= net; {
// A normalized linear blend. This means we do a linear blend
} else if (root->get_blend_type() == PartBundle::BT_normalized_linear) { // without scales or shears, normalize the scale and shear
// A normalized linear blend. This means we do a linear blend // components of the resulting matrix to eliminate
// without scales, normalize the scale components of the // artificially-introduced scales, and then reapply the
// resulting matrix to eliminate artificially-introduced scales, // scales and shears.
// and then reapply the scales.
_value = 0.0f;
// Perhaps we should treat shear the same as a scale here? LVecBase3f scale(0.0f, 0.0f, 0.0f);
LVecBase3f shear(0.0f, 0.0f, 0.0f);
_value = 0.0f; float net = 0.0f;
LVector3f scale(0.0f, 0.0f, 0.0f);
float net = 0.0f; PartBundle::ChannelBlend::const_iterator cbi;
for (cbi = blend.begin(); cbi != blend.end(); ++cbi) {
PartBundle::ChannelBlend::const_iterator cbi; AnimControl *control = (*cbi).first;
for (cbi = blend.begin(); cbi != blend.end(); ++cbi) { float effect = (*cbi).second;
AnimControl *control = (*cbi).first; nassertv(effect != 0.0f);
float effect = (*cbi).second;
nassertv(effect != 0.0f); int channel_index = control->get_channel_index();
nassertv(channel_index >= 0 && channel_index < (int)_channels.size());
int channel_index = control->get_channel_index(); ChannelType *channel = DCAST(ChannelType, _channels[channel_index]);
nassertv(channel_index >= 0 && channel_index < (int)_channels.size()); nassertv(channel != NULL);
ChannelType *channel = DCAST(ChannelType, _channels[channel_index]);
nassertv(channel != NULL); ValueType v;
channel->get_value_no_scale_shear(control->get_frame(), v);
ValueType v; LVecBase3f iscale, ishear;
channel->get_value_no_scale(control->get_frame(), v); channel->get_scale(control->get_frame(), iscale);
LVector3f s; channel->get_shear(control->get_frame(), ishear);
channel->get_scale(control->get_frame(), &s[0]);
_value += v * effect;
_value += v * effect; scale += iscale * effect;
scale += s * effect; shear += ishear * effect;
net += effect; net += effect;
}
nassertv(net != 0.0f);
_value /= net;
scale /= net;
shear /= net;
// Now rebuild the matrix with the correct scale values.
LVector3f false_scale, false_shear, hpr, translate;
decompose_matrix(_value, false_scale, false_shear, hpr, translate);
compose_matrix(_value, scale, shear, hpr, translate);
} }
break;
nassertv(net != 0.0f); case PartBundle::BT_componentwise:
_value /= net; {
scale /= net; // Componentwise linear, including componentwise H, P, and R.
LVecBase3f scale(0.0f, 0.0f, 0.0f);
LVecBase3f hpr(0.0f, 0.0f, 0.0f);
LVecBase3f pos(0.0f, 0.0f, 0.0f);
LVecBase3f shear(0.0f, 0.0f, 0.0f);
float net = 0.0f;
PartBundle::ChannelBlend::const_iterator cbi;
for (cbi = blend.begin(); cbi != blend.end(); ++cbi) {
AnimControl *control = (*cbi).first;
float effect = (*cbi).second;
nassertv(effect != 0.0f);
int channel_index = control->get_channel_index();
nassertv(channel_index >= 0 && channel_index < (int)_channels.size());
ChannelType *channel = DCAST(ChannelType, _channels[channel_index]);
nassertv(channel != NULL);
LVecBase3f iscale, ihpr, ipos, ishear;
channel->get_scale(control->get_frame(), iscale);
channel->get_hpr(control->get_frame(), ihpr);
channel->get_pos(control->get_frame(), ipos);
channel->get_shear(control->get_frame(), ishear);
scale += iscale * effect;
hpr += ihpr * effect;
pos += ipos * effect;
shear += ishear * effect;
net += effect;
}
nassertv(net != 0.0f);
scale /= net;
hpr /= net;
pos /= net;
shear /= net;
compose_matrix(_value, scale, shear, hpr, pos);
}
break;
// Now rebuild the matrix with the correct scale values. case PartBundle::BT_componentwise_quat:
{
// Componentwise linear, except for rotation, which is a
// quaternion.
LVecBase3f scale(0.0f, 0.0f, 0.0f);
LQuaternionf quat(0.0f, 0.0f, 0.0f, 0.0f);
LVecBase3f pos(0.0f, 0.0f, 0.0f);
LVecBase3f shear(0.0f, 0.0f, 0.0f);
float net = 0.0f;
PartBundle::ChannelBlend::const_iterator cbi;
for (cbi = blend.begin(); cbi != blend.end(); ++cbi) {
AnimControl *control = (*cbi).first;
float effect = (*cbi).second;
nassertv(effect != 0.0f);
int channel_index = control->get_channel_index();
nassertv(channel_index >= 0 && channel_index < (int)_channels.size());
ChannelType *channel = DCAST(ChannelType, _channels[channel_index]);
nassertv(channel != NULL);
LVecBase3f iscale, ipos, ishear;
LQuaternionf iquat;
channel->get_scale(control->get_frame(), iscale);
channel->get_quat(control->get_frame(), iquat);
channel->get_pos(control->get_frame(), ipos);
channel->get_shear(control->get_frame(), ishear);
scale += iscale * effect;
quat += iquat * effect;
pos += ipos * effect;
shear += ishear * effect;
net += effect;
}
nassertv(net != 0.0f);
scale /= net;
quat /= net;
pos /= net;
shear /= net;
LVector3f false_scale, shear, hpr, translate; // There should be no need to normalize the quaternion,
decompose_matrix(_value, false_scale, shear, hpr, translate); // assuming all of the input quaternions were already
compose_matrix(_value, scale, shear, hpr, translate); // normalized.
_value = LMatrix4f::scale_shear_mat(scale, shear) * quat;
_value.set_row(3, pos);
}
break;
} }
} }
} }

View File

@ -24,6 +24,7 @@
#include "movingPart.h" #include "movingPart.h"
#include "animChannel.h" #include "animChannel.h"
#include "animChannelFixed.h" #include "animChannelFixed.h"
#include "cmath.h"
EXPORT_TEMPLATE_CLASS(EXPCL_PANDA, EXPTP_PANDA, MovingPart<ACMatrixSwitchType>); EXPORT_TEMPLATE_CLASS(EXPCL_PANDA, EXPTP_PANDA, MovingPart<ACMatrixSwitchType>);

View File

@ -82,13 +82,22 @@ PUBLISHED:
BT_linear, BT_linear,
// BT_normalized_linear is a compromise on BT_linear. The matrix // BT_normalized_linear is a compromise on BT_linear. The matrix
// is blended linearly without the scale component, and the // is blended linearly without the scale and shear components, and
// blended scale component is applied separately. This keeps all // the blended scale and shear components are applied separately.
// of the character's body parts in the correct size and shape. // This keeps all of the character's body parts in the correct
// However, if the hierarchy is disconnected, body parts can fly // size and shape. However, if the hierarchy is disconnected,
// off. It's essential the skeleton hierarchy be completely // body parts can fly off. It's essential the skeleton hierarchy
// connected to use this blend mode successully. // be completely connected to use this blend mode successully.
BT_normalized_linear, BT_normalized_linear,
// BT_componentwise linearly blends all components separately,
// including H, P, and R, and recomposes the matrix.
BT_componentwise,
// BT_componentwise_quat linearly blends all components
// separately, except for rotation which is blended as a
// quaternion.
BT_componentwise_quat,
}; };
void set_blend_type(BlendType bt); void set_blend_type(BlendType bt);