671 lines
22 KiB
C++
671 lines
22 KiB
C++
// Filename: animControl.cxx
|
|
// Created by: drose (19Feb99)
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PANDA 3D SOFTWARE
|
|
// Copyright (c) 2001, Disney Enterprises, Inc. All rights reserved
|
|
//
|
|
// All use of this software is subject to the terms of the Panda 3d
|
|
// Software license. You should have received a copy of this license
|
|
// along with this source code; you will also find a current copy of
|
|
// the license at http://www.panda3d.org/license.txt .
|
|
//
|
|
// To contact the maintainers of this program write to
|
|
// panda3d@yahoogroups.com .
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
|
|
#if defined(WIN32_VC) && !defined(NO_PCH)
|
|
#include "chan_headers.h"
|
|
#endif
|
|
|
|
#pragma hdrstop
|
|
|
|
#if !defined(WIN32_VC) || defined(NO_PCH)
|
|
#include "animControl.h"
|
|
#include "animChannelBase.h"
|
|
#include "partBundle.h"
|
|
#include "config_chan.h"
|
|
|
|
#include <event.h>
|
|
#include <throw_event.h>
|
|
#endif
|
|
|
|
|
|
TypeHandle AnimControl::_type_handle;
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: AnimControl::Constructor
|
|
// Access: Public, Scheme
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
AnimControl::
|
|
AnimControl(PartBundle *part, AnimBundle *anim, int channel_index) {
|
|
MemoryUsage::update_type(this, get_class_type());
|
|
|
|
_part = part;
|
|
_anim = anim;
|
|
_channel_index = channel_index;
|
|
|
|
_play_rate = 1.0;
|
|
_frame = 0.0;
|
|
_as_of_time = 0.0;
|
|
_playing = false;
|
|
|
|
_marked_frame = -1;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: AnimControl::play
|
|
// Access: Public
|
|
// Description: Runs the entire animation from beginning to end and
|
|
// stops, throwing the stop event (if it is non-NULL).
|
|
////////////////////////////////////////////////////////////////////
|
|
void AnimControl::
|
|
play(const CPT_Event &stop_event) {
|
|
nassertv(get_num_frames() > 0);
|
|
|
|
if (get_play_rate() < 0.0) {
|
|
play(get_num_frames()-1, 0, stop_event);
|
|
} else {
|
|
play(0, get_num_frames()-1, stop_event);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: AnimControl::play
|
|
// Access: Public
|
|
// Description: Runs the animation from the frame "from" to and
|
|
// including the frame "to", at which point the
|
|
// animation is stopped and the indicated stop event is
|
|
// thrown (if it is non-NULL). If the to frame is less
|
|
// than the from frame (unless play_rate is negative),
|
|
// the animation will wrap around the end and begins
|
|
// again at the beginning before the animation stops.
|
|
////////////////////////////////////////////////////////////////////
|
|
void AnimControl::
|
|
play(int from, int to, const CPT_Event &stop_event) {
|
|
nassertv(get_num_frames() > 0);
|
|
|
|
nassertv(from >= 0 && from < get_num_frames());
|
|
nassertv(to >= 0 && to < get_num_frames());
|
|
_as_of_time = ClockObject::get_global_clock()->get_frame_time();
|
|
_playing = true;
|
|
|
|
_actions = _user_actions;
|
|
if (stop_event != (Event*)0L) {
|
|
insert_event_action(_actions, to, stop_event);
|
|
}
|
|
insert_stop_action(_actions, to);
|
|
|
|
get_part()->control_activated(this);
|
|
set_frame(from);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: AnimControl::loop
|
|
// Access: Public
|
|
// Description: Starts the entire animation looping. If restart is
|
|
// true, the animation is restarted from the beginning;
|
|
// otherwise, it continues from the current frame.
|
|
////////////////////////////////////////////////////////////////////
|
|
void AnimControl::
|
|
loop(bool restart) {
|
|
nassertv(get_num_frames() > 0);
|
|
|
|
_as_of_time = ClockObject::get_global_clock()->get_frame_time();
|
|
_playing = true;
|
|
|
|
_actions = _user_actions;
|
|
get_part()->control_activated(this);
|
|
|
|
if (restart) {
|
|
if (get_play_rate() < 0.0) {
|
|
set_frame(get_num_frames() - 1);
|
|
} else {
|
|
set_frame(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: AnimControl::loop
|
|
// Access: Public
|
|
// Description: Loops the animation from the frame "from" to and
|
|
// including the frame "to", indefinitely. If restart
|
|
// is true, the animation is restarted from the
|
|
// beginning; otherwise, it continues from the current
|
|
// frame.
|
|
////////////////////////////////////////////////////////////////////
|
|
void AnimControl::
|
|
loop(bool restart, int from, int to) {
|
|
nassertv(get_num_frames() > 0);
|
|
|
|
nassertv(from >= 0 && from < get_num_frames());
|
|
nassertv(to >= 0 && to < get_num_frames());
|
|
_as_of_time = ClockObject::get_global_clock()->get_frame_time();
|
|
_playing = true;
|
|
|
|
_actions = _user_actions;
|
|
|
|
if (get_play_rate() < 0.0) {
|
|
// If we're playing backward, we need to set up the loop a little
|
|
// differently.
|
|
|
|
if ((to == 0 && from == get_num_frames()-1) ||
|
|
(to == from-1)) {
|
|
|
|
// In this case, the user has specified to loop over the whole
|
|
// range of animation. We don't need a special jump action to
|
|
// handle this.
|
|
|
|
} else {
|
|
// Otherwise, set up a jump action to effect the loop. This isn't
|
|
// completely accurate, since it will always jump exactly to the
|
|
// first frame of the loop, no matter how many frames past the end
|
|
// we'd gotten to, but it should be reasonably close, especially
|
|
// if the number of frames in the loop is large enough and/or the
|
|
// frame rate is high enough.
|
|
insert_jump_action(_actions, (to-1+get_num_frames())%get_num_frames(),
|
|
from);
|
|
}
|
|
} else {
|
|
|
|
if ((from == 0 && to == get_num_frames()-1) ||
|
|
(from == to-1)) {
|
|
|
|
// In this case, the user has specified to loop over the whole
|
|
// range of animation. We don't need a special jump action to
|
|
// handle this.
|
|
|
|
} else {
|
|
// Otherwise, set up a jump action to effect the loop. This isn't
|
|
// completely accurate, since it will always jump exactly to the
|
|
// first frame of the loop, no matter how many frames past the end
|
|
// we'd gotten to, but it should be reasonably close, especially
|
|
// if the number of frames in the loop is large enough and/or the
|
|
// frame rate is high enough.
|
|
insert_jump_action(_actions, (to+1)%get_num_frames(), from);
|
|
}
|
|
}
|
|
|
|
get_part()->control_activated(this);
|
|
if (restart) {
|
|
set_frame(from);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: AnimControl::stop
|
|
// Access: Public
|
|
// Description: Stops a currently playing or looping animation right
|
|
// where it is. The animation remains posed at the
|
|
// current frame, and no event is thrown.
|
|
////////////////////////////////////////////////////////////////////
|
|
void AnimControl::
|
|
stop() {
|
|
_playing = false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: AnimControl::pose
|
|
// Access: Public
|
|
// Description: Sets the animation to the indicated frame and holds
|
|
// it there.
|
|
////////////////////////////////////////////////////////////////////
|
|
void AnimControl::
|
|
pose(int frame) {
|
|
nassertv(get_num_frames() > 0);
|
|
|
|
nassertv(frame >= 0 && frame < get_num_frames());
|
|
_as_of_time = ClockObject::get_global_clock()->get_frame_time();
|
|
_playing = false;
|
|
|
|
_actions = _user_actions;
|
|
|
|
get_part()->control_activated(this);
|
|
set_frame(frame);
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: AnimControl::add_event
|
|
// Access: Public
|
|
// Description: Adds the indicated event to the list of events that
|
|
// will be called whenever the animation reaches the
|
|
// indicated frame number. Once added, the event will
|
|
// persist until it is removed via remove_event() or
|
|
// remove_all_events().
|
|
////////////////////////////////////////////////////////////////////
|
|
void AnimControl::
|
|
add_event(int frame, const CPT_Event &event) {
|
|
insert_event_action(_user_actions, frame, event);
|
|
if (_playing) {
|
|
insert_event_action(_actions, frame, event);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: AnimControl::remove_event
|
|
// Access: Public
|
|
// Description: Removes all events found that match the indicated
|
|
// event name, and returns the number of events
|
|
// removed.
|
|
////////////////////////////////////////////////////////////////////
|
|
int AnimControl::
|
|
remove_event(const string &event_name) {
|
|
Actions new_actions;
|
|
|
|
int removed = 0;
|
|
|
|
Actions::const_iterator ai;
|
|
for (ai = _user_actions.begin(); ai != _user_actions.end(); ++ai) {
|
|
const Action &action = (*ai).second;
|
|
if (action._type == AT_event &&
|
|
action._event->get_name() == event_name) {
|
|
// Remove this event by not copying it to new_actions.
|
|
removed++;
|
|
} else {
|
|
// Preserve this event.
|
|
new_actions.insert(*ai);
|
|
}
|
|
}
|
|
|
|
if (removed != 0) {
|
|
_user_actions.swap(new_actions);
|
|
|
|
if (_playing) {
|
|
new_actions.clear();
|
|
int p_removed = 0;
|
|
for (ai = _actions.begin(); ai != _actions.end(); ++ai) {
|
|
const Action &action = (*ai).second;
|
|
if (action._type == AT_event &&
|
|
action._event->get_name() == event_name) {
|
|
// Remove this event by not copying it to new_actions.
|
|
p_removed++;
|
|
} else {
|
|
// Preserve this event.
|
|
new_actions.insert(*ai);
|
|
}
|
|
}
|
|
nassertr(p_removed == removed, removed);
|
|
_actions.swap(new_actions);
|
|
}
|
|
}
|
|
|
|
return removed;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: AnimControl::remove_all_events
|
|
// Access: Public
|
|
// Description: Removes all user-defined event messages. However, if
|
|
// called while an animation is running, this will not
|
|
// take effect until the animation is stopped and
|
|
// restarted.
|
|
////////////////////////////////////////////////////////////////////
|
|
void AnimControl::
|
|
remove_all_events() {
|
|
_user_actions.clear();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: AnimControl::get_part
|
|
// Access: Public
|
|
// Description: Returns the PartBundle bound in with this
|
|
// AnimControl.
|
|
////////////////////////////////////////////////////////////////////
|
|
PartBundle *AnimControl::
|
|
get_part() const {
|
|
return DCAST(PartBundle, _part);
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: AnimControl::advance_time
|
|
// Access: Public
|
|
// Description: Tells the AnimControl what time it is. This
|
|
// recomputes the frame number according to the amount
|
|
// of time elapsed since last time. Until this function
|
|
// is called, the frame number will not increment. The
|
|
// time passed to this function must be nondecreasing;
|
|
// it is an error to call it with a value less than a
|
|
// previously-passed value.
|
|
////////////////////////////////////////////////////////////////////
|
|
void AnimControl::
|
|
advance_time(double time) {
|
|
double elapsed_time = time - _as_of_time;
|
|
double elapsed_frames = elapsed_time * get_frame_rate();
|
|
_as_of_time = time;
|
|
|
|
if (_playing && elapsed_frames != 0.0) {
|
|
int orig_frame = get_frame();
|
|
|
|
_frame += elapsed_frames;
|
|
double num_frames = (double)get_num_frames();
|
|
|
|
int new_frame = get_frame();
|
|
|
|
// Now call all the actions.
|
|
if (elapsed_frames < 0.0) {
|
|
// If we're playing the animation backward, we have to check the
|
|
// actions in reverse order.
|
|
|
|
if (new_frame >= 0) {
|
|
do_actions_backward(orig_frame-1, new_frame);
|
|
} else {
|
|
if (do_actions_backward(orig_frame-1, 0)) {
|
|
_frame = _frame - floor(_frame / num_frames) * num_frames;
|
|
new_frame = get_frame();
|
|
do_actions_backward(get_num_frames(), new_frame);
|
|
}
|
|
}
|
|
} else {
|
|
// Normally, we'll be playing the animation forward.
|
|
|
|
if (new_frame < get_num_frames()) {
|
|
do_actions_forward(orig_frame+1, new_frame);
|
|
} else {
|
|
if (do_actions_forward(orig_frame+1, get_num_frames()-1)) {
|
|
_frame = _frame - floor(_frame / num_frames) * num_frames;
|
|
new_frame = get_frame();
|
|
do_actions_forward(0, new_frame);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: AnimControl::channel_has_changed
|
|
// Access: Public
|
|
// Description: Returns true if the indicated channel value has
|
|
// changed since the last call to mark_channels().
|
|
////////////////////////////////////////////////////////////////////
|
|
bool AnimControl::
|
|
channel_has_changed(AnimChannelBase *channel) const {
|
|
if (_marked_frame < 0) {
|
|
return true;
|
|
}
|
|
|
|
int this_frame = get_frame();
|
|
|
|
if (this_frame == _marked_frame) {
|
|
return false;
|
|
}
|
|
|
|
return channel->has_changed(_marked_frame, this_frame);
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: AnimControl::mark_channels
|
|
// Access: Public
|
|
// Description: Marks this point as the point of reference for the
|
|
// next call to channel_has_changed().
|
|
////////////////////////////////////////////////////////////////////
|
|
void AnimControl::
|
|
mark_channels() {
|
|
_marked_frame = get_frame();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: AnimControl::output
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void AnimControl::
|
|
output(ostream &out) const {
|
|
out << "AnimControl(" << get_part()->get_name()
|
|
<< ", " << get_anim()->get_name() << ")";
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: AnimControl::insert_event_action
|
|
// Access: Private, Static
|
|
// Description: Inserts an "event" action at the indicated frame
|
|
// number. The event will be thrown as the animation
|
|
// reaches the indicated frame number.
|
|
////////////////////////////////////////////////////////////////////
|
|
void AnimControl::
|
|
insert_event_action(Actions &actions, int frame, const CPT_Event &event) {
|
|
Action new_action;
|
|
new_action._type = AT_event;
|
|
new_action._event = event;
|
|
actions.insert(pair<int, Action>(frame, new_action));
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: AnimControl::insert_stop_action
|
|
// Access: Private, Static
|
|
// Description: Inserts a "stop" action at the indicated frame
|
|
// number. The animation will stop as soon as it
|
|
// reaches the indicated frame number, traveling from
|
|
// either direction.
|
|
////////////////////////////////////////////////////////////////////
|
|
void AnimControl::
|
|
insert_stop_action(Actions &actions, int frame) {
|
|
Action new_action;
|
|
new_action._type = AT_stop;
|
|
actions.insert(pair<int, Action>(frame, new_action));
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: AnimControl::insert_jump_action
|
|
// Access: Private, Static
|
|
// Description: Inserts a "jump" action at the indicated frame
|
|
// number. When the animation reaches the indicated
|
|
// frame number, it will jump to the indicated jump_to
|
|
// frame. It will not seem to spend any time at the
|
|
// reached frame number.
|
|
////////////////////////////////////////////////////////////////////
|
|
void AnimControl::
|
|
insert_jump_action(Actions &actions, int frame, int jump_to) {
|
|
Action new_action;
|
|
new_action._type = AT_jump;
|
|
new_action._jump_to = jump_to;
|
|
actions.insert(pair<int, Action>(frame, new_action));
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: AnimControl::set_frame
|
|
// Access: Private
|
|
// Description: Sets the current frame number to the indicated frame,
|
|
// and performs any actions on that frame.
|
|
////////////////////////////////////////////////////////////////////
|
|
void AnimControl::
|
|
set_frame(double frame) {
|
|
_frame = frame;
|
|
int iframe = get_frame();
|
|
do_actions_forward(iframe, iframe);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: AnimControl::do_actions_forward
|
|
// Access: Private
|
|
// Description: Calls each of the actions, in turn, on the timeline
|
|
// between the indicated "from" frame and the indicated
|
|
// "to" frame, inclusive. If any of the actions
|
|
// specifies to stop the animation, the animation is
|
|
// stopped, the current frame number is set to the point
|
|
// of stopping, no further actions are called, and false
|
|
// is returned. Otherwise, true is returned.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool AnimControl::
|
|
do_actions_forward(int from, int to) {
|
|
if (to >= from) {
|
|
Actions::const_iterator lower = _actions.lower_bound(from);
|
|
Actions::const_iterator upper = _actions.lower_bound(to+1);
|
|
|
|
int sequence_frame = -1;
|
|
const Action *sequence_action;
|
|
|
|
Actions::const_iterator ai;
|
|
for (ai = lower; ai != upper; ++ai) {
|
|
int frame = (*ai).first;
|
|
const Action &action = (*ai).second;
|
|
|
|
if (sequence_frame != -1 && frame > sequence_frame) {
|
|
// We encountered an action that resequenced our frame numbers.
|
|
// Now that we've finished evaluating all the other actions that
|
|
// occurred in the same frame, evaluate this one action and
|
|
// exit.
|
|
do_sequence_action(sequence_frame, *sequence_action);
|
|
return false;
|
|
}
|
|
|
|
do_action(frame, action, sequence_frame, sequence_action);
|
|
}
|
|
|
|
if (sequence_frame != -1) {
|
|
do_sequence_action(sequence_frame, *sequence_action);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: AnimControl::do_actions_backward
|
|
// Access: Private
|
|
// Description: Calls each of the actions, in turn, on the timeline
|
|
// between the indicated "from" frame and the indicated
|
|
// "to" frame, in reverse order. If any of the actions
|
|
// specifies to stop the animation, the animation is
|
|
// stopped, the current frame number is set to the point
|
|
// of stopping, no further actions are called, and false
|
|
// is returned. Otherwise, true is returned.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool AnimControl::
|
|
do_actions_backward(int from, int to) {
|
|
#ifndef WIN32_VC
|
|
if (from >= to) {
|
|
#if defined(WIN32_VC) && !defined(NO_PCH)
|
|
typedef reverse_iterator<Actions::const_iterator, Actions::value_type> Action_reverse_iterator;
|
|
#else
|
|
typedef reverse_iterator<Actions::const_iterator> Action_reverse_iterator;
|
|
#endif
|
|
Action_reverse_iterator lower(_actions.upper_bound(from));
|
|
Action_reverse_iterator upper(_actions.upper_bound(to-1));
|
|
|
|
int sequence_frame = -1;
|
|
const Action *sequence_action;
|
|
|
|
Action_reverse_iterator ai;
|
|
for (ai = lower; ai != upper; ++ai) {
|
|
int frame = (*ai).first;
|
|
const Action &action = (*ai).second;
|
|
|
|
if (sequence_frame != -1 && frame < sequence_frame) {
|
|
// We encountered an action that resequenced our frame numbers.
|
|
// Now that we've finished evaluating all the other actions that
|
|
// occurred in the same frame, evaluate this one action and
|
|
// exit.
|
|
do_sequence_action(sequence_frame, *sequence_action);
|
|
return false;
|
|
}
|
|
|
|
do_action(frame, action, sequence_frame, sequence_action);
|
|
}
|
|
|
|
if (sequence_frame != -1) {
|
|
do_sequence_action(sequence_frame, *sequence_action);
|
|
return false;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: AnimControl::do_action
|
|
// Access: Private
|
|
// Description: Performs a single action. If the action involves
|
|
// some resequencing behavior--stopping, or jumping
|
|
// around to a new frame or something--does nothing
|
|
// immediately, but instead sets sequence_frame and
|
|
// sequence_action to the current frame number and
|
|
// action, so they may be executed later (after all the
|
|
// other actions this frame have been executed).
|
|
////////////////////////////////////////////////////////////////////
|
|
void AnimControl::
|
|
do_action(int frame, const Action &action,
|
|
int &sequence_frame, const Action *&sequence_action) {
|
|
switch (action._type) {
|
|
case AT_stop:
|
|
case AT_jump:
|
|
sequence_frame = frame;
|
|
sequence_action = &action;
|
|
break;
|
|
|
|
case AT_event:
|
|
throw_event(action._event);
|
|
break;
|
|
|
|
default:
|
|
chan_cat.error() << "Invalid action!\n";
|
|
abort();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: AnimControl::do_sequence_action
|
|
// Access: Private
|
|
// Description: Performs an action that was saved from do_action(),
|
|
// above. This action presumably does some mucking
|
|
// around with the frame number or something, so we
|
|
// needed to do all the other actions associated with
|
|
// that frame first.
|
|
////////////////////////////////////////////////////////////////////
|
|
void AnimControl::
|
|
do_sequence_action(int, const Action &action) {
|
|
switch (action._type) {
|
|
case AT_stop:
|
|
stop();
|
|
break;
|
|
|
|
case AT_jump:
|
|
set_frame(action._jump_to);
|
|
break;
|
|
|
|
case AT_event:
|
|
default:
|
|
chan_cat.error() << "Invalid sequence action!\n";
|
|
abort();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: AnimControl::Action::output
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void AnimControl::Action::
|
|
output(ostream &out) const {
|
|
switch (_type) {
|
|
case AT_event:
|
|
out << "event " << *_event;
|
|
break;
|
|
|
|
case AT_stop:
|
|
out << "stop";
|
|
break;
|
|
|
|
case AT_jump:
|
|
out << "jump to " << _jump_to;
|
|
break;
|
|
|
|
default:
|
|
out << "**error**";
|
|
}
|
|
}
|