mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-10 12:41:35 -10:00
* Add Generic Rotation Segment * SEXPd rotations work * Fully replace old animation system * Add sound segment * Fixes * Trigger Hangarbay doors for lone player without AI * Address Warnings * Fix Docking ANimations + some bugs * Fix sound issue + docking bug * fix bugs, add multi handling * Change direction into enum class * Address feedback * Futher clean aicode * Address feedback * Add Multi packet constexprs * Fix multi constexprs * Clarify AIS_DOCK comments * Address multi issues * Clang somehow detects issues where there are none...
439 lines
16 KiB
C++
439 lines
16 KiB
C++
#include "model/modelanimation_segments.h"
|
|
|
|
namespace animation {
|
|
|
|
void ModelAnimationSegmentSerial::recalculate(const submodel_instance* submodel_instance, const bsp_info* submodel, const ModelAnimationData<>& base, int pmi_id) {
|
|
ModelAnimationData<true> data = base;
|
|
float& duration = m_duration[pmi_id];
|
|
duration = 0.0f;
|
|
|
|
for (const auto& segment : m_segments) {
|
|
segment->recalculate(submodel_instance, submodel, data, pmi_id);
|
|
|
|
duration += segment->getDuration(pmi_id);
|
|
|
|
//To properly recalculate, we actually need to fully calculate the previous' segment's final delta
|
|
data.applyDelta(segment->calculateAnimation(data, segment->getDuration(pmi_id), pmi_id));
|
|
}
|
|
}
|
|
|
|
ModelAnimationData<true> ModelAnimationSegmentSerial::calculateAnimation(const ModelAnimationData<>& base, float time, int pmi_id) const {
|
|
ModelAnimationData<true> delta;
|
|
ModelAnimationData<> absoluteState = base;
|
|
|
|
size_t animationCnt = 0;
|
|
while (time > 0.0f && animationCnt < m_segments.size()) {
|
|
float timeLocal = time;
|
|
//Make sure that each segment actually stops at its end
|
|
if (timeLocal > m_segments[animationCnt]->getDuration(pmi_id))
|
|
timeLocal = m_segments[animationCnt]->getDuration(pmi_id);
|
|
ModelAnimationData<true> deltaLocal = m_segments[animationCnt]->calculateAnimation(absoluteState, timeLocal, pmi_id);
|
|
|
|
absoluteState.applyDelta(deltaLocal);
|
|
delta.applyDelta(deltaLocal);
|
|
|
|
time -= m_segments[animationCnt]->getDuration(pmi_id);
|
|
animationCnt++;
|
|
}
|
|
|
|
return delta;
|
|
}
|
|
|
|
void ModelAnimationSegmentSerial::executeAnimation(const ModelAnimationData<>& state, float timeboundLower, float timeboundUpper, ModelAnimationDirection direction, int pmi_id) {
|
|
for (const auto& segment : m_segments) {
|
|
if (timeboundLower < segment->getDuration(pmi_id)) {
|
|
segment->executeAnimation(state, fmaxf(0.0f, timeboundLower), fminf(timeboundUpper, segment->getDuration(pmi_id)), direction, pmi_id);
|
|
}
|
|
|
|
timeboundLower -= segment->getDuration(pmi_id);
|
|
timeboundUpper -= segment->getDuration(pmi_id);
|
|
|
|
if (timeboundUpper < 0)
|
|
return;
|
|
}
|
|
}
|
|
|
|
void ModelAnimationSegmentSerial::addSegment(std::shared_ptr<ModelAnimationSegment> segment) {
|
|
m_segments.push_back(std::move(segment));
|
|
}
|
|
|
|
|
|
void ModelAnimationSegmentParallel::recalculate(const submodel_instance* submodel_instance, const bsp_info* submodel, const ModelAnimationData<>& base, int pmi_id) {
|
|
float& duration = m_duration[pmi_id];
|
|
duration = 0.0f;
|
|
|
|
for (const auto& segment : m_segments) {
|
|
segment->recalculate(submodel_instance, submodel, base, pmi_id);
|
|
|
|
//recalculate total duration if necessary
|
|
float newDur = segment->getDuration(pmi_id);
|
|
duration = newDur > duration ? newDur : duration;
|
|
}
|
|
|
|
}
|
|
|
|
ModelAnimationData<true> ModelAnimationSegmentParallel::calculateAnimation(const ModelAnimationData<>& base, float time, int pmi_id) const {
|
|
ModelAnimationData<true> delta;
|
|
|
|
for (const auto& segment : m_segments) {
|
|
float timeLocal = time;
|
|
//Make sure that no segment runs over its length
|
|
if (timeLocal > segment->getDuration(pmi_id))
|
|
timeLocal = segment->getDuration(pmi_id);
|
|
|
|
ModelAnimationData<true> deltaLocal = segment->calculateAnimation(base, timeLocal, pmi_id);
|
|
delta.applyDelta(deltaLocal);
|
|
}
|
|
|
|
return delta;
|
|
}
|
|
|
|
void ModelAnimationSegmentParallel::executeAnimation(const ModelAnimationData<>& state, float timeboundLower, float timeboundUpper, ModelAnimationDirection direction, int pmi_id) {
|
|
for (const auto& segment : m_segments) {
|
|
if (timeboundLower < segment->getDuration(pmi_id)) {
|
|
segment->executeAnimation(state, timeboundLower, fminf(timeboundUpper, segment->getDuration(pmi_id)), direction, pmi_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ModelAnimationSegmentParallel::addSegment(std::shared_ptr<ModelAnimationSegment> segment) {
|
|
m_segments.push_back(std::move(segment));
|
|
}
|
|
|
|
|
|
ModelAnimationSegmentWait::ModelAnimationSegmentWait(float time) : m_time(time) { }
|
|
|
|
void ModelAnimationSegmentWait::recalculate(const submodel_instance* /*submodel_instance*/, const bsp_info* /*submodel*/, const ModelAnimationData<>& /*base*/, int pmi_id) {
|
|
m_duration[pmi_id] = m_time;
|
|
};
|
|
|
|
|
|
ModelAnimationSegmentSetPHB::ModelAnimationSegmentSetPHB(const angles& angle, bool isAngleRelative) :
|
|
m_targetAngle(angle), m_isAngleRelative(isAngleRelative) { }
|
|
|
|
void ModelAnimationSegmentSetPHB::recalculate(const submodel_instance* /*submodel_instance*/, const bsp_info* /*submodel*/, const ModelAnimationData<>& base, int pmi_id) {
|
|
if (m_isAngleRelative) {
|
|
vm_angles_2_matrix(&m_instances[pmi_id].rot, &m_targetAngle);
|
|
}
|
|
else {
|
|
//In Absolute mode we need to undo the previously applied rotation to make sure we actually end up at the target rotation despite having only a delta we output, as opposed to just overwriting the value
|
|
matrix unrotate, target;
|
|
vm_copy_transpose(&unrotate, &base.orientation);
|
|
vm_angles_2_matrix(&target, &m_targetAngle);
|
|
vm_matrix_x_matrix(&m_instances[pmi_id].rot, &target, &unrotate);
|
|
}
|
|
|
|
m_duration[pmi_id] = 0.0f;
|
|
}
|
|
|
|
ModelAnimationData<true> ModelAnimationSegmentSetPHB::calculateAnimation(const ModelAnimationData<>& /*base*/, float /*time*/, int pmi_id) const {
|
|
ModelAnimationData<true> data;
|
|
data.orientation = m_instances.at(pmi_id).rot;
|
|
return data;
|
|
}
|
|
|
|
|
|
ModelAnimationSegmentSetAngle::ModelAnimationSegmentSetAngle(float angle) :
|
|
m_angle(angle) { }
|
|
|
|
void ModelAnimationSegmentSetAngle::recalculate(const submodel_instance* /*submodel_instance*/, const bsp_info* submodel, const ModelAnimationData<>& /*base*/, int pmi_id) {
|
|
angles angs = vmd_zero_angles;
|
|
|
|
switch (submodel->movement_axis_id)
|
|
{
|
|
case MOVEMENT_AXIS_X:
|
|
angs.p = m_angle;
|
|
vm_angles_2_matrix(&m_rot, &angs);
|
|
break;
|
|
|
|
case MOVEMENT_AXIS_Y:
|
|
angs.h = m_angle;
|
|
vm_angles_2_matrix(&m_rot, &angs);
|
|
break;
|
|
|
|
case MOVEMENT_AXIS_Z:
|
|
angs.b = m_angle;
|
|
vm_angles_2_matrix(&m_rot, &angs);
|
|
break;
|
|
|
|
default:
|
|
vm_quaternion_rotate(&m_rot, m_angle, &submodel->movement_axis);
|
|
break;
|
|
}
|
|
|
|
m_duration[pmi_id] = 0.0f;
|
|
}
|
|
|
|
ModelAnimationData<true> ModelAnimationSegmentSetAngle::calculateAnimation(const ModelAnimationData<>& /*base*/, float /*time*/, int /*pmi_id*/) const {
|
|
ModelAnimationData<true> data;
|
|
data.orientation = m_rot;
|
|
return data;
|
|
}
|
|
|
|
|
|
constexpr float angles::*pbh[] = { &angles::p, &angles::b, &angles::h };
|
|
|
|
ModelAnimationSegmentRotation::ModelAnimationSegmentRotation(optional<angles> targetAngle, optional<angles> velocity, optional<float> time, optional<angles> acceleration, bool isAbsolute) :
|
|
m_targetAngle(targetAngle), m_velocity(velocity), m_time(time), m_acceleration(acceleration), m_isAbsolute(isAbsolute) { }
|
|
|
|
void ModelAnimationSegmentRotation::recalculate(const submodel_instance* /*submodel_instance*/, const bsp_info* submodel, const ModelAnimationData<>& base, int pmi_id) {
|
|
Assertion(!(m_targetAngle.has() ^ m_velocity.has() ^ m_time.has()), "Tried to run over- or underdefined rotation. Define exactly two out of 'time', 'velocity', and 'angle'!");
|
|
|
|
instance_data& instanceData = m_instances[pmi_id];
|
|
|
|
if (m_targetAngle.has()) { //If we have an angle specified, use it.
|
|
if (m_isAbsolute) {
|
|
angles absoluteOffset{ 0,0,0 };
|
|
vm_extract_angles_matrix_alternate(&absoluteOffset, &base.orientation);
|
|
const angles& targetAngle = m_targetAngle;
|
|
for(float angles::* i : pbh)
|
|
instanceData.m_actualTarget.*i = targetAngle.*i - absoluteOffset.*i;
|
|
}
|
|
else
|
|
instanceData.m_actualTarget = m_targetAngle;
|
|
}
|
|
else { //If we don't have an angle specified, calculate it. This implies we must have velocity and time.
|
|
const angles& v = m_velocity;
|
|
const float& t = m_time;
|
|
|
|
if (m_acceleration.has()) { //Consider acceleration to calculate the angle
|
|
//Let the following equations define our accelerated and braked movement, under the assumption that 2 * ta <= t.
|
|
//d : distance, v : max velocity, a : acceleration, t : total time, ta : time spent accelerating (and breaking)
|
|
//v = a * ta
|
|
//d = v * (t - 2 * ta) + 1/2 * 2 * a * ta^2
|
|
//this simplifies to d = (v(a * t - v))/a and ta = v / a
|
|
//if 2 * ta <= t does not hold, it's just d = 1/2 * 2 * a * (t/2)^2 -> this implies that the acceleration is too small to reach the target velocity within the specified time.
|
|
angles a = m_acceleration;
|
|
angles at;
|
|
|
|
for (float angles::* i : pbh) {
|
|
a.*i = copysignf(a.*i, v.*i);
|
|
at.*i = fmaxf(v.*i / a.*i, t / 2.0f);
|
|
}
|
|
instanceData.m_actualAccel = a;
|
|
instanceData.m_accelTime = at;
|
|
|
|
for (float angles::* i : pbh)
|
|
instanceData.m_actualTarget.*i = 2.0f * v.*i / a.*i <= t ? (v.*i * (a.*i * t - v.*i)) / a.*i: a.*i * (t * t / 4.0f);
|
|
|
|
}
|
|
else { //Don't consider acceleration, assume instant velocity.
|
|
for (float angles::* i : pbh)
|
|
instanceData.m_actualTarget.*i = v.*i * t;
|
|
}
|
|
}
|
|
|
|
if (m_velocity.has()) { //If we have velocity specified, use it.
|
|
instanceData.m_actualVelocity = m_velocity;
|
|
}
|
|
else { //If we don't have velocity specified, calculate it. This implies we must have an angle and time.
|
|
const float& t = m_time;
|
|
const angles& d = instanceData.m_actualTarget;
|
|
|
|
if (m_acceleration.has()) { //Consider acceleration to calculate the velocity
|
|
//Assume equations from calc angles case, but solve for ta and v now, under the assumption that these roots have a real solution.
|
|
//v = 1/2*(|a|*t-sqrt(|a|)*sqrt(|a|*t^2-4*|d|))*sign(d) and ta = 1/2*(t-(sqrt(|a|*t^2-4*|d|)/sqrt(|a|)))
|
|
//If the roots don't have a real solution, it's v = a * t/2, and ta = 1/2*t -> this implies that the acceleration is too small to reach the target distance within the specified time.
|
|
|
|
angles a = m_acceleration;
|
|
for (float angles::* i : pbh)
|
|
a.*i = copysignf(a.*i, d.*i);
|
|
instanceData.m_actualAccel = a;
|
|
|
|
angles at{ 0,0,0 };
|
|
|
|
for (float angles::* i : pbh) {
|
|
float a_abs = fabsf(a.*i);
|
|
float radicant = a_abs * t * t - 4 * fabsf(d.*i);
|
|
if (radicant >= 0) {
|
|
instanceData.m_actualVelocity.*i = copysignf(0.5f * (a_abs * t - sqrtf(a_abs) * sqrtf(radicant)), d.*i);
|
|
at.*i = 0.5f * (t - (sqrtf(radicant) / sqrtf(a_abs)));
|
|
}
|
|
else {
|
|
instanceData.m_actualVelocity.*i = a.*i * t / 2.0f;
|
|
at.*i = t / 2.0f;
|
|
}
|
|
}
|
|
|
|
instanceData.m_accelTime = at;
|
|
}
|
|
else { //Don't consider acceleration, assume instant velocity.
|
|
for (float angles::* i : pbh)
|
|
instanceData.m_actualVelocity.*i = d.*i / t;
|
|
}
|
|
|
|
}
|
|
|
|
if (m_time.has()) { //If we have time specified, use it.
|
|
const float& time = m_time;
|
|
m_duration[pmi_id] = time;
|
|
|
|
angles actualTime{ 0,0,0 };
|
|
for (float angles::* i : pbh)
|
|
actualTime.*i = time;
|
|
|
|
instanceData.m_actualTime = actualTime;
|
|
|
|
if (time <= 0.0f) {
|
|
Error(LOCATION, "Tried to rotate submodel %s in %.2f seconds. Rotation time must be positive and nonzero!", submodel->name, time);
|
|
}
|
|
}
|
|
else { //Calc time
|
|
float& duration = m_duration[pmi_id] = 0.0f;
|
|
|
|
angles& v = instanceData.m_actualVelocity;
|
|
const angles& d = instanceData.m_actualTarget;
|
|
|
|
angles actualAccel{ 0,0,0 };
|
|
angles accelTime{ 0,0,0 };
|
|
angles actualTime{ 0,0,0 };
|
|
|
|
for (float angles::* i : pbh) {
|
|
if (d.*i != 0.0f) {
|
|
if (v.*i == 0.0f) {
|
|
Warning(LOCATION, "Tried to rotate submodel %s by %.2f, but velocity was 0! Rotating with velocity 1...", submodel->name, d.*i);
|
|
v.*i = 1;
|
|
}
|
|
|
|
v.*i = copysignf(v.*i, d.*i);
|
|
|
|
float durationAxis = 0.0f;
|
|
|
|
if (m_acceleration.has()) { //Consider acceleration to calculate the time
|
|
//Assume equations from calc angles case, but solve for ta and t now, with the resulting ta <= t / 2.
|
|
//t = v/a+d/v and ta = v/a
|
|
//If thus d/v < v/a, it's t = 2*sqrt(d/a), and ta = 1/2*t -> this implies that the acceleration is too small to reach the target velocity within the specified distance.
|
|
float a = copysignf(((angles)m_acceleration).*i, d.*i);
|
|
actualAccel.*i = a;
|
|
|
|
float va = v.*i / a;
|
|
float dv = d.*i / v.*i;
|
|
|
|
if (dv >= va) {
|
|
durationAxis = va + dv;
|
|
accelTime.*i = va;
|
|
}
|
|
else {
|
|
durationAxis = 2.0f * sqrtf(d.*i / a);
|
|
accelTime.*i = durationAxis / 2.0f;
|
|
}
|
|
}
|
|
else {
|
|
durationAxis = d.*i / v.*i;
|
|
}
|
|
|
|
duration = duration < durationAxis ? durationAxis : duration;
|
|
actualTime.*i = durationAxis;
|
|
}
|
|
}
|
|
|
|
if (m_acceleration.has()) {
|
|
instanceData.m_actualAccel = actualAccel;
|
|
instanceData.m_accelTime = accelTime;
|
|
}
|
|
|
|
instanceData.m_actualTime = actualTime;
|
|
}
|
|
}
|
|
|
|
ModelAnimationData<true> ModelAnimationSegmentRotation::calculateAnimation(const ModelAnimationData<>& /*base*/, float time, int pmi_id) const {
|
|
const instance_data& instanceData = m_instances.at(pmi_id);
|
|
|
|
angles currentRot{ 0,0,0 };
|
|
|
|
if (instanceData.m_actualAccel.has()) {
|
|
const angles& a = instanceData.m_actualAccel;
|
|
const angles& at = instanceData.m_accelTime;
|
|
const angles& v = instanceData.m_actualVelocity;
|
|
const angles& t = instanceData.m_actualTime;
|
|
|
|
for (float angles::* i : pbh) {
|
|
float acceltime1 = fminf(time, at.*i);
|
|
currentRot.*i = 0.5f * a.*i * acceltime1 * acceltime1;
|
|
|
|
float lineartime = fminf(time - at.*i, t.*i - 2.0f * at.*i);
|
|
if (lineartime > 0) {
|
|
currentRot.*i += v.*i * lineartime;
|
|
}
|
|
|
|
float acceltime2 = fminf(time - (t.*i - at.*i), at.*i);
|
|
if (acceltime2 > 0) {
|
|
//Cap this to 0, as it could get negative if it rotates "longer than its duration" (which happens when a different axis takes longer to rotate)
|
|
float accel2dist = at.*i * a.*i * acceltime2 - 0.5f * a.*i * acceltime2 * acceltime2;
|
|
if(std::signbit(a.*i))
|
|
currentRot.*i += fminf(accel2dist, 0.0f);
|
|
else
|
|
currentRot.*i += fmaxf(accel2dist, 0.0f);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
for (float angles::* i : pbh)// Linear Rotation
|
|
currentRot.*i = instanceData.m_actualVelocity.*i * time;
|
|
}
|
|
|
|
for (float angles::* i : pbh) { // Clamp rotation to actual target
|
|
if (instanceData.m_actualTarget.*i < 0) {
|
|
currentRot.*i = fmaxf(instanceData.m_actualTarget.*i, currentRot.*i);
|
|
}
|
|
else {
|
|
currentRot.*i = fminf(instanceData.m_actualTarget.*i, currentRot.*i);
|
|
}
|
|
}
|
|
|
|
matrix orient;
|
|
vm_angles_2_matrix(&orient, ¤tRot);
|
|
|
|
ModelAnimationData<true> delta;
|
|
delta.orientation = orient;
|
|
|
|
return delta;
|
|
}
|
|
|
|
|
|
ModelAnimationSegmentSoundDuring::ModelAnimationSegmentSoundDuring(std::shared_ptr<ModelAnimationSegment> segment, gamesnd_id start, gamesnd_id end, gamesnd_id during, bool flipIfReversed) :
|
|
m_segment(std::move(segment)), m_start(start), m_end(end), m_during(during), m_flipIfReversed(flipIfReversed) { }
|
|
|
|
void ModelAnimationSegmentSoundDuring::recalculate(const submodel_instance* submodel_instance, const bsp_info* submodel, const ModelAnimationData<>& base, int pmi_id) {
|
|
m_segment->recalculate(submodel_instance, submodel, base, pmi_id);
|
|
m_duration[pmi_id] = m_segment->getDuration(pmi_id);
|
|
}
|
|
|
|
ModelAnimationData<true> ModelAnimationSegmentSoundDuring::calculateAnimation(const ModelAnimationData<>& base, float time, int pmi_id) const {
|
|
return m_segment->calculateAnimation(base, time, pmi_id);
|
|
}
|
|
|
|
void ModelAnimationSegmentSoundDuring::executeAnimation(const ModelAnimationData<>& state, float timeboundLower, float timeboundUpper, ModelAnimationDirection direction, int pmi_id) {
|
|
if (timeboundLower <= 0.0f && 0.0f <= timeboundUpper) {
|
|
if (!m_flipIfReversed || direction == ModelAnimationDirection::FWD)
|
|
playStartSnd(pmi_id);
|
|
else
|
|
playEndSnd(pmi_id);
|
|
}
|
|
|
|
if (0.0f < timeboundLower && timeboundUpper < m_duration.at(pmi_id)) {
|
|
if (m_during.isValid() && (!m_instances[pmi_id].currentlyPlaying.isValid() || !snd_is_playing(m_instances[pmi_id].currentlyPlaying)))
|
|
m_instances[pmi_id].currentlyPlaying = snd_play_looping(gamesnd_get_game_sound(m_during));
|
|
}
|
|
|
|
if (timeboundLower <= m_duration.at(pmi_id) && m_duration.at(pmi_id) <= timeboundUpper) {
|
|
if (!m_flipIfReversed || direction == ModelAnimationDirection::FWD)
|
|
playEndSnd(pmi_id);
|
|
else
|
|
playStartSnd(pmi_id);
|
|
}
|
|
m_segment->executeAnimation(state, timeboundLower, timeboundUpper, direction, pmi_id);
|
|
}
|
|
|
|
void ModelAnimationSegmentSoundDuring::playStartSnd(int pmi_id) {
|
|
if(m_start.isValid())
|
|
m_instances[pmi_id].currentlyPlaying = snd_play(gamesnd_get_game_sound(m_start));
|
|
}
|
|
void ModelAnimationSegmentSoundDuring::playEndSnd(int pmi_id) {
|
|
if (snd_is_playing(m_instances[pmi_id].currentlyPlaying))
|
|
snd_stop(m_instances[pmi_id].currentlyPlaying);
|
|
|
|
if (m_end.isValid())
|
|
m_instances[pmi_id].currentlyPlaying = snd_play(gamesnd_get_game_sound(m_end));
|
|
}
|
|
} |