//
// Asura_Cutscene_Camera_Shake.cpp
//
// Created by KOR - 05/02/2008
// 
// Copyright (C) 2008 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

// This file's header.
#include "Cutscene/Asura_Cutscene_Camera_Shake.h"

// Asura
#include "ChunkSystem/Asura_Chunk_Macros.h"
#include "ChunkSystem/Asura_Chunk_Stream.h"
#include "SpecialFX/Asura_PerlinNoise_System.h"


Asura_Cutscene_Camera_Shake::Asura_Cutscene_Camera_Shake()
: m_fFrequency(4.f)
, m_fPitch(0.1f)
, m_fYaw(0.1f)
, m_fRoll(0.05f)
, m_fSpeedScale(1.f)
, m_fMinScale(0.2f)
, m_uFlags(0)
{

}

Asura_Cutscene_Camera_Shake& Asura_Cutscene_Camera_Shake::operator = (const Asura_Cutscene_Camera_Shake& xFrom)
{
	CopyFrom(xFrom);
	return *this;
}

void Asura_Cutscene_Camera_Shake::CopyFrom(const Asura_Cutscene_Camera_Shake& xFrom)
{
	m_fFrequency = xFrom.m_fFrequency;
	m_fPitch = xFrom.m_fPitch;
	m_fYaw = xFrom.m_fYaw;
	m_fRoll = xFrom.m_fRoll;
	m_fSpeedScale = xFrom.m_fSpeedScale;
	m_fMinScale = xFrom.m_fMinScale;
	m_uFlags = xFrom.m_uFlags;
}

void Asura_Cutscene_Camera_Shake::SaveToChunk(char*& pcChunkBuffer) const
{
	Asura_Chunk_ChunkBuffer_Stream xStream(pcChunkBuffer);
	WriteToChunkStream(xStream);
}

void Asura_Cutscene_Camera_Shake::LoadFromChunk(int iFileHandle)
{
	Asura_Chunk_File_Stream xStream(iFileHandle);
	ReadFromChunkStream(xStream);
}

void Asura_Cutscene_Camera_Shake::WriteToChunkStream(Asura_Chunk_Stream& xStream) const
{
	xStream << CHUNK_VERSION;
	Asura_WriteToStream_Bitfield_Wrapper<u_int> xFlags(m_uFlags);
	xFlags.Add(m_bEnabled, 1);
	xStream << xFlags;
	xStream << m_fFrequency;
	xStream << m_fPitch;
	xStream << m_fRoll;
	xStream << m_fYaw;
	xStream << m_fSpeedScale;
	xStream << m_fMinScale;
}

void Asura_Cutscene_Camera_Shake::ReadFromChunkStream(Asura_Chunk_Stream& xStream)
{
	u_int uVersion;
	xStream >> uVersion;
	if(uVersion < 2)
	{
		// Version is actually the enabled flag
		m_bEnabled = (uVersion != 0);
	}
	else
	{
		xStream >> m_uFlags;
	}

	xStream >> m_fFrequency;
	xStream >> m_fPitch;
	xStream >> m_fRoll;
	xStream >> m_fYaw;
	xStream >> m_fSpeedScale;
	xStream >> m_fMinScale;
}

void Asura_Cutscene_Camera_Shake::Lerp(const Asura_Cutscene_Camera_Shake& xInput1, const Asura_Cutscene_Camera_Shake& xInput2, float fLambda)
{
	Asura_Assert(0.f <= fLambda && fLambda <= 1.f, "Lambda for lerp out of range");
	const bool bInput1Enabled = xInput1.IsEnabled();
	const bool bInput2Enabled = xInput2.IsEnabled();
	if(bInput1Enabled)
	{
		if(bInput2Enabled)
		{
			// Both enabled, lerp between them
			m_fFrequency	= Asura_Maths::Lerp(xInput1.m_fFrequency,	xInput2.m_fFrequency,	fLambda);
			m_fPitch		= Asura_Maths::Lerp(xInput1.m_fPitch,		xInput2.m_fPitch,		fLambda);
			m_fYaw			= Asura_Maths::Lerp(xInput1.m_fYaw,			xInput2.m_fYaw,			fLambda);
			m_fRoll			= Asura_Maths::Lerp(xInput1.m_fRoll,		xInput2.m_fRoll,		fLambda);
			m_fSpeedScale	= Asura_Maths::Lerp(xInput1.m_fSpeedScale,	xInput2.m_fSpeedScale,	fLambda);
			m_fMinScale		= Asura_Maths::Lerp(xInput1.m_fMinScale,	xInput2.m_fMinScale,	fLambda);
			m_bEnabled = true;
		}
		else
		{
			// Just 1 enabled, scale away from it
			const float fOneMinusLambda = 1.f - fLambda;
			m_fPitch	= fOneMinusLambda * xInput1.m_fPitch;
			m_fYaw		= fOneMinusLambda * xInput1.m_fYaw;
			m_fRoll		= fOneMinusLambda * xInput1.m_fRoll;
			m_fFrequency	= xInput1.m_fFrequency;
			m_fSpeedScale	= xInput1.m_fSpeedScale;
			m_fMinScale		= xInput1.m_fMinScale;
			m_bEnabled = true;
		}
	}
	else if(bInput2Enabled)
	{
		// Just 2 enabled, scale towards it
		m_fPitch	= fLambda * xInput2.m_fPitch;
		m_fYaw		= fLambda * xInput2.m_fYaw;
		m_fRoll		= fLambda * xInput2.m_fRoll;
		m_fFrequency	= xInput2.m_fFrequency;
		m_fSpeedScale	= xInput2.m_fSpeedScale;
		m_fMinScale		= xInput2.m_fMinScale;
		m_bEnabled = true;
	}
	else
	{
		// Neither enabled
		m_bEnabled = false;
		// Other parameters don't matter.
	}
}

void Asura_Cutscene_Camera_Shake::ApplyShake(float fTime, float fSpeed, Asura_Matrix_3x3& xOrientation) const
{
	if(m_bEnabled)
	{
		fSpeed *= m_fSpeedScale;
		if(fSpeed > 1.0f) fSpeed=1.0f;
		if(fSpeed < m_fMinScale) fSpeed=m_fMinScale;

		// Get camera vectors
		const Asura_Vector_3 xXVec(xOrientation.e11, xOrientation.e21, xOrientation.e31);
		const Asura_Vector_3 xYVec(xOrientation.e12, xOrientation.e22, xOrientation.e32);
		const Asura_Vector_3 xZVec(xOrientation.e13, xOrientation.e23, xOrientation.e33);

		// Perlin noise system repeats after 16 units, so pick three points that are as far apart as possible for each axis
		const float fJitterX = fSpeed * m_fPitch*Asura_PerlinNoise_System::GetNoise(fTime*m_fFrequency*fSpeed);
		const float fJitterY = fSpeed * m_fYaw*Asura_PerlinNoise_System::GetNoise(4.0f+(fTime*m_fFrequency*fSpeed));
		const float fJitterZ = fSpeed * m_fRoll*Asura_PerlinNoise_System::GetNoise(12.0f+(fTime*m_fFrequency*fSpeed));

		// Generate rotation matrix for desired yaw, pitch and roll
		const Asura_Euler_3 xJitter(fJitterX, fJitterY, fJitterZ);
		Asura_Matrix_3x3 xJitterMat;
		xJitter.ToMatrix(xJitterMat);
		xOrientation = xOrientation * xJitterMat;
	}
}
