Fix one dollar recognizer implementation

This commit is contained in:
2025-05-14 03:14:20 -04:00
parent d21de2a7c5
commit ce2dfbcb54
11 changed files with 76 additions and 53 deletions
@@ -3,18 +3,19 @@
#include "LumiCharacter.h" #include "LumiCharacter.h"
#include "Engine/LocalPlayer.h"
#include "Engine/UserInterfaceSettings.h"
#include "Camera/CameraComponent.h" #include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h" #include "Components/CapsuleComponent.h"
#include "GameFramework/SpringArmComponent.h" #include "Engine/LocalPlayer.h"
#include "GameFramework/Controller.h" #include "Engine/UserInterfaceSettings.h"
#include "InputActionValue.h"
#include "GameFramework/CharacterMovementComponent.h" #include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/Controller.h"
#include "GameFramework/SpringArmComponent.h"
#include "InputActionValue.h"
#include "WizardingCentral/Logger/LogLumiCharacter.h"
#include "WizardingCentral/Utils/OneDollar/UniStrokeDataTable.h" #include "WizardingCentral/Utils/OneDollar/UniStrokeDataTable.h"
#include "WizardingCentral/Utils/OneDollar/UniStrokeResult.h" #include "WizardingCentral/Utils/OneDollar/UniStrokeResult.h"
DEFINE_LOG_CATEGORY(LogLumiCharacter); static const FString SpellTemplatesTablePath = TEXT("/Game/DataTables/SpellTemplates.SpellTemplates");
// Sets default values // Sets default values
ALumiCharacter::ALumiCharacter() ALumiCharacter::ALumiCharacter()
@@ -49,7 +50,16 @@ ALumiCharacter::ALumiCharacter()
// Spell System // Spell System
bIsDrawing = false; bIsDrawing = false;
SpellRecognizer = new FUniStrokeRecognizer(); SpellRecognizer = new FUniStrokeRecognizer();
if (SpellTemplatesTable) if (SpellTemplatesTable == nullptr)
{
SpellTemplatesTable = Cast<UDataTable>(
StaticLoadObject(
UDataTable::StaticClass(),
nullptr,
*SpellTemplatesTablePath
)
);
}
LoadSpellTemplates(); LoadSpellTemplates();
} }
@@ -123,7 +133,9 @@ void ALumiCharacter::OnJumpActionStarted(const FInputActionValue& Value)
Jump(); Jump();
} }
void ALumiCharacter::OnJumpActionOngoing(const FInputActionValue& Value) {} void ALumiCharacter::OnJumpActionOngoing(const FInputActionValue& Value)
{
}
void ALumiCharacter::OnJumpActionCompleted(const FInputActionValue& Value) void ALumiCharacter::OnJumpActionCompleted(const FInputActionValue& Value)
{ {
@@ -300,7 +312,7 @@ void ALumiCharacter::LoadSpellTemplates() const
void ALumiCharacter::CastSpell() void ALumiCharacter::CastSpell()
{ {
const TArray<FVector2D>* CurrentPoints = SpellOverlay->GetSpellPoints(); const TArray<FVector2D>* CurrentPoints = SpellOverlay->GetSpellPoints();
const FUniStrokeResult Result = SpellRecognizer->Recognize(*CurrentPoints, false); const FUniStrokeResult Result = SpellRecognizer->Recognize(*CurrentPoints);
bool bHasMatch = false; bool bHasMatch = false;
if (Result.Score < 0.8f) if (Result.Score < 0.8f)
@@ -15,8 +15,6 @@ class USpringArmComponent;
class UCameraComponent; class UCameraComponent;
struct FInputActionValue; struct FInputActionValue;
DECLARE_LOG_CATEGORY_EXTERN(LogLumiCharacter, Log, All);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FJumpDelegate); DECLARE_DYNAMIC_MULTICAST_DELEGATE(FJumpDelegate);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FStanceChangedDelegate, DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FStanceChangedDelegate,
@@ -145,13 +143,24 @@ public:
OnSpellActionCompleted(); OnSpellActionCompleted();
void void
OnSpellPositionActionTriggered(const FInputActionValue& Value); OnSpellPositionActionTriggered(const FInputActionValue& Value);
void
OnSpellTrainSwitchActionCompleted();
void OnSpellTrainSwitchActionCompleted(); UFUNCTION(BlueprintCallable, Category = "Spell")
void
SetIsTraining(const bool bTraining);
void SetIsTraining(const bool bTraining); UFUNCTION(BlueprintCallable, Category = "Spell")
void HideTrainWidget(); void
void ShowTrainWidget(); HideTrainWidget();
void AddSpellTemplateToTable(const FString& Name);
UFUNCTION(BlueprintCallable, Category = "Spell")
void
ShowTrainWidget();
UFUNCTION(BlueprintCallable, Category = "Spell")
void
AddSpellTemplateToTable(const FString& Name);
protected: protected:
/** /**
@@ -30,6 +30,11 @@ void ALumiController::GetLumiCharacter(ALumiCharacter*& OutLumiCharacter) const
OutLumiCharacter = LumiCharacter; OutLumiCharacter = LumiCharacter;
} }
ALumiCharacter* ALumiController::GetLumiCharacter() const
{
return LumiCharacter;
}
void ALumiController::OnPossess(APawn* InPawn) void ALumiController::OnPossess(APawn* InPawn)
{ {
Super::OnPossess(InPawn); Super::OnPossess(InPawn);
@@ -99,8 +104,8 @@ void ALumiController::OnPossess(APawn* InPawn)
{ {
EnhancedInputComponent->BindAction(SpellAction, EnhancedInputComponent->BindAction(SpellAction,
ETriggerEvent::Started, ETriggerEvent::Started,
LumiCharacter, this,
&ALumiCharacter::OnSpellActionStarted); &ALumiController::OnSpellActionStarted);
EnhancedInputComponent->BindAction(SpellAction, EnhancedInputComponent->BindAction(SpellAction,
ETriggerEvent::Completed, ETriggerEvent::Completed,
this, this,
@@ -128,12 +133,12 @@ void ALumiController::OnUnPossess()
void ALumiController::OnSpellActionStarted() void ALumiController::OnSpellActionStarted()
{ {
ShowMouseCursor(); // ShowMouseCursor();
LumiCharacter->OnSpellActionStarted(); LumiCharacter->OnSpellActionStarted();
} }
void ALumiController::OnSpellActionCompleted() void ALumiController::OnSpellActionCompleted()
{ {
LumiCharacter->OnSpellActionCompleted(); LumiCharacter->OnSpellActionCompleted();
HideMouseCursor(); // HideMouseCursor();
} }
@@ -60,6 +60,10 @@ public:
void void
GetLumiCharacter(ALumiCharacter*& OutLumiCharacter) const; GetLumiCharacter(ALumiCharacter*& OutLumiCharacter) const;
UFUNCTION(BlueprintCallable, Category = "LumiController")
ALumiCharacter*
GetLumiCharacter() const;
protected: protected:
virtual void virtual void
OnPossess(APawn* InPawn) override; OnPossess(APawn* InPawn) override;
@@ -0,0 +1,3 @@
#pragma once
DECLARE_LOG_CATEGORY_EXTERN(LogLumiCharacter, Log, All);
@@ -1,6 +1,6 @@
#include "UniStrokePoint.h" #include "UniStrokePoint.h"
#include "ScreenPass.h" #include "WizardingCentral/Logger/LogLumiCharacter.h"
FUniStrokePoint::FUniStrokePoint() FUniStrokePoint::FUniStrokePoint()
{ {
@@ -49,9 +49,7 @@ FUniStrokePoint::BoundingBox(const TArray<FUniStrokePoint>& Points)
{ {
// Edge case // Edge case
if (Points.Num() == 0) if (Points.Num() == 0)
{
return FUniStrokeRectangle(); return FUniStrokeRectangle();
}
float MinX = Points[0].X; float MinX = Points[0].X;
float MinY = Points[0].Y; float MinY = Points[0].Y;
@@ -86,14 +84,16 @@ FUniStrokePoint::Vectorize(const TArray<FUniStrokePoint>& Points)
const float Magnitude = FMath::Sqrt(Sum); const float Magnitude = FMath::Sqrt(Sum);
for (float& V : Vector) for (float& V : Vector)
{
V /= Magnitude; V /= Magnitude;
}
return Vector; return Vector;
} }
// TODO: Proofread this function FString FUniStrokePoint::ToString() const
{
return FString::Printf(TEXT("X=%.3f Y=%.3f"), X, Y);
}
float float
FUniStrokePoint::OptimalCosineDistance(const TArray<float>& VectorA, FUniStrokePoint::OptimalCosineDistance(const TArray<float>& VectorA,
const TArray<float>& VectorB) const TArray<float>& VectorB)
@@ -173,10 +173,8 @@ FUniStrokePoint::Resample(TArray<FUniStrokePoint>& Points,
// Edge case // Edge case
if (Points.Num() == Num - 1) if (Points.Num() == Num - 1)
{
Points.Add(OldPoints.Last()); Points.Add(OldPoints.Last());
} }
}
float float
FUniStrokePoint::PathLength(const TArray<FUniStrokePoint>& Points) FUniStrokePoint::PathLength(const TArray<FUniStrokePoint>& Points)
@@ -385,9 +383,7 @@ FUniStrokePoint::PathDistance(const TArray<FUniStrokePoint>& PathA,
{ {
// Edge case: Different number of points // Edge case: Different number of points
if (PathA.Num() != PathB.Num()) if (PathA.Num() != PathB.Num())
{
return TNumericLimits<float>::Max(); return TNumericLimits<float>::Max();
}
// d <- 0 // d <- 0
float d = 0; float d = 0;
@@ -84,6 +84,9 @@ struct WIZARDINGCENTRAL_API FUniStrokePoint
static TArray<float> static TArray<float>
Vectorize(const TArray<FUniStrokePoint>& Points); Vectorize(const TArray<FUniStrokePoint>& Points);
FString
ToString() const;
private: private:
float X; float X;
float Y; float Y;
@@ -12,16 +12,13 @@ FUniStrokeRecognizer::~FUniStrokeRecognizer()
// TODO: Review this function // TODO: Review this function
FUniStrokeResult FUniStrokeResult
FUniStrokeRecognizer::Recognize(const TArray<FVector2D>& VectorPoints, FUniStrokeRecognizer::Recognize(const TArray<FVector2D>& VectorPoints)
const bool& UseProtractor)
{ {
TArray<FUniStrokePoint> Points = FUniStrokePoint::From(VectorPoints); TArray<FUniStrokePoint> Points = FUniStrokePoint::From(VectorPoints);
// Edge case: Not enough points // Edge case: Not enough points
if (Points.Num() < 2 || FUniStrokePoint::PathLength(Points) < 100.0f) if (Points.Num() < 2 || FUniStrokePoint::PathLength(Points) < 100.0f)
{
return FUniStrokeResult("Too few points made", 0.0); return FUniStrokeResult("Too few points made", 0.0);
}
const FUniStrokeTemplate Candidate = FUniStrokeTemplate("", Points); const FUniStrokeTemplate Candidate = FUniStrokeTemplate("", Points);
int TemplateIndex = -1; int TemplateIndex = -1;
@@ -33,12 +30,7 @@ FUniStrokeRecognizer::Recognize(const TArray<FVector2D>& VectorPoints,
for (int i = 0; i < Templates.Num(); i++) for (int i = 0; i < Templates.Num(); i++)
{ {
// d <- DistanceAtBestAngle(points, T, -theta, theta, threshold) // d <- DistanceAtBestAngle(points, T, -theta, theta, threshold)
const float d = UseProtractor const float d = FUniStrokePoint::DistanceAtBestAngle(
? FUniStrokePoint::OptimalCosineDistance(
Templates[i].Vector,
Candidate.Vector
)
: FUniStrokePoint::DistanceAtBestAngle(
Candidate.Points, Candidate.Points,
Templates[i].Points, Templates[i].Points,
-AngleRange, -AngleRange,
@@ -57,7 +49,8 @@ FUniStrokeRecognizer::Recognize(const TArray<FVector2D>& VectorPoints,
} }
// score <- 1 b / 0.5 sqrt(size^2 + size^2) // score <- 1 b / 0.5 sqrt(size^2 + size^2)
const float Score = UseProtractor ? 1.0 - b : 1.0 - b / HalfDiagonal; static const float HalfDiagonal = 0.5 * FMath::Sqrt(2 * FMath::Square(SquareSize));
const float Score = 1.0 - b / HalfDiagonal;
// return <T', score> // return <T', score>
return TemplateIndex == -1 return TemplateIndex == -1
@@ -8,8 +8,6 @@
#include "UniStrokeRecognizer.generated.h" #include "UniStrokeRecognizer.generated.h"
static constexpr int NumTemplates = 16; static constexpr int NumTemplates = 16;
static const float Diagonal = FMath::Sqrt(2 * FMath::Square(SquareSize));
static const float HalfDiagonal = 0.5 * Diagonal;
static constexpr float AngleRange = FMath::DegreesToRadians(45.0); static constexpr float AngleRange = FMath::DegreesToRadians(45.0);
static constexpr float AnglePrecision = FMath::DegreesToRadians(2.0); static constexpr float AnglePrecision = FMath::DegreesToRadians(2.0);
@@ -22,7 +20,7 @@ struct WIZARDINGCENTRAL_API FUniStrokeRecognizer
~FUniStrokeRecognizer(); ~FUniStrokeRecognizer();
FUniStrokeResult FUniStrokeResult
Recognize(const TArray<FVector2D>& VectorPoints, const bool& UseProtractor); Recognize(const TArray<FVector2D>& VectorPoints);
void void
AddTemplate(const FString& Name, const TArray<FVector2D>& VectorPoints); AddTemplate(const FString& Name, const TArray<FVector2D>& VectorPoints);
@@ -8,7 +8,7 @@ FUniStrokeTemplate::FUniStrokeTemplate()
} }
FUniStrokeTemplate::FUniStrokeTemplate(const FString& Name, FUniStrokeTemplate::FUniStrokeTemplate(const FString& Name,
const TArray<FUniStrokePoint>) const TArray<FUniStrokePoint>& Points)
{ {
this->Name = Name; this->Name = Name;
this->Points = Points; this->Points = Points;
@@ -16,7 +16,7 @@ struct WIZARDINGCENTRAL_API FUniStrokeTemplate
FUniStrokeTemplate(); FUniStrokeTemplate();
FUniStrokeTemplate(const FString& Name, FUniStrokeTemplate(const FString& Name,
const TArray<FUniStrokePoint>); const TArray<FUniStrokePoint>& Points);
~FUniStrokeTemplate(); ~FUniStrokeTemplate();
FString Name; FString Name;