1048 lines
38 KiB
C++
1048 lines
38 KiB
C++
// Filename: partBundle.cxx
|
|
// Created by: drose (22Feb99)
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PANDA 3D SOFTWARE
|
|
// Copyright (c) Carnegie Mellon University. All rights reserved.
|
|
//
|
|
// All use of this software is subject to the terms of the revised BSD
|
|
// license. You should have received a copy of this license along
|
|
// with this source code in a file named "LICENSE."
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
#include "partBundle.h"
|
|
#include "animBundle.h"
|
|
#include "animBundleNode.h"
|
|
#include "animControl.h"
|
|
#include "loader.h"
|
|
#include "animPreloadTable.h"
|
|
#include "config_chan.h"
|
|
#include "bitArray.h"
|
|
#include "string_utils.h"
|
|
#include "indent.h"
|
|
#include "datagram.h"
|
|
#include "datagramIterator.h"
|
|
#include "bamReader.h"
|
|
#include "bamWriter.h"
|
|
#include "configVariableEnum.h"
|
|
#include "loaderOptions.h"
|
|
#include "bindAnimRequest.h"
|
|
|
|
#include <algorithm>
|
|
|
|
TypeHandle PartBundle::_type_handle;
|
|
|
|
|
|
static ConfigVariableEnum<PartBundle::BlendType> anim_blend_type
|
|
("anim-blend-type", PartBundle::BT_normalized_linear,
|
|
PRC_DESC("The default blend type to use for blending animations between "
|
|
"frames, or between multiple animations. See interpolate-frames, "
|
|
"and also PartBundle::set_anim_blend_flag() and "
|
|
"PartBundle::set_frame_blend_flag()."));
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::Copy Constructor
|
|
// Access: Protected
|
|
// Description: Normally, you'd use make_copy() or copy_subgraph() to
|
|
// make a copy of this.
|
|
////////////////////////////////////////////////////////////////////
|
|
PartBundle::
|
|
PartBundle(const PartBundle ©) :
|
|
PartGroup(copy)
|
|
{
|
|
_anim_preload = copy._anim_preload;
|
|
_update_delay = 0.0;
|
|
|
|
CDWriter cdata(_cycler, true);
|
|
CDReader cdata_from(copy._cycler);
|
|
cdata->_blend_type = cdata_from->_blend_type;
|
|
cdata->_anim_blend_flag = cdata_from->_anim_blend_flag;
|
|
cdata->_frame_blend_flag = cdata_from->_frame_blend_flag;
|
|
cdata->_root_xform = cdata_from->_root_xform;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::Constructor
|
|
// Access: Public
|
|
// Description: Normally, a PartBundle constructor should not be
|
|
// called directly--it will get created when a
|
|
// PartBundleNode is created.
|
|
////////////////////////////////////////////////////////////////////
|
|
PartBundle::
|
|
PartBundle(const string &name) :
|
|
PartGroup(name)
|
|
{
|
|
_update_delay = 0.0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::make_copy
|
|
// Access: Public, Virtual
|
|
// Description: Allocates and returns a new copy of the node.
|
|
// Children are not copied, but see copy_subgraph().
|
|
////////////////////////////////////////////////////////////////////
|
|
PartGroup *PartBundle::
|
|
make_copy() const {
|
|
return new PartBundle(*this);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::merge_anim_preloads
|
|
// Access: Published
|
|
// Description: Copies the contents of the other PartBundle's preload
|
|
// table into this one.
|
|
////////////////////////////////////////////////////////////////////
|
|
void PartBundle::
|
|
merge_anim_preloads(const PartBundle *other) {
|
|
if (other->_anim_preload == (AnimPreloadTable *)NULL ||
|
|
_anim_preload == other->_anim_preload) {
|
|
// No-op.
|
|
return;
|
|
}
|
|
|
|
if (_anim_preload == (AnimPreloadTable *)NULL) {
|
|
// Trivial case.
|
|
_anim_preload = other->_anim_preload;
|
|
return;
|
|
}
|
|
|
|
// Copy-on-write.
|
|
PT(AnimPreloadTable) anim_preload = _anim_preload.get_write_pointer();
|
|
anim_preload->add_anims_from(other->_anim_preload.get_read_pointer());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::set_anim_blend_flag
|
|
// Access: Published
|
|
// Description: Defines the way the character responds to multiple
|
|
// calls to set_control_effect()). By default, this
|
|
// flag is set false, which disallows multiple
|
|
// animations. When this flag is false, it is not
|
|
// necessary to explicitly set the control_effect when
|
|
// starting an animation; starting the animation will
|
|
// implicitly remove the control_effect from the
|
|
// previous animation and set it on the current one.
|
|
//
|
|
// However, if this flag is set true, the control_effect
|
|
// must be explicitly set via set_control_effect()
|
|
// whenever an animation is to affect the character.
|
|
////////////////////////////////////////////////////////////////////
|
|
void PartBundle::
|
|
set_anim_blend_flag(bool anim_blend_flag) {
|
|
nassertv(Thread::get_current_pipeline_stage() == 0);
|
|
|
|
CDLockedReader cdata(_cycler);
|
|
if (cdata->_anim_blend_flag != anim_blend_flag) {
|
|
CDWriter cdataw(_cycler, cdata);
|
|
cdataw->_anim_blend_flag = anim_blend_flag;
|
|
|
|
if (!anim_blend_flag && cdataw->_blend.size() > 1) {
|
|
// If we just changed to disallow animation blending, we should
|
|
// eliminate all the AnimControls other than the
|
|
// most-recently-added one.
|
|
|
|
nassertv(cdataw->_last_control_set != NULL);
|
|
clear_and_stop_intersecting(cdataw->_last_control_set, cdataw);
|
|
}
|
|
|
|
cdataw->_anim_changed = true;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::apply_transform
|
|
// Access: Published
|
|
// Description: Returns a PartBundle that is a duplicate of this one,
|
|
// but with the indicated transform applied. If this is
|
|
// called multiple times with the same TransformState
|
|
// pointer, it returns the same PartBundle each time.
|
|
////////////////////////////////////////////////////////////////////
|
|
PT(PartBundle) PartBundle::
|
|
apply_transform(const TransformState *transform) {
|
|
if (transform->is_identity()) {
|
|
// Trivial no-op.
|
|
return this;
|
|
}
|
|
|
|
AppliedTransforms::iterator ati = _applied_transforms.find(transform);
|
|
if (ati != _applied_transforms.end()) {
|
|
if ((*ati).first.is_valid_pointer() &&
|
|
(*ati).second.is_valid_pointer()) {
|
|
// Here's our cached result.
|
|
return (*ati).second.p();
|
|
}
|
|
}
|
|
|
|
PT(PartBundle) new_bundle = DCAST(PartBundle, copy_subgraph());
|
|
new_bundle->xform(transform->get_mat());
|
|
|
|
if (ati != _applied_transforms.end()) {
|
|
// A stale pointer to a deleted result. Update it.
|
|
(*ati).first.refresh();
|
|
(*ati).second = new_bundle;
|
|
} else {
|
|
// No such result yet. Store it.
|
|
bool inserted = _applied_transforms.insert(AppliedTransforms::value_type(transform, new_bundle)).second;
|
|
nassertr(inserted, new_bundle);
|
|
}
|
|
|
|
// Make sure the new transform gets immediately applied to all of
|
|
// the joints.
|
|
new_bundle->force_update();
|
|
|
|
return new_bundle;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::clear_control_effects
|
|
// Access: Published
|
|
// Description: Sets the control effect of all AnimControls to zero
|
|
// (but does not "stop" the AnimControls). The
|
|
// character will no longer be affected by any
|
|
// animation, and will return to its default
|
|
// pose (unless restore-initial-pose is false).
|
|
//
|
|
// The AnimControls which are no longer associated will
|
|
// not be using any CPU cycles, but they may still be in
|
|
// the "playing" state; if they are later reassociated
|
|
// with the PartBundle they will resume at their current
|
|
// frame as if they'd been running all along.
|
|
////////////////////////////////////////////////////////////////////
|
|
void PartBundle::
|
|
clear_control_effects() {
|
|
nassertv(Thread::get_current_pipeline_stage() == 0);
|
|
|
|
CDLockedReader cdata(_cycler);
|
|
if (!cdata->_blend.empty()) {
|
|
CDWriter cdataw(_cycler, cdata);
|
|
cdataw->_blend.clear();
|
|
cdataw->_net_blend = 0.0f;
|
|
cdataw->_anim_changed = true;
|
|
determine_effective_channels(cdataw);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::output
|
|
// Access: Published, Virtual
|
|
// Description: Writes a one-line description of the bundle.
|
|
////////////////////////////////////////////////////////////////////
|
|
void PartBundle::
|
|
output(ostream &out) const {
|
|
out << get_type() << " " << get_name();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::write
|
|
// Access: Published, Virtual
|
|
// Description: Writes a brief description of the bundle and all of
|
|
// its descendants.
|
|
////////////////////////////////////////////////////////////////////
|
|
void PartBundle::
|
|
write(ostream &out, int indent_level) const {
|
|
indent(out, indent_level)
|
|
<< get_type() << " " << get_name() << " {\n";
|
|
write_descendants(out, indent_level + 2);
|
|
indent(out, indent_level) << "}\n";
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::bind_anim
|
|
// Access: Published
|
|
// Description: Binds the animation to the bundle, if possible, and
|
|
// returns a new AnimControl that can be used to start
|
|
// and stop the animation. If the anim hierarchy does
|
|
// not match the part hierarchy, returns NULL.
|
|
//
|
|
// If hierarchy_match_flags is 0, only an exact match is
|
|
// accepted; otherwise, it may contain a union of
|
|
// PartGroup::HierarchyMatchFlags values indicating
|
|
// conditions that will be tolerated (but warnings will
|
|
// still be issued).
|
|
//
|
|
// If subset is specified, it restricts the binding only
|
|
// to the named subtree of joints.
|
|
//
|
|
// The AnimControl is not stored within the PartBundle;
|
|
// it is the user's responsibility to maintain the
|
|
// pointer. The animation will automatically unbind
|
|
// itself when the AnimControl destructs (i.e. its
|
|
// reference count goes to zero).
|
|
////////////////////////////////////////////////////////////////////
|
|
PT(AnimControl) PartBundle::
|
|
bind_anim(AnimBundle *anim, int hierarchy_match_flags,
|
|
const PartSubset &subset) {
|
|
PT(AnimControl) control = new AnimControl(anim->get_name(), this, 1.0f, 0);
|
|
if (do_bind_anim(control, anim, hierarchy_match_flags, subset)) {
|
|
return control;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::load_bind_anim
|
|
// Access: Published
|
|
// Description: Binds an animation to the bundle. The animation is
|
|
// loaded from the disk via the indicated Loader object.
|
|
// In other respects, this behaves similarly to
|
|
// bind_anim(), with the addition of asynchronous
|
|
// support.
|
|
//
|
|
// If allow_aysnc is true, the load will be asynchronous
|
|
// if possible. This requires that the animation
|
|
// basename can be found in the PartBundle's preload
|
|
// table (see get_anim_preload()).
|
|
//
|
|
// In an asynchronous load, the animation file will be
|
|
// loaded and bound in a sub-thread. This means that
|
|
// the animation will not necessarily be available at
|
|
// the time this method returns. You may still use the
|
|
// returned AnimControl immediately, though, but no
|
|
// visible effect will occur until the animation
|
|
// eventually becomes available.
|
|
//
|
|
// You can test AnimControl::is_pending() to see if the
|
|
// animation has been loaded yet, or wait for it to
|
|
// finish with AnimControl::wait_pending() or even
|
|
// PartBundle::wait_pending(). You can also set an
|
|
// event to be triggered when the animation finishes
|
|
// loading with AnimControl::set_pending_done_event().
|
|
////////////////////////////////////////////////////////////////////
|
|
PT(AnimControl) PartBundle::
|
|
load_bind_anim(Loader *loader, const Filename &filename,
|
|
int hierarchy_match_flags, const PartSubset &subset,
|
|
bool allow_async) {
|
|
nassertr(loader != (Loader *)NULL, NULL);
|
|
|
|
LoaderOptions anim_options(LoaderOptions::LF_search |
|
|
LoaderOptions::LF_report_errors |
|
|
LoaderOptions::LF_convert_anim);
|
|
string basename = filename.get_basename_wo_extension();
|
|
|
|
int anim_index = -1;
|
|
CPT(AnimPreloadTable) anim_preload = _anim_preload.get_read_pointer();
|
|
if (anim_preload != (AnimPreloadTable *)NULL) {
|
|
anim_index = anim_preload->find_anim(basename);
|
|
}
|
|
|
|
if (anim_index < 0 || !allow_async || !Thread::is_threading_supported()) {
|
|
// The animation is not present in the table, or allow_async is
|
|
// false. Therefore, perform an ordinary synchronous
|
|
// load-and-bind.
|
|
|
|
PT(PandaNode) model = loader->load_sync(filename, anim_options);
|
|
if (model == (PandaNode *)NULL) {
|
|
// Couldn't load the file.
|
|
return NULL;
|
|
}
|
|
AnimBundle *anim = AnimBundleNode::find_anim_bundle(model);
|
|
if (anim == (AnimBundle *)NULL) {
|
|
// No anim bundle.
|
|
return NULL;
|
|
}
|
|
PT(AnimControl) control = bind_anim(anim, hierarchy_match_flags, subset);
|
|
if (control == (AnimControl *)NULL) {
|
|
// Couldn't bind.
|
|
return NULL;
|
|
}
|
|
control->set_anim_model(model);
|
|
return control;
|
|
}
|
|
|
|
// The animation is present in the table, so we can perform an
|
|
// asynchronous load-and-bind.
|
|
PN_stdfloat frame_rate = anim_preload->get_base_frame_rate(anim_index);
|
|
int num_frames = anim_preload->get_num_frames(anim_index);
|
|
PT(AnimControl) control =
|
|
new AnimControl(basename, this, frame_rate, num_frames);
|
|
|
|
if (!subset.is_include_empty()) {
|
|
// Figure out the actual subset of joints to be bound.
|
|
int joint_index = 0;
|
|
BitArray bound_joints;
|
|
find_bound_joints(joint_index, false, bound_joints, subset);
|
|
control->set_bound_joints(bound_joints);
|
|
}
|
|
|
|
PT(BindAnimRequest) request =
|
|
new BindAnimRequest(string("bind:") + filename.get_basename(),
|
|
filename, anim_options, loader, control,
|
|
hierarchy_match_flags, subset);
|
|
request->set_priority(async_bind_priority);
|
|
loader->load_async(request);
|
|
|
|
return control;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::wait_pending
|
|
// Access: Published
|
|
// Description: Blocks the current thread until all currently-pending
|
|
// AnimControls, with a nonzero control effect, have
|
|
// been loaded and are properly bound.
|
|
////////////////////////////////////////////////////////////////////
|
|
void PartBundle::
|
|
wait_pending() {
|
|
CDReader cdata(_cycler);
|
|
ChannelBlend::const_iterator cbi;
|
|
for (cbi = cdata->_blend.begin();
|
|
cbi != cdata->_blend.end();
|
|
++cbi) {
|
|
AnimControl *control = (*cbi).first;
|
|
PN_stdfloat effect = (*cbi).second;
|
|
if (effect != 0.0f) {
|
|
control->wait_pending();
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::freeze_joint
|
|
// Access: Published
|
|
// Description: Specifies that the joint with the indicated name
|
|
// should be frozen with the specified transform. It
|
|
// will henceforth always hold this fixed transform,
|
|
// regardless of any animations that may subsequently be
|
|
// bound to the joint.
|
|
//
|
|
// Returns true if the joint is successfully frozen, or
|
|
// false if the named child is not a joint (or slider)
|
|
// or does not exist.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool PartBundle::
|
|
freeze_joint(const string &joint_name, const TransformState *transform) {
|
|
PartGroup *child = find_child(joint_name);
|
|
if (child == (PartGroup *)NULL) {
|
|
return false;
|
|
}
|
|
|
|
CDWriter cdata(_cycler, false);
|
|
cdata->_anim_changed = true;
|
|
|
|
return child->apply_freeze(transform);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::freeze_joint
|
|
// Access: Published
|
|
// Description: Specifies that the joint with the indicated name
|
|
// should be frozen with the specified transform. It
|
|
// will henceforth always hold this fixed transform,
|
|
// regardless of any animations that may subsequently be
|
|
// bound to the joint.
|
|
//
|
|
// Returns true if the joint is successfully frozen, or
|
|
// false if the named child is not a joint (or slider)
|
|
// or does not exist.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool PartBundle::
|
|
freeze_joint(const string &joint_name, const LVecBase3 &pos, const LVecBase3 &hpr, const LVecBase3 &scale) {
|
|
PartGroup *child = find_child(joint_name);
|
|
if (child == (PartGroup *)NULL) {
|
|
return false;
|
|
}
|
|
|
|
CDWriter cdata(_cycler, false);
|
|
cdata->_anim_changed = true;
|
|
|
|
return child->apply_freeze_matrix(pos, hpr, scale);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::freeze_joint
|
|
// Access: Published
|
|
// Description: Specifies that the joint with the indicated name
|
|
// should be frozen with the specified transform. It
|
|
// will henceforth always hold this fixed transform,
|
|
// regardless of any animations that may subsequently be
|
|
// bound to the joint.
|
|
//
|
|
// Returns true if the joint is successfully frozen, or
|
|
// false if the named child is not a joint (or slider)
|
|
// or does not exist.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool PartBundle::
|
|
freeze_joint(const string &joint_name, PN_stdfloat value) {
|
|
PartGroup *child = find_child(joint_name);
|
|
if (child == (PartGroup *)NULL) {
|
|
return false;
|
|
}
|
|
|
|
CDWriter cdata(_cycler, false);
|
|
cdata->_anim_changed = true;
|
|
|
|
return child->apply_freeze_scalar(value);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::control_joint
|
|
// Access: Published
|
|
// Description: Specifies that the joint with the indicated name
|
|
// should be animated with the transform on the
|
|
// indicated node. It will henceforth always follow the
|
|
// node's transform, regardless of any animations that
|
|
// may subsequently be bound to the joint.
|
|
//
|
|
// Returns true if the joint is successfully controlled,
|
|
// or false if the named child is not a joint (or
|
|
// slider) or does not exist.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool PartBundle::
|
|
control_joint(const string &joint_name, PandaNode *node) {
|
|
PartGroup *child = find_child(joint_name);
|
|
if (child == (PartGroup *)NULL) {
|
|
return false;
|
|
}
|
|
|
|
CDWriter cdata(_cycler, false);
|
|
cdata->_anim_changed = true;
|
|
|
|
return child->apply_control(node);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::release_joint
|
|
// Access: Published
|
|
// Description: Releases the named joint from the effects of a
|
|
// previous call to freeze_joint() or control_joint().
|
|
// It will henceforth once again follow whatever
|
|
// transforms are dictated by the animation.
|
|
//
|
|
// Returns true if the joint is released, or false if
|
|
// the named child was not previously controlled or
|
|
// frozen, or it does not exist.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool PartBundle::
|
|
release_joint(const string &joint_name) {
|
|
PartGroup *child = find_child(joint_name);
|
|
if (child == (PartGroup *)NULL) {
|
|
return false;
|
|
}
|
|
|
|
CDWriter cdata(_cycler, false);
|
|
cdata->_anim_changed = true;
|
|
|
|
return child->clear_forced_channel();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::update
|
|
// Access: Published
|
|
// Description: Updates all the parts in the bundle to reflect the
|
|
// data for the current frame (as set in each of the
|
|
// AnimControls).
|
|
//
|
|
// Returns true if any part has changed as a result of
|
|
// this, or false otherwise.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool PartBundle::
|
|
update() {
|
|
Thread *current_thread = Thread::get_current_thread();
|
|
CDWriter cdata(_cycler, false, current_thread);
|
|
bool any_changed = false;
|
|
|
|
double now = ClockObject::get_global_clock()->get_frame_time(current_thread);
|
|
if (now > cdata->_last_update + _update_delay || cdata->_anim_changed) {
|
|
bool anim_changed = cdata->_anim_changed;
|
|
bool frame_blend_flag = cdata->_frame_blend_flag;
|
|
|
|
any_changed = do_update(this, cdata, NULL, false, anim_changed,
|
|
current_thread);
|
|
|
|
// Now update all the controls for next time.
|
|
ChannelBlend::const_iterator cbi;
|
|
for (cbi = cdata->_blend.begin(); cbi != cdata->_blend.end(); ++cbi) {
|
|
AnimControl *control = (*cbi).first;
|
|
control->mark_channels(frame_blend_flag);
|
|
}
|
|
|
|
cdata->_anim_changed = false;
|
|
cdata->_last_update = now;
|
|
}
|
|
|
|
return any_changed;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::force_update
|
|
// Access: Published
|
|
// Description: Updates all the parts in the bundle to reflect the
|
|
// data for the current frame, whether we believe it
|
|
// needs it or not.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool PartBundle::
|
|
force_update() {
|
|
Thread *current_thread = Thread::get_current_thread();
|
|
CDWriter cdata(_cycler, false, current_thread);
|
|
bool any_changed = do_update(this, cdata, NULL, true, true, current_thread);
|
|
|
|
// Now update all the controls for next time.
|
|
ChannelBlend::const_iterator cbi;
|
|
for (cbi = cdata->_blend.begin(); cbi != cdata->_blend.end(); ++cbi) {
|
|
AnimControl *control = (*cbi).first;
|
|
control->mark_channels(cdata->_frame_blend_flag);
|
|
}
|
|
|
|
cdata->_anim_changed = false;
|
|
|
|
return any_changed;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::control_activated
|
|
// Access: Public, Virtual
|
|
// Description: Called by the AnimControl whenever it starts an
|
|
// animation. This is just a hook so the bundle can do
|
|
// something, if necessary, before the animation starts.
|
|
////////////////////////////////////////////////////////////////////
|
|
void PartBundle::
|
|
control_activated(AnimControl *control) {
|
|
nassertv(Thread::get_current_pipeline_stage() == 0);
|
|
nassertv(control->get_part() == this);
|
|
|
|
CDLockedReader cdata(_cycler);
|
|
|
|
// If (and only if) our anim_blend_flag is false, then starting an
|
|
// animation implicitly enables it.
|
|
if (!cdata->_anim_blend_flag) {
|
|
CDWriter cdataw(_cycler, cdata);
|
|
do_set_control_effect(control, 1.0f, cdataw);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::do_bind_anim
|
|
// Access: Public
|
|
// Description: The internal implementation of bind_anim(), this
|
|
// receives a pointer to an uninitialized AnimControl
|
|
// and fills it in if the bind is successful. Returns
|
|
// true if successful, false otherwise.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool PartBundle::
|
|
do_bind_anim(AnimControl *control, AnimBundle *anim,
|
|
int hierarchy_match_flags, const PartSubset &subset) {
|
|
nassertr(Thread::get_current_pipeline_stage() == 0, false);
|
|
|
|
// Make sure this pointer doesn't destruct during the lifetime of this
|
|
// method.
|
|
PT(AnimBundle) ptanim = anim;
|
|
|
|
if ((hierarchy_match_flags & HMF_ok_wrong_root_name) == 0) {
|
|
// Make sure the root names match.
|
|
if (get_name() != ptanim->get_name()) {
|
|
if (chan_cat.is_error()) {
|
|
chan_cat.error()
|
|
<< "Root name of part (" << get_name()
|
|
<< ") does not match that of anim (" << ptanim->get_name()
|
|
<< ")\n";
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!check_hierarchy(anim, NULL, hierarchy_match_flags)) {
|
|
return false;
|
|
}
|
|
|
|
plist<int> holes;
|
|
int channel_index = 0;
|
|
pick_channel_index(holes, channel_index);
|
|
|
|
if (!holes.empty()) {
|
|
channel_index = holes.front();
|
|
}
|
|
|
|
int joint_index = 0;
|
|
BitArray bound_joints;
|
|
if (subset.is_include_empty()) {
|
|
bound_joints = BitArray::all_on();
|
|
}
|
|
bind_hierarchy(ptanim, channel_index, joint_index,
|
|
subset.is_include_empty(), bound_joints, subset);
|
|
control->setup_anim(this, anim, channel_index, bound_joints);
|
|
|
|
CDReader cdata(_cycler);
|
|
determine_effective_channels(cdata);
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::add_node
|
|
// Access: Protected, Virtual
|
|
// Description: Adds the PartBundleNode pointer to the set of nodes
|
|
// associated with the PartBundle. Normally called only
|
|
// by the PartBundleNode itself, for instance when the
|
|
// bundle is flattened with another node.
|
|
////////////////////////////////////////////////////////////////////
|
|
void PartBundle::
|
|
add_node(PartBundleNode *node) {
|
|
nassertv(find(_nodes.begin(), _nodes.end(), node) == _nodes.end());
|
|
_nodes.push_back(node);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::remove_node
|
|
// Access: Protected, Virtual
|
|
// Description: Removes the PartBundleNode pointer from the set of
|
|
// nodes associated with the PartBundle. Normally
|
|
// called only by the PartBundleNode itself, for
|
|
// instance when the bundle is flattened with another
|
|
// node.
|
|
////////////////////////////////////////////////////////////////////
|
|
void PartBundle::
|
|
remove_node(PartBundleNode *node) {
|
|
Nodes::iterator ni = find(_nodes.begin(), _nodes.end(), node);
|
|
nassertv(ni != _nodes.end());
|
|
_nodes.erase(ni);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::do_set_control_effect
|
|
// Access: Private
|
|
// Description: The private implementation of set_control_effect().
|
|
////////////////////////////////////////////////////////////////////
|
|
void PartBundle::
|
|
do_set_control_effect(AnimControl *control, PN_stdfloat effect, CData *cdata) {
|
|
nassertv(control->get_part() == this);
|
|
|
|
if (effect == 0.0f) {
|
|
// An effect of zero means to eliminate the control.
|
|
ChannelBlend::iterator cbi = cdata->_blend.find(control);
|
|
if (cbi != cdata->_blend.end()) {
|
|
cdata->_blend.erase(cbi);
|
|
cdata->_anim_changed = true;
|
|
}
|
|
|
|
} else {
|
|
// Otherwise we define it.
|
|
|
|
// If anim_blend_flag is false, we only allow one AnimControl at a
|
|
// time. Stop all of the other AnimControls.
|
|
if (!cdata->_anim_blend_flag) {
|
|
clear_and_stop_intersecting(control, cdata);
|
|
}
|
|
|
|
if (do_get_control_effect(control, cdata) != effect) {
|
|
cdata->_blend[control] = effect;
|
|
cdata->_anim_changed = true;
|
|
}
|
|
cdata->_last_control_set = control;
|
|
}
|
|
|
|
recompute_net_blend(cdata);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::do_get_control_effect
|
|
// Access: Private
|
|
// Description: The private implementation of get_control_effect().
|
|
////////////////////////////////////////////////////////////////////
|
|
PN_stdfloat PartBundle::
|
|
do_get_control_effect(AnimControl *control, const CData *cdata) const {
|
|
nassertr(control->get_part() == this, 0.0f);
|
|
|
|
ChannelBlend::const_iterator cbi = cdata->_blend.find(control);
|
|
if (cbi == cdata->_blend.end()) {
|
|
// The control is not in effect.
|
|
return 0.0f;
|
|
} else {
|
|
return (*cbi).second;
|
|
}
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::recompute_net_blend
|
|
// Access: Private
|
|
// Description: Recomputes the total blending amount after a control
|
|
// effect has been adjusted. This value must be kept
|
|
// up-to-date so we can normalize the blending amounts.
|
|
////////////////////////////////////////////////////////////////////
|
|
void PartBundle::
|
|
recompute_net_blend(CData *cdata) {
|
|
cdata->_net_blend = 0.0f;
|
|
|
|
ChannelBlend::const_iterator bti;
|
|
for (bti = cdata->_blend.begin(); bti != cdata->_blend.end(); ++bti) {
|
|
cdata->_net_blend += (*bti).second;
|
|
}
|
|
determine_effective_channels(cdata);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::clear_and_stop_intersecting
|
|
// Access: Private
|
|
// Description: Removes and stops all the currently activated
|
|
// AnimControls that animate some joints also animated
|
|
// by the indicated AnimControl. This is a special
|
|
// internal function that's only called when
|
|
// _anim_blend_flag is false, to automatically stop all
|
|
// the other currently-executing animations.
|
|
////////////////////////////////////////////////////////////////////
|
|
void PartBundle::
|
|
clear_and_stop_intersecting(AnimControl *control, CData *cdata) {
|
|
double new_net_blend = 0.0f;
|
|
ChannelBlend new_blend;
|
|
bool any_changed = false;
|
|
|
|
ChannelBlend::iterator cbi;
|
|
for (cbi = cdata->_blend.begin(); cbi != cdata->_blend.end(); ++cbi) {
|
|
AnimControl *ac = (*cbi).first;
|
|
if (ac == control ||
|
|
!ac->get_bound_joints().has_bits_in_common(control->get_bound_joints())) {
|
|
// Save this control--it's either the target control, or it has
|
|
// no joints in common with the target control.
|
|
new_blend.insert(new_blend.end(), (*cbi));
|
|
new_net_blend += (*cbi).second;
|
|
} else {
|
|
// Remove and stop this control.
|
|
ac->stop();
|
|
any_changed = true;
|
|
}
|
|
}
|
|
|
|
if (any_changed) {
|
|
cdata->_net_blend = new_net_blend;
|
|
cdata->_blend.swap(new_blend);
|
|
cdata->_anim_changed = true;
|
|
determine_effective_channels(cdata);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::finalize
|
|
// Access: Public, Virtual
|
|
// Description: Called by the BamReader to perform any final actions
|
|
// needed for setting up the object after all objects
|
|
// have been read and all pointers have been completed.
|
|
////////////////////////////////////////////////////////////////////
|
|
void PartBundle::
|
|
finalize(BamReader *) {
|
|
Thread *current_thread = Thread::get_current_thread();
|
|
CDWriter cdata(_cycler, true);
|
|
do_update(this, cdata, NULL, true, true, current_thread);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::write_datagram
|
|
// Access: Public, Virtual
|
|
// Description: Writes the contents of this object to the datagram
|
|
// for shipping out to a Bam file.
|
|
////////////////////////////////////////////////////////////////////
|
|
void PartBundle::
|
|
write_datagram(BamWriter *manager, Datagram &dg) {
|
|
PartGroup::write_datagram(manager, dg);
|
|
manager->write_pointer(dg, _anim_preload.get_read_pointer());
|
|
manager->write_cdata(dg, _cycler);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::complete_pointers
|
|
// Access: Public
|
|
// Description: Takes in a vector of pointers to TypedWritable
|
|
// objects that correspond to all the requests for
|
|
// pointers that this object made to BamReader.
|
|
////////////////////////////////////////////////////////////////////
|
|
int PartBundle::
|
|
complete_pointers(TypedWritable **p_list, BamReader *manager) {
|
|
int pi = PartGroup::complete_pointers(p_list, manager);
|
|
|
|
if (manager->get_file_minor_ver() >= 17) {
|
|
_anim_preload = DCAST(AnimPreloadTable, p_list[pi++]);
|
|
}
|
|
|
|
return pi;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::make_from_bam
|
|
// Access: Protected
|
|
// Description: Factory method to generate a PartBundle object
|
|
////////////////////////////////////////////////////////////////////
|
|
TypedWritable* PartBundle::
|
|
make_from_bam(const FactoryParams ¶ms) {
|
|
PartBundle *me = new PartBundle;
|
|
DatagramIterator scan;
|
|
BamReader *manager;
|
|
|
|
parse_params(params, scan, manager);
|
|
me->fillin(scan, manager);
|
|
manager->register_finalize(me);
|
|
return me;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::fillin
|
|
// Access: Protected
|
|
// Description: This internal function is called by make_from_bam to
|
|
// read in all of the relevant data from the BamFile for
|
|
// the new PartBundle.
|
|
////////////////////////////////////////////////////////////////////
|
|
void PartBundle::
|
|
fillin(DatagramIterator &scan, BamReader *manager) {
|
|
PartGroup::fillin(scan, manager);
|
|
if (manager->get_file_minor_ver() >= 17) {
|
|
manager->read_pointer(scan); // _anim_preload
|
|
}
|
|
if (manager->get_file_minor_ver() >= 10) {
|
|
manager->read_cdata(scan, _cycler);
|
|
}
|
|
if (manager->get_file_minor_ver() == 11) {
|
|
// No longer need the _modifies_anim_bundles flag
|
|
scan.get_bool();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::register_with_factory
|
|
// Access: Public, Static
|
|
// Description: Factory method to generate a PartBundle object
|
|
////////////////////////////////////////////////////////////////////
|
|
void PartBundle::
|
|
register_with_read_factory() {
|
|
BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::CData::Constructor
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
PartBundle::CData::
|
|
CData() {
|
|
_blend_type = anim_blend_type;
|
|
_anim_blend_flag = false;
|
|
_frame_blend_flag = interpolate_frames;
|
|
_root_xform = LMatrix4::ident_mat();
|
|
_last_control_set = NULL;
|
|
_net_blend = 0.0f;
|
|
_anim_changed = false;
|
|
_last_update = 0.0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::CData::Copy Constructor
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
PartBundle::CData::
|
|
CData(const PartBundle::CData ©) :
|
|
_blend_type(copy._blend_type),
|
|
_anim_blend_flag(copy._anim_blend_flag),
|
|
_frame_blend_flag(copy._frame_blend_flag),
|
|
_root_xform(copy._root_xform),
|
|
_last_control_set(copy._last_control_set),
|
|
_blend(copy._blend),
|
|
_net_blend(copy._net_blend),
|
|
_anim_changed(copy._anim_changed),
|
|
_last_update(copy._last_update)
|
|
{
|
|
// Note that this copy constructor is not used by the PartBundle
|
|
// copy constructor! Any elements that must be copied between
|
|
// PartBundles should also be explicitly copied there.
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::CData::make_copy
|
|
// Access: Public, Virtual
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
CycleData *PartBundle::CData::
|
|
make_copy() const {
|
|
return new CData(*this);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::CData::write_datagram
|
|
// Access: Public, Virtual
|
|
// Description: Writes the contents of this object to the datagram
|
|
// for shipping out to a Bam file.
|
|
////////////////////////////////////////////////////////////////////
|
|
void PartBundle::CData::
|
|
write_datagram(BamWriter *manager, Datagram &dg) const {
|
|
dg.add_uint8(_blend_type);
|
|
dg.add_bool(_anim_blend_flag);
|
|
dg.add_bool(_frame_blend_flag);
|
|
_root_xform.write_datagram(dg);
|
|
|
|
// The remaining members are strictly dynamic.
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::CData::fillin
|
|
// Access: Public, Virtual
|
|
// Description: This internal function is called by make_from_bam to
|
|
// read in all of the relevant data from the BamFile for
|
|
// the new PartBundle.
|
|
////////////////////////////////////////////////////////////////////
|
|
void PartBundle::CData::
|
|
fillin(DatagramIterator &scan, BamReader *manager) {
|
|
_blend_type = (BlendType)scan.get_uint8();
|
|
_anim_blend_flag = scan.get_bool();
|
|
_frame_blend_flag = scan.get_bool();
|
|
_root_xform.read_datagram(scan);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::BlendType output operator
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
ostream &
|
|
operator << (ostream &out, PartBundle::BlendType blend_type) {
|
|
switch (blend_type) {
|
|
case PartBundle::BT_linear:
|
|
return out << "linear";
|
|
|
|
case PartBundle::BT_normalized_linear:
|
|
return out << "normalized_linear";
|
|
|
|
case PartBundle::BT_componentwise:
|
|
return out << "componentwise";
|
|
|
|
case PartBundle::BT_componentwise_quat:
|
|
return out << "componentwise_quat";
|
|
}
|
|
|
|
chan_cat->error()
|
|
<< "Invalid BlendType value: " << (int)blend_type << "\n";
|
|
nassertr(false, out);
|
|
return out;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PartBundle::BlendType input operator
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
istream &
|
|
operator >> (istream &in, PartBundle::BlendType &blend_type) {
|
|
string word;
|
|
in >> word;
|
|
|
|
if (cmp_nocase_uh(word, "linear") == 0) {
|
|
blend_type = PartBundle::BT_linear;
|
|
|
|
} else if (cmp_nocase_uh(word, "normalized_linear") == 0) {
|
|
blend_type = PartBundle::BT_normalized_linear;
|
|
|
|
} else if (cmp_nocase_uh(word, "componentwise") == 0) {
|
|
blend_type = PartBundle::BT_componentwise;
|
|
|
|
} else if (cmp_nocase_uh(word, "componentwise_quat") == 0) {
|
|
blend_type = PartBundle::BT_componentwise_quat;
|
|
|
|
} else {
|
|
chan_cat->error()
|
|
<< "Invalid BlendType string: " << word << "\n";
|
|
blend_type = PartBundle::BT_linear;
|
|
}
|
|
return in;
|
|
}
|