//===========================================================================//
// File:	audloc.cpp                                                          //
// Project: MUNGA           Brick: Audio manager									  //
// Contents: 																					  //
//---------------------------------------------------------------------------//
//   Date    Who  Modification                                               //
// --------  ---  ---------------------------------------------------------- //
// 01/30/95  ECH  Initial coding.                                            //
//---------------------------------------------------------------------------//
// Copyright (C) 1994-1995, Virtual World Entertainment, Inc.                //
// All Rights reserved worldwide                                             //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL               //
//===========================================================================//

#include <munga.hpp>
#pragma hdrstop

#if !defined(AUDLOC_HPP)
#	include <audloc.hpp>
#endif

#if !defined(OBJSTRM_HPP)
	#include <objstrm.hpp>
#endif

#if !defined(NAMELIST_HPP)
	#include <namelist.hpp>
#endif

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ AudioLocation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//
//#############################################################################
//#############################################################################
//
AudioLocation::AudioLocation(
	PlugStream *stream,
	Entity *entity
):
	AudioComponent(stream),
	vectorToSource(0.0f, 0.0f, 0.0f),
	locationOffset(0.0f, 0.0f, 0.0f)
{
	AudioFrameCount next_update_delay;
	Scalar clipping_scale;

	MemoryStream_Read(stream, &locationOffset);
	MemoryStream_Read(stream, &next_update_delay);
	MemoryStream_Read(stream, &clipping_scale);

	AudioLocationX(entity, next_update_delay, clipping_scale);
}

//
//#############################################################################
//#############################################################################
//
AudioLocation::~AudioLocation()
{
}

//
//#############################################################################
//#############################################################################
//
void
	AudioLocation::AudioLocationX(
		Entity *entity,
		AudioFrameCount next_update_delay,
		Scalar clipping_scale
	)
{
	Check(entity);
	linkedEntity = entity;
	entity->AddAudioComponent(this);

	clippingScale = clipping_scale;
	isHeadSource = False;
	distanceToSource = 1.0f;
	dopplerCents = 0;
	#if 0
	angleOffOrientation = 0.0f;
	#endif
	azimuthOfSource = 0.0f;
	distanceVolumeScale = 1.0f;
	highFreqCutoffScale = 1.0f;
	#if 0
	reverbVolumeScale = 0.0f;
	#endif
	nextUpdateDelay = next_update_delay;
   nextUpdateFrame = NullAudioFrameCount;
}

//
//#############################################################################
//#############################################################################
//
Logical
	AudioLocation::TestInstance() const
{
	Component::TestInstance();
	return True;
}

//
//#############################################################################
//#############################################################################
//
void
	AudioLocation::BuildFromPage(
		PlugStream *stream,
		NameList *name_list,
		ClassID class_ID,
		ObjectID object_ID
	)
{
	AudioComponent::BuildFromPage(stream, name_list, class_ID, object_ID);

	MEM_STRM_WRITE_ENTRY(*stream, name_list, Point3D, location_offset);

	//
	// Read update delay
	//
	CString update_delay_string("update_delay");
	AudioFrameCount update_delay = DefaultAudioFrameDelay;

	if (name_list->FindData(update_delay_string) != NULL)
	{
		Check_Pointer(name_list->FindData(update_delay_string));
		Convert_From_Ascii(
			(const char *)name_list->FindData(update_delay_string), 
			&update_delay
		);
	}
	MemoryStream_Write(stream, &update_delay);

	//
	// Read culling scale
	//
	CString clipping_scale_string("clipping_scale");
	Scalar clipping_scale = 1.0f;

	if (name_list->FindData(clipping_scale_string) != NULL)
	{
		Check_Pointer(name_list->FindData(clipping_scale_string));
		Convert_From_Ascii(
			(const char *)name_list->FindData(clipping_scale_string), 
			&clipping_scale
		);
	}
	MemoryStream_Write(stream, &clipping_scale);
}

//
//#############################################################################
//#############################################################################
//
void
	AudioLocation::ReceiveControl(
		AudioControlID,
		AudioControlValue
	)
{
}

//
//#############################################################################
//#############################################################################
//
Logical
	AudioLocation::IsAudioLocationClipped(AudioHead *audio_head)
{
	Check(this);
	Check(audio_head);

	//
	// HACK - This is a rough approximation in that it does not
	// take into account the offset of the location from the
	// center of the entity.  But, this is more efficient and is OK
	// as long as the clipping sphere does not exclude the location
	// unnaturally
	//
	Entity 
		*head_entity;
	Vector3D
		difference;
	Scalar
		scaled_radius;
	
	head_entity = audio_head->GetHeadEntity();
	Check(head_entity);
	Check(linkedEntity);
	difference.Subtract(
		head_entity->localOrigin.linearPosition, 
		linkedEntity->localOrigin.linearPosition
	);		
	scaled_radius = audio_head->GetClippingRadius() * clippingScale;
	return difference.LengthSquared() > scaled_radius * scaled_radius;	
}

