// Fill out your copyright notice in the Description page of Project Settings.


#include "CharacterPreviewActor.h"

#include "Camera/CameraComponent.h"
#include "CharacterAppearanceComponent.h"
#include "Components/SkeletalMeshComponent.h"

ACharacterPreviewActor::ACharacterPreviewActor()
{
	// Allume par SetFraming et eteint des que la camera est arrivee. Un
	// mannequin immobile dans un menu n'a aucune raison de couter une frame.
	PrimaryActorTick.bCanEverTick = true;
	PrimaryActorTick.bStartWithTickEnabled = false;

	USceneComponent* SceneRoot = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
	SetRootComponent(SceneRoot);

	TurntablePivot = CreateDefaultSubobject<USceneComponent>(TEXT("TurntablePivot"));
	TurntablePivot->SetupAttachment(SceneRoot);

	BodyMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("BodyMesh"));
	BodyMesh->SetupAttachment(TurntablePivot);
	// Aucune collision : c'est un decor de menu, rien ne doit le heurter ni le
	// tracer.
	BodyMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);

	// Les reperes de cadrage sont filles de la RACINE et pas du pivot : ils ne
	// doivent surtout pas tourner avec le personnage, sinon la camera suivrait
	// le clic-glisser au lieu de le montrer.
	FullBodyViewSpot = CreateDefaultSubobject<USceneComponent>(TEXT("FullBodyViewSpot"));
	FullBodyViewSpot->SetupAttachment(SceneRoot);
	FullBodyViewSpot->SetRelativeLocation(FVector(260.f, 60.f, 95.f));
	FullBodyViewSpot->SetRelativeRotation(FRotator(0.f, 180.f, 0.f));

	FaceViewSpot = CreateDefaultSubobject<USceneComponent>(TEXT("FaceViewSpot"));
	FaceViewSpot->SetupAttachment(SceneRoot);
	FaceViewSpot->SetRelativeLocation(FVector(90.f, 25.f, 165.f));
	FaceViewSpot->SetRelativeRotation(FRotator(0.f, 180.f, 0.f));

	PreviewCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("PreviewCamera"));
	PreviewCamera->SetupAttachment(SceneRoot);

	AppearanceComponent = CreateDefaultSubobject<UCharacterAppearanceComponent>(TEXT("AppearanceComponent"));
}

void ACharacterPreviewActor::BeginPlay()
{
	Super::BeginPlay();

	// Le composant cherche le mesh d'un ACharacter par defaut ; ici il n'y en a
	// pas, on lui designe le notre.
	if (AppearanceComponent)
	{
		AppearanceComponent->SetTargetMesh(BodyMesh);
	}

	// Pose la camera sur le cadrage de depart sans interpolation : glisser
	// depuis l'origine de l'acteur au premier affichage montrerait un travelling
	// que personne n'a demande.
	if (const USceneComponent* Spot = GetSpotForFraming(CurrentFraming))
	{
		PreviewCamera->SetRelativeLocationAndRotation(Spot->GetRelativeLocation(), Spot->GetRelativeRotation());
	}
}

void ACharacterPreviewActor::Tick(float DeltaSeconds)
{
	Super::Tick(DeltaSeconds);

	UpdateFramingInterpolation(DeltaSeconds);
}

void ACharacterPreviewActor::ApplyAppearance(const FCharacterAppearance& InAppearance)
{
	if (AppearanceComponent)
	{
		AppearanceComponent->ApplyAppearance(InAppearance);
	}
}

void ACharacterPreviewActor::SetFraming(ECharacterPreviewFraming NewFraming)
{
	if (CurrentFraming == NewFraming)
	{
		return;
	}

	CurrentFraming = NewFraming;
	SetActorTickEnabled(true);
}

void ACharacterPreviewActor::AddPreviewYaw(float DeltaDegrees)
{
	TurntableYaw = FRotator::NormalizeAxis(TurntableYaw + DeltaDegrees);

	if (TurntablePivot)
	{
		TurntablePivot->SetRelativeRotation(FRotator(0.f, TurntableYaw, 0.f));
	}
}

void ACharacterPreviewActor::ResetPreviewYaw()
{
	TurntableYaw = 0.f;

	if (TurntablePivot)
	{
		TurntablePivot->SetRelativeRotation(FRotator::ZeroRotator);
	}
}

USceneComponent* ACharacterPreviewActor::GetSpotForFraming(ECharacterPreviewFraming InFraming) const
{
	return (InFraming == ECharacterPreviewFraming::Face) ? FaceViewSpot : FullBodyViewSpot;
}

void ACharacterPreviewActor::UpdateFramingInterpolation(float DeltaSeconds)
{
	const USceneComponent* Spot = GetSpotForFraming(CurrentFraming);
	if (!Spot || !PreviewCamera)
	{
		SetActorTickEnabled(false);
		return;
	}

	const FVector TargetLocation = Spot->GetRelativeLocation();
	const FRotator TargetRotation = Spot->GetRelativeRotation();

	const FVector NewLocation = FMath::VInterpTo(
		PreviewCamera->GetRelativeLocation(), TargetLocation, DeltaSeconds, FramingInterpSpeed);
	const FRotator NewRotation = FMath::RInterpTo(
		PreviewCamera->GetRelativeRotation(), TargetRotation, DeltaSeconds, FramingInterpSpeed);

	PreviewCamera->SetRelativeLocationAndRotation(NewLocation, NewRotation);

	// Un interpolateur exponentiel n'atteint jamais sa cible exactement : sans
	// ce seuil, le Tick ne s'eteindrait plus jamais. On termine le dernier
	// millimetre d'un coup, il ne se voit pas.
	const bool bLocationReached = NewLocation.Equals(TargetLocation, 0.5f);
	const bool bRotationReached = NewRotation.Equals(TargetRotation, 0.5f);

	if (bLocationReached && bRotationReached)
	{
		PreviewCamera->SetRelativeLocationAndRotation(TargetLocation, TargetRotation);
		SetActorTickEnabled(false);
	}
}
