diff --git a/Content/TangibleActor.uasset b/Content/TangibleActor.uasset index 9d9a6994..8caa1ae1 100644 --- a/Content/TangibleActor.uasset +++ b/Content/TangibleActor.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c0cb088b3d5231066229add82f253f629a4e049bd906b4a4901ee48d1aa5fa81 -size 60559 +oid sha256:ff28db0da7cc626522df8323b269291d5796c29889d52cd62528489e5471cd67 +size 89310 diff --git a/Source/Integration/AnimQueue.cpp b/Source/Integration/AnimQueue.cpp index e0a1c1f0..4bf31cbf 100644 --- a/Source/Integration/AnimQueue.cpp +++ b/Source/Integration/AnimQueue.cpp @@ -14,19 +14,19 @@ FlxAnimField FlxAnimStepDecoder::ReadField() { result.Persistent = Decoder.read_bool(); result.Type = (ElxAnimValueType)Decoder.read_uint8(); switch (result.Type) { - case T_STRING: { + case ElxAnimValueType::STRING: { result.S = Decoder.read_string_view(); break; } - case T_NUMBER: { + case ElxAnimValueType::NUMBER: { result.X = Decoder.read_double(); break; } - case T_BOOLEAN: { + case ElxAnimValueType::BOOLEAN: { result.X = Decoder.read_bool() ? 1.0 : 0.0; break; } - case T_XYZ: { + case ElxAnimValueType::XYZ: { result.X = Decoder.read_double(); result.Y = Decoder.read_double(); result.Z = Decoder.read_double(); @@ -34,7 +34,7 @@ FlxAnimField FlxAnimStepDecoder::ReadField() { } default: { Decoder.set_at_eof(); - result.Type = T_BOOLEAN; + result.Type = ElxAnimValueType::BOOLEAN; result.X = 0; break; } @@ -65,16 +65,16 @@ FString FlxAnimStepDecoder::DebugString(const FlxAnimStep& step) { result.Append(FString(field.Name.size(), (const UTF8CHAR*)field.Name.data())); result.Append(field.Persistent ? TEXT("=") : TEXT(":")); switch (field.Type) { - case ElxAnimValueType::T_STRING: + case ElxAnimValueType::STRING: result.Append(FString(field.S.size(), (const UTF8CHAR*)field.S.data())); break; - case ElxAnimValueType::T_NUMBER: + case ElxAnimValueType::NUMBER: result.Appendf(TEXT("%lf"), field.X); break; - case ElxAnimValueType::T_BOOLEAN: + case ElxAnimValueType::BOOLEAN: result.Append((field.X) == 1.0 ? TEXT("true") : TEXT("false")); break; - case ElxAnimValueType::T_XYZ: + case ElxAnimValueType::XYZ: result.Appendf(TEXT("%lf,%lf,%lf"), field.X, field.Y, field.Z); break; } @@ -84,33 +84,12 @@ FString FlxAnimStepDecoder::DebugString(const FlxAnimStep& step) { } -//// Our own copy of the animation queue. We only -//// store the hashes, not the steps. The First element -//// of the queue is the oldest item. -//// -//TDeque AQ; - -//// The sequence number of the first item in AQ. -//// -//int32 FirstSeqno; - -//// Map from hash to sequence number. -//// -//TMap HashToSeqno; - -//// The sequence number of the first unstarted animation. -//// -//int32 UnstartedSeqno; - -//// Array of recently-aborted hash values. -//TArray AbortedHashes; - - FlxAnimTracker::FlxAnimTracker() { AQ.Empty(); FirstSeqno = 0; HashToSeqno.Empty(); UnstartedSeqno = 0; + PlaybackMode = ElxAnimPlaybackMode::INVALID; AbortedHashes.Empty(); } @@ -157,18 +136,34 @@ void FlxAnimTracker::Update(std::string_view encqueue) { } } - // Abort all animations after the most recent matching - // record. Animations are aborted in most-recent-first order. - // - int32 nabort = (FirstSeqno + AQ.Num()) - (matchingseqno + 1); - check((nabort >= 0) && (nabort <= AQ.Num())); - for (int32 i = 0; i < nabort; i++) { + // Remove all animations after the most recent matching + // record. If we remove a 'started' animation, add that + // animation to the list of aborted animations. + // + int32 nremove = (FirstSeqno + AQ.Num()) - (matchingseqno + 1); + check((nremove >= 0) && (nremove <= AQ.Num())); + for (int32 i = 0; i < nremove; i++) { uint64 lasthash = AQ.Last().Hash; + int32 seqno = FirstSeqno + AQ.Num() - 1; HashToSeqno.Remove(lasthash); - AbortedHashes.Emplace(lasthash); + if (seqno < UnstartedSeqno) { + AbortedHashes.Emplace(lasthash); + } AQ.PopLast(); } + // If we aborted a 'started' animation, we have to fix + // up the Unstarted animation pointer. + // + // Note: this could leave the Unstarted pointer at + // seqno less than FirstSeqno. We will fix that state + // of affairs up later. + // + if (UnstartedSeqno > (FirstSeqno + AQ.Num())) { + UnstartedSeqno = matchingseqno; + PlaybackMode = ElxAnimPlaybackMode::BLEND_TO_FINAL; + } + // Transfer the new animations onto the queue. // while (!newsteps.IsEmpty()) { @@ -178,13 +173,6 @@ void FlxAnimTracker::Update(std::string_view encqueue) { HashToSeqno.Emplace(step.Hash, seqno); } - // Move back the Unstarted pointer, so that the - // unstarted range includes all the new animations. - // - if (UnstartedSeqno > matchingseqno + 1) { - UnstartedSeqno = matchingseqno + 1; - } - // If there are too many animations in AQ, discard // any very old ones. // @@ -194,11 +182,21 @@ void FlxAnimTracker::Update(std::string_view encqueue) { uint64 hash = AQ.First().Hash; HashToSeqno.Remove(hash); AQ.PopFirst(); + FirstSeqno += 1; } - FirstSeqno += ndiscard; - if (UnstartedSeqno < FirstSeqno) { + } + + // If UnstartedSeqno is before the live range, + // then the whole queue has to be replayed. Don't + // do that: just fast skip to the end. + // + if (UnstartedSeqno <= FirstSeqno) { + if (AQ.Num() == 0) { UnstartedSeqno = FirstSeqno; + } else { + UnstartedSeqno = FirstSeqno + AQ.Num() - 1; } + PlaybackMode = ElxAnimPlaybackMode::WARP_TO_FINAL; } } @@ -209,21 +207,23 @@ TArray FlxAnimTracker::GetAborted() { return result; } -bool FlxAnimTracker::AnyUnstarted() { +ElxAnimPlaybackMode FlxAnimTracker::GetNextStep(FlxAnimStoredStep &step) { int offset = UnstartedSeqno - FirstSeqno; - return (offset < AQ.Num()); + if (offset < AQ.Num()) { + step = AQ[offset]; + return PlaybackMode; + } else { + step.Hash = 0; + step.Body = ""; + return ElxAnimPlaybackMode::INVALID; + } } -FlxAnimStoredStep FlxAnimTracker::GetUnstarted() { - int offset = UnstartedSeqno - FirstSeqno; - check(offset < AQ.Num()); - return AQ[offset]; -} - -void FlxAnimTracker::Started(uint64 hash) { +void FlxAnimTracker::StartedStep(uint64 hash) { int offset = UnstartedSeqno - FirstSeqno; check(offset < AQ.Num()); check(AQ[offset].Hash == hash); UnstartedSeqno += 1; + PlaybackMode = ElxAnimPlaybackMode::START_ANIMATION; } diff --git a/Source/Integration/AnimQueue.h b/Source/Integration/AnimQueue.h index 268125c6..eeec04f3 100644 --- a/Source/Integration/AnimQueue.h +++ b/Source/Integration/AnimQueue.h @@ -10,12 +10,37 @@ // //////////////////////////////////////////////// -enum ElxAnimValueType { - T_STRING, - T_NUMBER, - T_BOOLEAN, - T_XYZ, - T_UNINITIALIZED +enum class ElxAnimValueType { + STRING, + NUMBER, + BOOLEAN, + XYZ, + INVALID +}; + +//////////////////////////////////////////////// +// +// Playback Modes +// +// There are three different ways to play an animation: +// +// 1. Start Animation. This is the normal way to play an animation. +// +// 2. Warp to Final. Skip the actual animation, and jump +// instantaneously to the final state of the animation. +// +// 3. Blend to Final. Skip the actual animation, and blend +// smoothly to the final state of the animation. If the current +// state is very far from the final state, then maybe jump instantaneously +// instead. +// +//////////////////////////////////////////////// + +enum class ElxAnimPlaybackMode { + START_ANIMATION, + WARP_TO_FINAL, + BLEND_TO_FINAL, + INVALID, }; //////////////////////////////////////////////// @@ -167,7 +192,12 @@ public: // int32 UnstartedSeqno; + // Indicates whether the unstarted animation should be played or otherwise. + // + ElxAnimPlaybackMode PlaybackMode; + // Array of recently-aborted hash values. + // TArray AbortedHashes; public: @@ -191,19 +221,17 @@ public: // TArray GetAborted(); - // Return true if there are any unstarted animation steps. - // - bool AnyUnstarted(); - // Get the next unstarted animation step. // - // You may only call this if AnyUnstarted returns true. + // Get the next animation step. Returns the next step and the + // playback mode. If the playback mode is INVALID then there is + // no next step to play // - FlxAnimStoredStep GetUnstarted(); + ElxAnimPlaybackMode GetNextStep(FlxAnimStoredStep& step); // Declare that an animation has been started. // // After starting an animation, you should call this. // - void Started(uint64 Hash); + void StartedStep(uint64 Hash); }; \ No newline at end of file diff --git a/Source/Integration/IntegrationGameModeBase.cpp b/Source/Integration/IntegrationGameModeBase.cpp index d410f009..73d08994 100644 --- a/Source/Integration/IntegrationGameModeBase.cpp +++ b/Source/Integration/IntegrationGameModeBase.cpp @@ -127,13 +127,25 @@ void AIntegrationGameModeBase::UpdateTangibles() { for (uint64 hash : aborted) { IlxTangibleInterface::Execute_AbortAnimation(t->Actor, hash); } - - if (t->AnimTracker.AnyUnstarted()) { - FlxAnimStoredStep step = t->AnimTracker.GetUnstarted(); - bool started = IlxTangibleInterface::Execute_StartAnimation(t->Actor, step.Hash, step.Body.size()); - if (started) { - t->AnimTracker.Started(step.Hash); - } + FlxAnimStoredStep step; + ElxAnimPlaybackMode mode = t->AnimTracker.GetNextStep(step); + bool started = false; + switch (mode) { + case ElxAnimPlaybackMode::INVALID: + started = false; // Nothing to do. + break; + case ElxAnimPlaybackMode::WARP_TO_FINAL: + started = IlxTangibleInterface::Execute_WarpToFinal(t->Actor, step.Hash, step.Body.size()); + break; + case ElxAnimPlaybackMode::BLEND_TO_FINAL: + started = IlxTangibleInterface::Execute_BlendToFinal(t->Actor, step.Hash, step.Body.size()); + break; + case ElxAnimPlaybackMode::START_ANIMATION: + started = IlxTangibleInterface::Execute_StartAnimation(t->Actor, step.Hash, step.Body.size()); + break; + } + if (started) { + t->AnimTracker.StartedStep(step.Hash); } } } diff --git a/Source/Integration/TangibleInterface.h b/Source/Integration/TangibleInterface.h index 221872ba..269fcd39 100644 --- a/Source/Integration/TangibleInterface.h +++ b/Source/Integration/TangibleInterface.h @@ -25,6 +25,12 @@ public: UFUNCTION(BlueprintImplementableEvent, Category = "Tangible Functionality") bool StartAnimation(int64 hash, int StrLen); + UFUNCTION(BlueprintImplementableEvent, Category = "Tangible Functionality") + bool WarpToFinal(int64 hash, int StrLen); + + UFUNCTION(BlueprintImplementableEvent, Category = "Tangible Functionality") + bool BlendToFinal(int64 hash, int StrLen); + UFUNCTION(BlueprintImplementableEvent, Category = "Tangible Functionality") bool AbortAnimation(int64 hash); };