//
//#############################################################################
//#############################################################################
//
void
	AudioLocation::UpdateSpatialModelImplementation(AudioHead *audio_head)
{
	Check(this);
	Check(audio_head);

	//
	//--------------------------------------------------------------------------
	// Get head and source entity
	//--------------------------------------------------------------------------
	//
	Entity *source_entity = GetLinkedEntity();
	Entity *head_entity = audio_head->GetHeadEntity();

	Check(source_entity);
	Check(head_entity);

	//
	//--------------------------------------------------------------------------
	// Catch case of head == source at origin
	//--------------------------------------------------------------------------
	//
	if (
		head_entity == source_entity &&
		locationOffset == Vector3D::Identity
	)
	{
		isHeadSource = True;
		vectorToSource = Vector3D::Identity;
		distanceToSource = 0.0f;
		dopplerCents = 0;
		#if 0
		angleOffOrientation = 0.0f;
		#endif
		azimuthOfSource = 0.0f;
		distanceVolumeScale = 1.0f;
		highFreqCutoffScale = 1.0f;
		#if 0
		reverbVolumeScale = audio_head->GetReverbToDryRatio();
		#endif
		return;
	}
	isHeadSource = False;

	//
	//--------------------------------------------------------------------------
	// Get the ear to world linear matrix
	//--------------------------------------------------------------------------
	//
	LinearMatrix ear_to_world = audio_head->GetEarToWorld();

	//
	//--------------------------------------------------------------------------
	// Calculate vector to source with respect to local coordinate system
	// of the ears
	//--------------------------------------------------------------------------
	//
	{
		//
		// Get the source location offset from the entity position in
		// world coordinates
		//
		Vector3D world_location_offset;
		Vector3D world_source_location;

		world_location_offset.Multiply(
			locationOffset,
			source_entity->localToWorld
		);
		Check(&world_location_offset);

		world_source_location.Add(
			source_entity->localOrigin.linearPosition,
			world_location_offset
		);
		Check(&world_source_location);

		//
		// Get the vector from the ear to the source, note that the vector
		// is not calculated by the ears offset but from the entities location
		//
		Vector3D world_ear_to_source_vector;

		world_ear_to_source_vector.Subtract(
			world_source_location,
			head_entity->localOrigin.linearPosition
		);
		Check(&world_ear_to_source_vector);

		//
		// Transform the vector into ear coordinates
		//
		vectorToSource.MultiplyByInverse(
			world_ear_to_source_vector,
			ear_to_world
		);
		Check(&vectorToSource);
	}

	//
	//--------------------------------------------------------------------------
	// Calculate distance to source
	//--------------------------------------------------------------------------
	//
	distanceToSource = vectorToSource.Length();

	//
	//--------------------------------------------------------------------------
	// Calculate normal to source
	//--------------------------------------------------------------------------
	//
	Vector3D normal_to_source(0.0f, 0.0f, -1.0f);

	if (!Small_Enough(distanceToSource))
	{
		normal_to_source.Normalize(vectorToSource);
		Check(&normal_to_source);
	}

	#if 0
	//
	//--------------------------------------------------------------------------
	// Calculate angle between the orientation of the head and source
	//--------------------------------------------------------------------------
	//
	if (Small_Enough(distanceToSource))
	{
		angleOffOrientation = 0.0f;
	}
	else
	{
		UnitVector	direction;
		Scalar		cosine;

		ear_to_world.GetToAxis(Z_Axis, &direction);
		Check(&direction);

		Check(&normal_to_source);
		cosine = normal_to_source * direction;
		Clamp(cosine, -1.0f, 1.0f);
		angleOffOrientation = Arccos(cosine);
		angleOffOrientation.Normalize();
	}
	Verify(
		angleOffOrientation <= 180.0f*RAD_PER_DEG &&
		angleOffOrientation >= -180.0f*RAD_PER_DEG
	);
	#endif

	//
	//--------------------------------------------------------------------------
	// Calculate azimuth
	//--------------------------------------------------------------------------
	//
	if (Small_Enough(vectorToSource.x) && Small_Enough(vectorToSource.z))
	{
		azimuthOfSource = 0.0f;
	}
	else
	{
		Verify(!(Small_Enough(vectorToSource.x) && Small_Enough(vectorToSource.z)));
		azimuthOfSource = Arctan(vectorToSource.x, vectorToSource.z);
	}
	Verify(
		azimuthOfSource <= 180.0f*RAD_PER_DEG &&
		azimuthOfSource >= -180.0f*RAD_PER_DEG
	);

	//
	//--------------------------------------------------------------------------
	// Calculate distance volume scaling
	// Apply clipping scale to rolloff distance scale, thereby giving this 
	// audio location a specifc rolloff characteristic
	//--------------------------------------------------------------------------
	//
	if (distanceToSource > audio_head->GetAmplitudeRollOffKnee())
	{
		//
		// y = 1 / 1 + (K(x - knee))^exp
		//
		Verify(!Small_Enough(clippingScale));
		const Scalar temp =
			(audio_head->GetAmplitudeRollOffDistanceScale() / clippingScale) *
				(distanceToSource - audio_head->GetAmplitudeRollOffKnee());
		const Scalar temp2 =
			1.0f + pow(temp, audio_head->GetAmplitudeRollOffExponent());

		Verify(!Small_Enough(temp2));
		distanceVolumeScale = 1.0f / temp2;
	}
	else
	{
		distanceVolumeScale = 1.0f;
	}
	Verify(distanceVolumeScale >= 0.0f && distanceVolumeScale <= 1.0f);

	//
	//--------------------------------------------------------------------------
	// Calculate high frequency cutoff scaling
	// Apply clipping scale to high frequency rolloff distance scale, thereby 
	// giving this audio location a specifc rolloff characteristic
	//--------------------------------------------------------------------------
	//
	if (distanceToSource > audio_head->GetHighFrequencyRollOffKnee())
	{
		//
		// y = 1 / 1 + (K(x - knee))^exp
		//
		Verify(!Small_Enough(clippingScale));
		const Scalar temp =
			(audio_head->GetHighFrequencyRollOffDistanceScale() / clippingScale) *
				(distanceToSource - audio_head->GetHighFrequencyRollOffKnee());
		const Scalar temp2 =
			1.0f + pow(temp, audio_head->GetHighFrequencyRollOffExponent());

		Verify(!Small_Enough(temp2));
		highFreqCutoffScale = 1.0f / temp2;
	}
	else
	{
		highFreqCutoffScale = 1.0f;
	}
	Verify(highFreqCutoffScale >= 0.0f && highFreqCutoffScale <= 1.0f);

	#if 0
	//
	//--------------------------------------------------------------------------
	// Calculate reverb volume scaling
	//--------------------------------------------------------------------------
	//
	if (distanceToSource > audio_head->GetAmplitudeRollOffKnee())
	{
		const Scalar temp =
			distanceToSource - audio_head->GetAmplitudeRollOffKnee() + 1.0f;

		reverbVolumeScale =
			audio_head->GetReverbToDryRatio() *
				pow(temp, audio_head->GetAmplitudeRollOffExponent());
		if (reverbVolumeScale > 1.0f)
			reverbVolumeScale = 1.0f;
	}
	else
	{
		reverbVolumeScale = audio_head->GetReverbToDryRatio();
	}
	Verify(reverbVolumeScale >= 0.0f && reverbVolumeScale <= 1.0f);
	#endif

	//
	//--------------------------------------------------------------------------
	// Calculate relative velocity of source
	//--------------------------------------------------------------------------
	//
	Vector3D relative_velocity_temp;
	Vector3D relative_velocity;

	relative_velocity_temp.Subtract(
		source_entity->GetWorldLinearVelocity(),
		head_entity->GetWorldLinearVelocity()
	);
	Check(&relative_velocity_temp);

	relative_velocity.MultiplyByInverse(
		relative_velocity_temp,
		head_entity->localToWorld
	);
	Check(&relative_velocity);

	//
	//--------------------------------------------------------------------------
	// Calculate doppler shift in cents
	// cents = (c / (c - v)) / ScaleRatio
	//--------------------------------------------------------------------------
	//
	if (!Small_Enough(distanceToSource))
	{
		AudioPitchCents
			cents;
		Scalar speed_of_source =
			normal_to_source * relative_velocity;
		const Scalar ear_radius =
			audio_head->GetDistanceBetweenEars() * 0.5f;

		if (distanceToSource < ear_radius)
		{
			Verify(!Small_Enough(ear_radius));
			speed_of_source *= (distanceToSource / ear_radius);
		}

		if (CalculateDoppler(audio_head, speed_of_source, &cents))
		{
			dopplerCents = cents;
		}
	}
	else
	{
		dopplerCents = 0;
	}
}

//
//#############################################################################
//#############################################################################
//
Logical
	AudioLocation::CalculateDoppler(
		AudioHead *audio_head,
		Scalar speed,
		AudioPitchCents *result
	)
{
	Check(this);
	Check(audio_head);
	Check_Pointer(result);

	Scalar speed_of_sound = audio_head->GetAudioSoundSpeed();

	Clamp(speed, -speed_of_sound, speed_of_sound);
	if (!Small_Enough(speed_of_sound - speed))
	{
		*result = audio_head->GetAudioDopplerConstant() *
			( 1 - speed_of_sound / (speed_of_sound - speed) );
		return True;
	}
	return False;
}

