// 
// audio/gtaaudioenvironment.cpp
// 
// Copyright (C) 1999-2006 Rockstar Games.  All Rights Reserved.
//  

#include "audio/ambience/ambientaudioentity.h"
#include "audio/cutsceneaudioentity.h"
#include "audio/debugaudio.h"
#include "audio/environment/environment.h"
#include "audio/environment/environmentgroup.h"
#include "audio/frontendaudioentity.h"
#include "audio/gameobjects.h"
#include "audio/northaudioengine.h"
#include "audio/radiostation.h"
#include "audio/scannermanager.h"
#include "audio/scriptaudioentity.h"
#include "audio/weatheraudioentity.h"
#include "audioeffecttypes/biquadfiltereffect.h"
#include "audioeffecttypes/delayeffect.h"
#include "audioeffecttypes/reverbeffect.h"
#include "audiosoundtypes/sounddefs.h"
#include "audioengine/engine.h"
#include "audioengine/engineutil.h"
#include "audioengine/environment.h"
#include "audiohardware/driverutil.h"
#include "audiohardware/submix.h"
#include "audwarpmanager.h"
#include "camera/CamInterface.h"
#include "camera/scripted/ScriptDirector.h"
#include "camera/viewports/ViewportManager.h"
#include "cutscene/CutSceneManagerNew.h"
#include "debug/debugglobals.h"
#include "debug/DebugScene.h"
#include "grcore/debugdraw.h"
#include "game/weather.h"
#include "game/zones.h"
#include "math/amath.h"
#include "modelinfo/MloModelInfo.h"
#include "objects/door.h"
#include "peds/PedIntelligence.h"
#include "peds/ped.h"
#include "peds/PopCycle.h"
#include "physics/WorldProbe/worldprobe.h"
#include "scene/portals/portal.h"
#include "scene/portals/portalInst.h"
#include "scene/WarpManager.h"
#include "scene/world/GameWorldWaterHeight.h"
#include "fwscene/search/Search.h"
#include "fwscene/search/SearchVolumes.h"
#include "fwscene/world/WorldRepMulti.h"
#include "fwsys/timer.h"
#include "task/Vehicle/TaskEnterVehicle.h"
#include "task/Vehicle/TaskExitVehicle.h"
#include "vector/geometry.h"
#include "vfx/Systems/VfxWeather.h"
#include "fwscene/world/WorldLimits.h"
#include "scene/world/GameWorldHeightMap.h"
#include "vectormath/vectormath.h"
#include "vehicleAi/pathfind.h"

AUDIO_ENVIRONMENT_OPTIMISATIONS()

// game-thread, gta-specific
f32 g_NumTreesForRain = 25.f;
f32 g_NumTreesForRainBuiltUp = 200.f;
f32 g_OverrideBuiltUpFactor = 0.5f;
f32 g_DeafeningEarLeak = 0.2f;
f32 g_DeafeningRolloff = 1.5f;
f32 g_MassiveDeafeningVolume = 16.0f;
f32 g_MinHeightForTree = 3.0f;
f32 g_MinBuildingHeight = 5.0f;
f32 g_MinBuildingFootprintArea = 1000.0f;
f32 g_MaxBuildingFootprintArea = 10000.0f;
f32 g_SourceEnvironmentRadius = 20.0f;
f32 g_OpenSpaceReverbSize = 0.3f;
f32 g_OpenSpaceReverbWet = 0.08f;
f32 g_SpeakerReverbSmootherRate = 0.001f;
f32 g_SpeakerReverbDamping = 0.525f;
f32 g_FitnessReduced = 0.01f;
f32 g_VolSmootherRate = 0.001f;
u32 g_DeafeningRampUpTime = 1500;
u32 g_DeafeningHoldTime = 1500;
u32 g_DeafeningRampDownTime = 4500;
u32 g_SpecialAbilitySlowMoTimeout = 250;

bool g_DebugListenerReverb = false;
bool g_ShouldOverrideBuiltUpFactor = false;

bool g_UseInteriorCarFilter = false;

bool g_EnableLocalEnvironmentMetrics = true;

u16 naEnvironment::sm_ObjOcclusionUpdateTime = 500;

bool naEnvironment::sm_ProcessSectors = false;

audCurve naEnvironment::sm_HDotToSlowmoResoVol;
audCurve naEnvironment::sm_VDotToSlowmoResoVol;
audCurve naEnvironment::sm_SpecialAbToFrequency;
audCurve naEnvironment::sm_CityGustDotToVolSpikeForBgTrees;	
audCurve naEnvironment::sm_CountrySideGustDotToVolSpikeForBgTrees;	

f32 naEnvironment::sm_SlowMoLFOFrequencyMin = 0.5f; 
f32 naEnvironment::sm_SlowMoLFOFrequencyMax = 2.f; 

f32 naEnvironment::sm_RadiusScale = 2.5f;
f32 naEnvironment::sm_RainOnPropsVolumeOffset = 0.f; 
u32 naEnvironment::sm_RainOnPopsPreDealy = 0; 
u32 naEnvironment::sm_MaxNumRainProps = 5; 
u32 naEnvironment::sm_MinWindObjTime = 15000; 
u32 naEnvironment::sm_MaxWindObjtTime = 25000; 
u32 naEnvironment::sm_WindTimeVariance = 10000; 
u32 naEnvironment::sm_MinTimeToRetrigger = 25000; 

u32 naEnvironment::sm_MinNumberOfTreesToTriggerGustEnd = 5;

#if __BANK
bool g_DrawWeaponEchoes = false;

bool g_audDrawLocalObjects = false;
bool g_audDrawShockwaves = false;
bool g_ForceResonanceTracksListener = false;
bool g_ForceRainLoopTracksListener = false;
bool g_ForceRainLoopPlayFromTopOfBounds = false;

f32 naEnvironment::sm_NumWaterProbeSets = 10.f;
bool naEnvironment::sm_UseCoverAproximation = false;
bool naEnvironment::sm_DebugCoveredObjects = false;
bool naEnvironment::sm_DebugGustRustle = false;
bool naEnvironment::sm_DebugGustWhistle = false;
bool naEnvironment::sm_DrawAudioWorldSectors = false; 
bool naEnvironment::sm_IsWaitingToInit = true;
bool naEnvironment::sm_LogicStarted = false;
bool naEnvironment::sm_DisplayResoIntensity = false;
bool naEnvironment::sm_DrawBgTreesInfo = false;
bool naEnvironment::sm_MuteBgFL = false;
bool naEnvironment::sm_MuteBgFR = false;
bool naEnvironment::sm_MuteBgRL = false;
bool naEnvironment::sm_MuteBgRR = false;
u32 naEnvironment::sm_CurrentSectorX = 0;
u32 naEnvironment::sm_CurrentSectorY = 0;
#endif
bank_bool g_UpdateBuiltUpFactor = false;

bool naEnvironment::ms_localObjectSearchLaunched = false;
fwSearch naEnvironment::ms_localObjectSearch(1900);

f32 naEnvironment::sm_HeliShockwaveRadius = 30.f;
f32 naEnvironment::sm_TrainDistanceThreshold = 300.f;
f32 naEnvironment::sm_TrainShockwaveRadius = 30.f;

f32 naEnvironment::sm_MeleeResoImpulse = 0.3f;
f32 naEnvironment::sm_PistolResoImpulse = 0.2f;
f32 naEnvironment::sm_RifleResoImpulse = 0.35f;
f32 naEnvironment::sm_WeaponsResonanceDistance = 15.f;
f32 naEnvironment::sm_ExplosionsResonanceImpulse = 0.2f;
f32 naEnvironment::sm_ExplosionsResonanceDistance = 100.f;
f32 naEnvironment::sm_VehicleCollisionResonanceImpulse = 0.05f;
f32 naEnvironment::sm_VehicleCollisionResonanceDistance = 5.f;
f32 naEnvironment::sm_DistancePlateau = 3.f;
// Echo metrics
f32 g_MaxEchoLength = 40.0f;
f32 g_MinEchoLength = 15.0f;
// BuiltUp stuff
f32 g_BuiltUpSmootherRate = 1.0f;
bool g_DebugBuiltUpInfo = false;
bool g_DisplayBuiltUpFactors = false;
bool g_UseProperBuiltUpFactor = true;

f32 g_NSEW_Offset = 0.49f;
f32 g_BuiltUpDampingFactorSmoothUpRate = 1.0f;
f32 g_BuiltUpDampingFactorSmoothDownRate = 1.0f;
f32 g_BuiltUpDampingFactorOverride = 1.0f;

f32 g_InCarRatioSmoothRate = 1.0f;
f32 g_InteriorRatioSmoothRate = 1.0f;

f32 g_windWhistleTreshold = 3.0f;

f32 naEnvironment::sm_SqdDistanceToPlayRainSounds = 225.f;
f32 naEnvironment::sm_BushesMinRadius = 1.5f; 
f32 naEnvironment::sm_SmallTreesMinRadius = 4.5f;
f32 naEnvironment::sm_BigTreesMinRadius = 10.f; 
f32 naEnvironment::sm_TreesSoundDist = 30.f; 
f32 naEnvironment::sm_BigTreesFitness = 1.f;
f32 naEnvironment::sm_TreesFitness = 0.6f;
f32 naEnvironment::sm_BushesFitness = 0.3f;
u8 naEnvironment::sm_NumTreesForAvgDist = 5;

#if 1 //!USE_AUDMESH_SECTORS
atRangeArray<audSector, g_audNumWorldSectors> g_AudioWorldSectors;
#endif

bool g_audDrawWorldSectorDebug = false;
bool g_audResetWorldSectorRoadInfo = false;

Vector3 g_Directions[4];


// Interior stuff
bool g_UseDebugRoomSettings = false;
bool g_TryRoomsEveryTime = false;
InteriorSettings* g_DebugRoomSettings;
f32 g_DebugRoomSettingsReverbSmall = 0.0f;
f32 g_DebugRoomSettingsReverbMedium = 0.0f;
f32 g_DebugRoomSettingsReverbLarge = 0.0f;
bool g_UsePlayer = true;

audCurve g_ShockwaveDistToVol;

char g_InteriorInfoDebug[64] = "";
char g_RoomInfoDebug[64] = "";

extern CPathFind ThePaths;
extern audScannerManager g_AudioScannerManager;

// only used on audio thread
PARAM(headphones, "[Audio] Mix for headphones.");
// only used on game thread
PARAM(echoes, "[Audio] Enables local environment scanning and turns echoes on.");

enum
{
	AUD_DEAFENING_OFF,
	AUD_DEAFENING_RAMP_UP,
	AUD_DEAFENING_HOLD,
	AUD_DEAFENING_RAMP_DOWN
};

naEnvironment::naEnvironment()
{
}

naEnvironment::~naEnvironment()
{
}

void naEnvironment::Init()
{
	g_Directions[0] = Vector3(0.0f, 1.0f, 0.0f); //N
	g_Directions[1] = Vector3(1.0f, 0.0f, 0.0f); //E
	g_Directions[2] = Vector3(0.0f, -1.0f, 0.0f); //S
	g_Directions[3] = Vector3(-1.0f, 0.0f, 0.0f); //W

	if(PARAM_echoes.Get())
	{
		g_EnableLocalEnvironmentMetrics = true;
	}
	
	m_BuiltUpToRolloff.Init(ATSTRINGHASH("BUILT_UP_TO_ROLLOFF", 0x7F6DB253));

	m_DeafeningState[0] = m_DeafeningState[1] = AUD_DEAFENING_OFF;
	m_DeafeningPhaseEndTime[0] = m_DeafeningPhaseEndTime[1] = 0;
	m_DeafeningMaxStrength[0] = m_DeafeningMaxStrength[1] = 0.0f;
	m_DeafeningMinStrength[0] = m_DeafeningMinStrength[1] = 0.0f;
	m_DeafeningAttenuationCurve.Init(ATSTRINGHASH("DEAFENING_STRENGTH_TO_ATTENUATION", 0x65D9D83E));
	m_DeafeningFilterCurve.Init(ATSTRINGHASH("DEAFENING_STRENGTH_TO_FILTER", 0xA8DE551F));
	m_DeafeningReverbCurve.Init(ATSTRINGHASH("DEAFENING_STRENGTH_TO_REVERB", 0x5D6B04BC));
	m_DeafeningVolumeToStrengthCurve.Init(ATSTRINGHASH("DEAFENING_VOLUME_TO_STRENGTH_CURVE", 0xE4309D3));
	m_DeafeningDistanceRolloffCurve.Init(ATSTRINGHASH("DEFAULT_ROLLOFF", 0x3BD35057));
	g_ShockwaveDistToVol.Init(ATSTRINGHASH("SHOCKWAVE_DIST_TO_VOL", 0x26002533));

	m_IsListenerInHead = false;
	m_ForceAmbientMetricsUpdate = false;

	g_DebugRoomSettings = audNorthAudioEngine::GetObject<InteriorSettings>(ATSTRINGHASH("DEBUG_INTERIOR", 0x044a9a7e2));

	ClearLinkedRooms();
	m_NumRainPropSounds = 0;
	m_EarStrength[0] = m_EarStrength[1] = 0.0f;

	for (s32 i=0; i<g_audNumLevelProbeRadials; i++)
	{
		m_LevelReflectionNormal[i].Zero();
	}

	for (s32 i=0; i<g_audNumMidProbeRadials; i++)
	{
		m_UpperReflectionNormal[i].Zero();
		m_EchoDistance[i] = 0.0f;
		m_EchoSuitability[i] = 0.0f;
		m_EchoPosition[i].Zero();
		m_EchoDirection[i].Set(1.0f);
	}
	
	m_ListenerInteriorInstance = NULL;
	m_InteriorRoomId = 0;
	m_ListenerInteriorSettings = NULL;
	m_ListenerInteriorRoom = NULL;

	m_DistanceToEchoCurve.Init(ATSTRINGHASH("DISTANCE_TO_ECHO_GOODNESS", 0x4883D0D7));
	m_SilentWhenEchoCoLocatedCurve.Init(ATSTRINGHASH("ECHO_COLOCATED_ATTENUATION", 0xDB040C16));
	m_EchoGoodnessLinearVolumeCurve.Init(ATSTRINGHASH("ECHO_GOODNESS_TO_LINEAR_VOLUME", 0x4D2F84CA));
	m_EchoGoodnessDbVolumeCurve.Init(ATSTRINGHASH("ECHO_GOODNESS_TO_DB_VOLUME", 0x59B4D084));
	m_DistanceToDelayCurve.Init(ATSTRINGHASH("ECHO_DISTANCE_TO_PREDELAY", 0xE42B577A));
	m_AngleOfIncidenceToSuitabilityCurve.Init(ATSTRINGHASH("ECHO_ANGLE_TO_SUITABILITY", 0x9972AF32));
	m_OutdoorInteriorWeaponVolCurve.Init(ATSTRINGHASH("REV_WET_TO_OUTDOOR_INTERIOR_WEAPON_VOLUME", 0xBD8ED687));
	m_OutdoorInteriorWeaponHoldCurve.Init(ATSTRINGHASH("REV_SIZE_TO_OUTDOOR_INTERIOR_WEAPON_HOLD", 0x6D07F377));

	BANK_ONLY(m_CoveredPolys.Reset());
	m_NumValidPolys = 0;
	for (u32 i=0; i<MAX_SURROUNDING_POLYS_FOR_AUDIO_PROPERTIES; i++)
	{
		m_SourceEnvironmentMetrics[i] = 0;
		m_SourceEnvironmentPolyAreas[i] = 0.0f;
		m_SourceEnvironmentPolyCentroids[i].Zero();
		for (u32 j=0; j<g_NumSourceReverbs; j++)
		{
			m_SourceEnvironmentPolyReverbWet[i][j] = 0.0f;
		}
	}
	m_ListenerPositionLastSourceEnvironmentUpdate.Zero();
	m_SourceEnvironmentTimeLastUpdated = 0;

	m_SourceEnvironmentPathHandle = PATH_HANDLE_NULL;

	m_SourceEnvironmentToReverbWetCurve.Init(ATSTRINGHASH("SOURCE_ENVIRONMENT_TO_REVERB_WET", 0x069607de3));
	m_SourceEnvironmentToReverbSizeCurve.Init(ATSTRINGHASH("SOURCE_ENVIRONMENT_TO_REVERB_SIZE", 0x0fd1b7244));
	m_SourceEnvironmentSizeToWetScaling.Init(ATSTRINGHASH("SOURCE_ENVIRONMENT_SIZE_TO_WET_SCALING", 0x033ef2c0b));

	m_DistanceToListenerReverbCurve.Init(ATSTRINGHASH("DISTANCE_TO_LISTENER_REVERB", 0x49BAD9E5));
	m_AreaToListenerReverbCurve.Init(ATSTRINGHASH("AREA_TO_LISTENER_REVERB", 0x2AD353B));
	for (u32 i=0; i<4; i++)
	{
		for (u32 j=0; j<3; j++)
		{
			m_SpeakerReverbWetSmoothers[i][j].Init(g_SpeakerReverbSmootherRate, true);
		}
	}
	//Prepare crossfade curves for source reverb submixes.
	for(u32 i=0; i<g_NumSourceReverbs; i++)
	{
		m_SourceReverbCrossfadeCurves[i].Init(g_SourceReverbCurveNames[i]);
	}

	m_NumBuildingAddsThisFrame = m_NumBuildingRemovesThisFrame = m_NumTreeAddsThisFrame = m_NumTreeRemovesThisFrame = 0;

	m_BuiltUpFactor = 0.0f;
	m_BuildingDensityFactor = 0.0f;
	m_TreeDensityFactor = 0.0f;
	m_WaterFactor = 0.0f;
	m_HighwayFactor = 0.0f;
	m_AveragePedZPos = 0.0f;
	m_PedFractionMale = 0.0f;

	m_WaterHeightAtListener = 0.f;

	m_HeightToBuiltUpFactor.Init(ATSTRINGHASH("HEIGHT_TO_BUILT_UP_FACTOR", 0x1ADE3D7F));
	m_BuildingNumToDensityFactor.Init(ATSTRINGHASH("BUILDING_COUNT_TO_DENSITY_FACTOR", 0x6E08E885));
	m_TreeNumToDensityFactor.Init(ATSTRINGHASH("TREE_COUNT_TO_DENSITY_FACTOR", 0x7DD816BE));
	m_WaterAmountToWaterFactor.Init(ATSTRINGHASH("WATER_AMOUNT_TO_WATER_FACTOR", 0xCD234F5D));
	m_HighwayAmountToHighwayFactor.Init(ATSTRINGHASH("HIGHWAY_AMOUNT_TO_HIGHWAY_FACTOR", 0x7446E468));
	m_VehicleAmountToVehicleDensity.Init(ATSTRINGHASH("VEHICLE_COUNT_TO_DENSITY_FACTOR", 0x4F5ECEE5));
	m_VehicleDistanceSqToVehicleAmount.Init(ATSTRINGHASH("VEHICLE_DISTANCE_SQ_TO_VEHICLE_AMOUNT", 0x81F1EBC2));
	m_VehicleSpeedToVehicleAmount.Init(ATSTRINGHASH("VEHICLE_SPEED_TO_VEHICLE_AMOUNT", 0xB181AEFB));

	m_BuiltUpSmoother.Init(g_BuiltUpSmootherRate, true);
	m_BuildingDensitySmoother.Init(g_BuiltUpSmootherRate, true);
	m_TreeDensitySmoother.Init(g_BuiltUpSmootherRate, true);
	m_WaterSmoother.Init(g_BuiltUpSmootherRate, true);
	m_HighwaySmoother.Init(g_BuiltUpSmootherRate, true);
	m_BuiltUpDampingFactorSmoother.Init(g_BuiltUpDampingFactorSmoothUpRate, g_BuiltUpDampingFactorSmoothDownRate, true);

	for (u32 i=0; i<AUD_NUM_SECTORS; i++)
	{
		m_BuildingHeights[i] = 0.0f;
		m_BuildingCount[i] = 0.0f;
		m_TreeCount[i] = 0.0f;
		m_BuiltUpFactorPerZone[i] = 0.0f;
	}
	for (u32 i=0; i<4; i++)
	{
		m_BuiltUpDirectionalSmoother[i].Init(g_BuiltUpSmootherRate, true);
		m_BuildingDensityDirectionalSmoother[i].Init(g_BuiltUpSmootherRate, true);
		m_TreeDensityDirectionalSmoother[i].Init(g_BuiltUpSmootherRate, true);
		m_WaterDirectionalSmoother[i].Init(g_BuiltUpSmootherRate, true);
		m_HighwayDirectionalSmoother[i].Init(g_BuiltUpSmootherRate, true);

		m_DirectionalBuiltUpFactor[i] = 0.0f;
		m_DirectionalBuildingDensity[i] = 0.0f;
		m_DirectionalTreeDensity[i] = 0.0f;
		m_DirectionalWaterFactor[i] = 0.0f;
		m_DirectionalHighwayFactor[i] = 0.0f;
	}
	for (u32 i=0; i<4; i++)
	{
		m_SpeakerBuiltUpSmoother[i].Init(g_BuiltUpSmootherRate, true);
		m_SpeakerBuiltUpFactors[i] = 0.0f;
		m_SpeakerBlockedFactor[i] = 0.0f;
		m_SpeakerBlockedLinearVolume[i] = 0.0f;
		m_SpeakerOWOFactor[i] = 0.0f;
	}

	m_CarInteriorFilterSmoother.Init(40.f,40.f,2000.f,24000.f);
	// On the first update it'll jump to the right spot and then start smoothing after that 
	m_CarInteriorFilterCutoff = 24000.f;

	m_PlayerInCarRatio = 0.0f;
	m_IsPlayerInVehicle = false;
	m_IsPlayerVehicleATrain = false;
	m_PlayerInCarSmoother.Init(g_InCarRatioSmoothRate/1000.0f, true);

	m_PlayerIsInASubwayStationOrSubwayTunnel = false;
	m_PlayerIsInASubwayOrTunnel = false;
	m_PlayerWasInASubwayOrTunnelLastFrame = false;
	m_PlayerIsInAnInterior = false;
	m_PlayerInteriorRatioSmoother.Init(g_InteriorRatioSmoothRate/1000.0f, true);
	m_PlayerInteriorRatio = 0.0f;
	m_RoomToneHash = g_NullSoundHash;
	m_RainType = RAIN_TYPE_NONE;

	m_NavmeshInformationUpdatedThisFrame = false;

	m_LastTimeWeWereAPassengerInATaxi = 0;

	for(u32 i = 0; i < g_MaxInterestingLocalObjects; i++)
	{
		m_InterestingLocalObjects[i].object = NULL;
		m_InterestingLocalObjects[i].envGroup = NULL;
		m_InterestingLocalObjects[i].rainSound = NULL;
		m_InterestingLocalObjects[i].windSound = NULL;
		m_InterestingLocalObjects[i].randomAmbientSound = NULL;
		m_InterestingLocalObjects[i].macsComponent = -1;
		m_InterestingLocalObjects[i].stillValid = false;
		m_InterestingLocalObjects[i].resoSoundInfo.resonanceSound = NULL;
		m_InterestingLocalObjects[i].resoSoundInfo.resonanceIntensity = 0.f;
		m_InterestingLocalObjects[i].resoSoundInfo.attackTime = 0.f;
		m_InterestingLocalObjects[i].resoSoundInfo.releaseTime = 0.f;
		m_InterestingLocalObjects[i].resoSoundInfo.resoSoundRef = g_NullSoundRef;
		m_InterestingLocalObjects[i].resoSoundInfo.slowmoResoVolLin = 0.f;
		m_InterestingLocalObjects[i].resoSoundInfo.slowmoResoPhase = 0.f;
		m_InterestingLocalObjects[i].shockwaveSoundInfo.attackTime = -1.f;
		m_InterestingLocalObjects[i].shockwaveSoundInfo.releaseTime = -1.f;
		m_InterestingLocalObjects[i].shockwaveSoundInfo.prevVol = 0.f;
		BANK_ONLY(m_InterestingLocalObjects[i].underCover = true);
		m_InterestingLocalObjectsCached[i].object = NULL;
		m_InterestingLocalObjectsCached[i].envGroup = NULL;
		m_InterestingLocalObjectsCached[i].rainSound = NULL;
		m_InterestingLocalObjectsCached[i].windSound = NULL;
		m_InterestingLocalObjectsCached[i].randomAmbientSound = NULL;
		m_InterestingLocalObjectsCached[i].macsComponent = -1;
		m_InterestingLocalObjectsCached[i].stillValid = false;
		m_InterestingLocalObjectsCached[i].resoSoundInfo.resonanceSound = NULL;
		m_InterestingLocalObjectsCached[i].resoSoundInfo.resonanceIntensity = 0.f;
		m_InterestingLocalObjectsCached[i].resoSoundInfo.attackTime = 0.f;
		m_InterestingLocalObjectsCached[i].resoSoundInfo.releaseTime = 0.f;
		m_InterestingLocalObjectsCached[i].resoSoundInfo.resoSoundRef = g_NullSoundRef;
		m_InterestingLocalObjectsCached[i].resoSoundInfo.slowmoResoVolLin = 0.f;
		m_InterestingLocalObjectsCached[i].resoSoundInfo.slowmoResoPhase = 0.f;
		m_InterestingLocalObjectsCached[i].shockwaveSoundInfo.attackTime = -1.f;
		m_InterestingLocalObjectsCached[i].shockwaveSoundInfo.releaseTime = -1.f;
		m_InterestingLocalObjectsCached[i].shockwaveSoundInfo.prevVol = 0.f;
		BANK_ONLY(m_InterestingLocalObjectsCached[i].underCover = true);
		m_NextInterestingObjectRandTime[i] = audEngineUtil::GetRandomNumberInRange(5000, 30000);
	}
	for(u32 i = 0; i < g_MaxWindSoundsHistory; i++)
	{
		m_WindTriggeredSoundsHistory[i].soundRef = g_NullSoundRef;
		m_WindTriggeredSoundsHistory[i].time = 0;
	}
	m_NextWindObjectTime = 0;
	for (u8 i = 0; i < g_MaxInterestingLocalTrees; i++ )
	{
		m_LocalTrees[i].tree = NULL;
		m_LocalTrees[i].sector = AUD_SECTOR_FL;
		m_LocalTrees[i].type = AUD_BUSHES;
		m_LocalTrees[i].windSound = NULL;
	}
	for (u8 i = 0; i < MAX_FB_SECTORS; i++)
	{
		m_BackGroundTreesSounds[0][i].sound = NULL;
		m_BackGroundTreesSounds[1][i].sound = NULL;
		m_BackGroundTreesSounds[0][i].fitness = 0.f;
		m_BackGroundTreesSounds[1][i].fitness = 0.f;
		BANK_ONLY(m_BackGroundTreesSounds[0][i].linVol = 0.f;)
		BANK_ONLY(m_BackGroundTreesSounds[1][i].linVol = 0.f;)
		m_BackGroundTreesSounds[0][i].avgDistance = 0.f;
		m_BackGroundTreesSounds[1][i].avgDistance = 0.f;
		m_BackGroundTreesSounds[0][i].volSmoother.Init(0.01f);
		m_BackGroundTreesSounds[1][i].volSmoother.Init(0.01f);

		m_BackGroundTrees[i].bigTreesFitness = 0.f;
		m_BackGroundTrees[i].treesFitness = 0.f;
		m_BackGroundTrees[i].bushesFitness = 0.f;
		m_BackGroundTrees[i].numBigTrees = 0;
		m_BackGroundTrees[i].numTrees = 0;
		m_BackGroundTrees[i].numBushes = 0;
		m_CurrentSoundIdx[i] = 0;
	}
	//for (u8 i = 0; i < MAX_AUD_TREE_TYPES; i++)
	//{
	m_BackGroundTreesFitness[AUD_BIG_TREES] = 1.f;
	m_BackGroundTreesFitness[AUD_SMALL_TREES] = 0.6f;
	m_BackGroundTreesFitness[AUD_BUSHES] = 0.3f;
	//}

	m_WindBushesSoundset.Init(ATSTRINGHASH("WIND_BUSH_SOUNDSET", 0x4AF50222));
	m_WindTreesSoundset.Init(ATSTRINGHASH("WIND_TREE_SMALL_SOUNDSET", 0xDA99FD95));
	m_WindBigTreesSoundset.Init(ATSTRINGHASH("WIND_TREE_LARGE_SOUNDSET", 0x377B6059));

	// BIG TREES
	m_NumTreesToVolume[AUD_BIG_TREES].Init(ATSTRINGHASH("NUM_BIG_TREES_TO_BG_VOLUME", 0xA19220F8));
	m_TreesDistanceToVolume[AUD_BIG_TREES].Init(ATSTRINGHASH("BIG_TREES_DIST_TO_ATTENUATION", 0x30D49CAD));
	m_NumTreesToVolume[AUD_SMALL_TREES].Init(ATSTRINGHASH("NUM_SMALL_TREES_TO_BG_VOLUME", 0xB2B37BAD));
	m_TreesDistanceToVolume[AUD_SMALL_TREES].Init(ATSTRINGHASH("SMALL_TREES_DIST_TO_ATTENUATION", 0x7E5C39CE));
	m_NumTreesToVolume[AUD_BUSHES].Init(ATSTRINGHASH("NUM_BUSHES_TO_BG_VOLUME", 0x86F51D84));
	m_TreesDistanceToVolume[AUD_BUSHES].Init(ATSTRINGHASH("BUSHES_DIST_TO_ATTENUATION", 0x602A042F));

	for(u32 i = 0; i < g_MaxResonatingObjects; i++)
	{
		m_ResonatingObjects[i].Clear();
	}
	
	m_RegisteredShockwavesAllocation.Init(kMaxRegisteredShockwaves);
	
	m_UsePortalInfoForWeather = false;
	
	//Load audio world sectors info 
	// TODO: Fix file endian for PC.
	if(!sm_ProcessSectors)
	{
		fiStream *dataFile = fiStream::Open("common:/data/levels/gta5/AudioWorldSectorsInfo.dat");
		if(dataFile)
		{
			dataFile->Read(&g_AudioWorldSectors,sizeof(g_AudioWorldSectors));
			dataFile->Close();
		}
		else
		{
			naWarningf("Fail opening audio world sectors data file");
		}
	}
	else
	{
		for(u32  sector = 0; sector < g_AudioWorldSectors.GetMaxCount(); sector++)
		{
			audSector initSector;
			initSector.numHighwayNodes = 0;
			initSector.tallestBuilding = 0;
			initSector.numBuildings = 0;
			initSector.numTrees = 0;
			initSector.isWaterSector = false;
			g_AudioWorldSectors[sector] = initSector;
		}
	}
	m_InterestingObjectRainSmoother.Init(0.001f, true);

	m_ScriptedSpecialEffectMode = kSpecialEffectModeNormal;

	sm_HDotToSlowmoResoVol.Init(ATSTRINGHASH("HDOT_TO_SLOWMO_RESO_VOL", 0xEE5D047A));
	sm_VDotToSlowmoResoVol.Init(ATSTRINGHASH("VDOT_TO_SLOWMO_RESO_VOL", 0x97E459ED));
	sm_SpecialAbToFrequency.Init(ATSTRINGHASH("SPECIAL_ABILITY_TO_FREQUENCY", 0xD0974421));
	sm_CityGustDotToVolSpikeForBgTrees.Init(ATSTRINGHASH("CITY_GUST_DOT_TO_VOL", 0x5322FF8F));
	sm_CountrySideGustDotToVolSpikeForBgTrees.Init(ATSTRINGHASH("COUNTRYSIDE_GUST_DOT_TO_VOL", 0x805E6E0));
	
	m_PlayerUnderCoverFactor = 0.f;
	m_PlayerUnderCoverSmoother.Init(0.03f,0.01f,0.f,100.f);

#if RSG_BANK
	m_OverrideSpecialEffectMode = false;
	m_SpecialEffectModeOverride = kSpecialEffectModeNormal;
#endif
}

void naEnvironment::Shutdown()
{
	for(u32 i = 0; i < g_MaxInterestingLocalObjects; i++)
	{
		m_InterestingLocalObjects[i].object = NULL;
		m_InterestingLocalObjects[i].envGroup = NULL;
		m_InterestingLocalObjects[i].macsComponent = -1;
		m_InterestingLocalObjects[i].stillValid = false;
		BANK_ONLY(	m_InterestingLocalObjects[i].underCover = true);
		m_InterestingLocalObjectsCached[i].object = NULL;
		m_InterestingLocalObjectsCached[i].envGroup = NULL;
		m_InterestingLocalObjectsCached[i].macsComponent = -1;
		m_InterestingLocalObjectsCached[i].stillValid = false;
		BANK_ONLY(m_InterestingLocalObjectsCached[i].underCover = true);
	}
	for(u32 i = 0; i < g_MaxWindSoundsHistory; i++)
	{
		m_WindTriggeredSoundsHistory[i].soundRef = g_NullSoundRef;
		m_WindTriggeredSoundsHistory[i].time = 0;
	}
	for (u8 i = 0; i < g_MaxInterestingLocalTrees; i++ )
	{
		m_LocalTrees[i].tree = NULL;
		m_LocalTrees[i].sector = AUD_SECTOR_FL;
		m_LocalTrees[i].type = AUD_BUSHES;
	}
	for(u32 i = 0; i < g_MaxResonatingObjects; i++)
	{
		m_ResonatingObjects[i].Clear();
	} 
}

void naEnvironment::PurgeInteriors()
{
	m_ListenerInteriorInstance = NULL;
	m_InteriorRoomId = 0;
	m_ListenerInteriorSettings = NULL;
	m_ListenerInteriorRoom = NULL;
}

//****************************************************************************************************************************************
// Game-thread GTA-specific functions
//****************************************************************************************************************************************
bool naEnvironment::ParseNodeCallback(CPathNode * pNode, void * pData)
{
	audParseNodeCallbackData* callbackData = (audParseNodeCallbackData*)pData;
	Vector3 position;
	pNode->GetCoors(position);

	if(position.x > callbackData->minPos.x &&
		position.y > callbackData->minPos.y &&
		position.x <= callbackData->maxPos.x &&
		position.y <= callbackData->maxPos.y)
	{
		if(!pNode->m_address.IsEmpty() && ThePaths.IsRegionLoaded(pNode->m_address))
		{
			if(pNode->IsHighway())
			{
				callbackData->numHighwayNodes = (callbackData->numHighwayNodes) + 1;
			}

			callbackData->numNodes = (callbackData->numNodes) + 1;
		}
	}
	
	return true;
}

void naEnvironment::Update()
{
	// Update the water height at the listener position. 
	m_WaterHeightAtListener = CGameWorldWaterHeight::GetWaterHeightAtPos(g_AudioEngine.GetEnvironment().GetPanningListenerPosition());

	// Update the metrics about how built-up the area is based on the world sector info
	UpdateMetrics();
	
	// Map our directional built-up metrics to speaker directions, to save an inverse mapping on every sound
	MapDirectionalMetricsToSpeakers();

	if (g_EnableLocalEnvironmentMetrics)
	{
		// Update our possible echo positions - on the basis of our local environment
		UpdateEchoPositions();

		// Update our view of the local reverb environment - based on our navmesh info, and the real-time wallscanning.
		UpdateLocalReverbEnvironment();
	}

	UpdateInsideCarMetric();

	bool passenegerInATaxi = AreWeAPassengerInATaxi();
	if (passenegerInATaxi)
	{
		m_LastTimeWeWereAPassengerInATaxi = fwTimer::GetTimeInMilliseconds();
	}

	bool isInteriorCam = camInterface::IsRenderedCameraInsideVehicle();

	if(!isInteriorCam && passenegerInATaxi && camInterface::GetScriptDirector().IsRendering())
	{
		isInteriorCam = true;
	}

	const f32 filterCutoff = (isInteriorCam?5000.f:24000.f);
	m_CarInteriorFilterCutoff = m_CarInteriorFilterSmoother.CalculateValue(filterCutoff,fwTimer::GetTimeInMilliseconds());

	// Update deafening state
	UpdateDeafeningState();
#if __BANK
	if(sm_ProcessSectors)
	{
		// world sector stuff
		ProcessCachedWorldSectorOps();
	}
#endif

	// trigger random ambients from local objects
	for(s32 i = g_MaxInterestingLocalObjects -1; i >= 0; i--)
	{
		if(m_InterestingLocalObjects[i].object && GetInterestingLocalObjectMaterial(i) && GetInterestingLocalObjectMaterial(i)->RandomAmbient != g_NullSoundHash &&
			fwTimer::GetTimeInMilliseconds() > m_NextInterestingObjectRandTime[i] && !m_InterestingLocalObjects[i].randomAmbientSound && !IsObjectDestroyed(m_InterestingLocalObjects[i].object))
		{
			audSoundInitParams initParams;
			initParams.Tracker = m_InterestingLocalObjects[i].object->GetPlaceableTracker();

			if (!initParams.Tracker)
			{
				initParams.Position = VEC3V_TO_VECTOR3(m_InterestingLocalObjects[i].object->GetMatrix().GetCol3());
			}

			initParams.EnvironmentGroup = m_InterestingLocalObjects[i].envGroup;
			const u32 interiorTrashRustle = ATSTRINGHASH("RANDOM_TRASH_RUSTLE", 0x04cfd6fb4);
			u32 soundHash = GetInterestingLocalObjectMaterial(i)->RandomAmbient;
			if(soundHash == interiorTrashRustle)
			{
				s32 roomId = m_InterestingLocalObjects[i].object->GetInteriorLocation().GetRoomIndex();
				if(!m_InterestingLocalObjects[i].object->m_nFlags.bInMloRoom || roomId < 0 || roomId == INTLOC_INVALID_INDEX)
				{
					const u32 exteriorTrashRustle = ATSTRINGHASH("TRASH_RUSTLE_EXTERIOR_RANDOM", 0x0e8c5b77a);
					soundHash = exteriorTrashRustle;
				}
			}
			g_AmbientAudioEntity.CreateAndPlaySound_Persistent(soundHash, &m_InterestingLocalObjects[i].randomAmbientSound, &initParams);

			if(audEngineUtil::ResolveProbability(0.2f))
			{
				// occasionally play two in quick succession
				m_NextInterestingObjectRandTime[i] = fwTimer::GetTimeInMilliseconds() + audEngineUtil::GetRandomNumberInRange(3000,10000);
			}
			else
			{
				m_NextInterestingObjectRandTime[i] = fwTimer::GetTimeInMilliseconds() + audEngineUtil::GetRandomNumberInRange(15000,45000);
			}
		}
		if(m_InterestingLocalObjects[i].object && m_InterestingLocalObjects[i].envGroup)
		{
			phInst * pInst = ((CObject*)((CEntity*)m_InterestingLocalObjects[i].object))->GetCurrentPhysicsInst();
			if(pInst && pInst->GetLevelIndex() != phInst::INVALID_INDEX && CPhysics::GetLevel()->IsActive(pInst->GetLevelIndex()))
			{
				m_InterestingLocalObjects[i].envGroup->SetSource(pInst->GetCenterOfMass()); 
				m_InterestingLocalObjects[i].envGroup->SetUpdateTime(sm_ObjOcclusionUpdateTime);	
			}
			else if(m_InterestingLocalObjects[i].envGroup->HasGotUnderCoverResults())
			{
				m_InterestingLocalObjects[i].envGroup->SetSource(m_InterestingLocalObjects[i].object->GetTransform().GetPosition()); 
				m_InterestingLocalObjects[i].envGroup->SetUpdateTime(0);
			}
		}
	}

	BANK_ONLY(DrawDebug());

#if RSG_BANK 
	if(sm_DrawAudioWorldSectors)
	{
		DrawAudioWorldSectors();
	}
	//GenerateAudioWorldSectorsInfo();
#endif

#if RSG_BANK
	if(m_OverrideSpecialEffectMode)
	{
		g_AudioEngine.GetEnvironment().SetSpecialEffectMode(m_SpecialEffectModeOverride);
		return;
	}
#endif // RSG_BANK

	// Special effect mode - underwater takes precedence over script
	if(g_FrontendAudioEntity.IsMenuMusicPlaying())
	{
		g_AudioEngine.GetEnvironment().SetSpecialEffectMode(kSpecialEffectModePauseMenu);
	}
	else if(Water::IsCameraUnderwater() && !audRadioStation::IsPlayingEndCredits())
	{
		g_AudioEngine.GetEnvironment().SetSpecialEffectMode(kSpecialEffectModeUnderwater);
	}
	else
	{
		g_AudioEngine.GetEnvironment().SetSpecialEffectMode(m_ScriptedSpecialEffectMode);
	}

	// Auto-reset scripted mode; for safety script must set every frame
	m_ScriptedSpecialEffectMode = kSpecialEffectModeNormal;
}

bool naEnvironment::IsObjectDestroyed(CEntity* pEntity)
{
	if(pEntity && pEntity->GetIsTypeObject())
	{
		if(((CObject*)pEntity)->GetHealth() == 0.f)
		{
			return true;
		}
	}

	return false;
}

#if __BANK 
void naEnvironment::DrawAudioWorldSectors(bool draw)
{
	sm_DrawAudioWorldSectors = draw;
	g_audDrawWorldSectorDebug = draw;
}
void naEnvironment::AbortAudWorldSectorsGeneration()
{
	sm_ProcessSectors = false; 
	sm_DrawAudioWorldSectors = false; 
	sm_IsWaitingToInit = true;
	audWarpManager::WaitUntilTheSceneIsLoaded(false);
	audWarpManager::FadeCamera(true);
	fiStream *rescueFile = fiStream::Open("C:/AudioSectorInfoGeneratorRescue",false);
	if(rescueFile)
	{
		sm_CurrentSectorX = 0;
		sm_CurrentSectorY = 0;
		rescueFile->WriteInt(&sm_CurrentSectorX,1);
		rescueFile->WriteInt(&sm_CurrentSectorY,1);
		rescueFile->Close();
	}
}
void naEnvironment::InitAudioWorldSectorsGenerator()
{
	sm_IsWaitingToInit = false;
	naDisplayf("Initializing process"); 
	naEnvironment::sm_DrawAudioWorldSectors = true; 
	naDisplayf("Making player invincible");
	CPed * pPlayer = CGameWorld::FindLocalPlayer();
	if (naVerifyf(pPlayer,"Couldn't find local player, aborting process"))
	{
		pPlayer->m_nPhysicalFlags.bNotDamagedByAnything = true;
	}
	else
	{
		AbortAudWorldSectorsGeneration();
	}
	naDisplayf("Checking if we have to resume the process or start a new one.");
	sm_CurrentSectorX = 0;
	sm_CurrentSectorY = 0;
	CleanUpWaterTest();

	fiStream *dataFile = fiStream::Open("C:/AudioWorldSectorsInfo.dat",false);
	if(!dataFile)
	{
		dataFile = fiStream::Create("C:/AudioWorldSectorsInfo.dat");
									 
		if(naVerifyf(dataFile,"Fail opening/creating the file, aborting the process."))
		{
			//Reset the array in memory and write it in one go to the file, so we reset the file as well to the size it will have. 
			naDisplayf("%d",g_AudioWorldSectors.GetMaxCount());
			for(u32  sector = 0; sector < g_AudioWorldSectors.GetMaxCount(); sector++)
			{
				audSector initSector;
				initSector.numHighwayNodes = 0;
				initSector.tallestBuilding = 0;
				initSector.numBuildings = 0;
				initSector.numTrees = 0;
				initSector.isWaterSector = false;
				dataFile->Write(&initSector,sizeof(audSector));
			}
		}
		else
		{
			AbortAudWorldSectorsGeneration();
		}
	}
	else
	{
		//Check if we are resuming a process.
		fiStream *rescueFile = fiStream::Open("C:/AudioSectorInfoGeneratorRescue");
		if(rescueFile)
		{
			rescueFile->ReadInt(&sm_CurrentSectorX,1);
			rescueFile->ReadInt(&sm_CurrentSectorY,1);
			rescueFile->Close();
		}
	}
	dataFile->Close();
}

void naEnvironment::WriteAudioWorldSectorsData()
{
	// The players has been warpped and the scene is being completely loaded, write the file
	naDisplayf("Scene loaded : Writing rescue file");
	fiStream *rescueFile = fiStream::Open("C:/AudioSectorInfoGeneratorRescue",false);
	if(!rescueFile)
	{
		rescueFile = fiStream::Create("C:/AudioSectorInfoGeneratorRescue");
	}
	if(naVerifyf(rescueFile,"Fail opening/creating the file, aborting the process."))
	{
		rescueFile->WriteInt(&sm_CurrentSectorX,1);
		rescueFile->WriteInt(&sm_CurrentSectorY,1);
		rescueFile->Close();
	}
	else
	{
		AbortAudWorldSectorsGeneration();
	}
	naDisplayf("Scene loaded : Writing data file");
	fiStream *dataFile = fiStream::Open("C:/AudioWorldSectorsInfo.dat",false);
	if(naVerifyf(dataFile,"Fail opening/creating the file, aborting the process."))
	{
		u32 idx = (sm_CurrentSectorY * g_audNumWorldSectorsX) + sm_CurrentSectorX;
		// Seek the last position
		dataFile->Seek(idx * sizeof(audSector) );
		dataFile->Write(&g_AudioWorldSectors[idx],sizeof(audSector));
		dataFile->Close();
	}
	else
	{
		AbortAudWorldSectorsGeneration();
	}
}
void naEnvironment::GenerateAudioWorldSectorsInfo()
{
	if(sm_ProcessSectors && sm_IsWaitingToInit)
	{
		InitAudioWorldSectorsGenerator();
	}
	else if(sm_ProcessSectors)
	{
		if(audWarpManager::IsActive())
		{
			// Waiting for the scene to be loaded.
			CheckForWaterSector();
			return;
		}
		else if(sm_LogicStarted) // need to check that we have made a loop already, otherwise we have nothing to write on the file.
		{
			CheckForWaterSector();
			WriteAudioWorldSectorsData();
			sm_CurrentSectorX ++;
			if(sm_CurrentSectorX == g_audNumWorldSectorsX )
			{
				sm_CurrentSectorX = 0;
				sm_CurrentSectorY ++;
			}
			if(sm_CurrentSectorY == g_audNumWorldSectorsY )
			{
				naDisplayf("All sectors have been processed, terminating...");
				fiStream *rescueFile = fiStream::Open("C:/AudioSectorInfoGeneratorRescue");
				if(rescueFile)
				{
					sm_CurrentSectorX = 0;
					sm_CurrentSectorY = 0;
					rescueFile->WriteInt(&sm_CurrentSectorX,1);
					rescueFile->WriteInt(&sm_CurrentSectorY,1);
					rescueFile->Close();
				}
				sm_ProcessSectors =  false;
				audWarpManager::WaitUntilTheSceneIsLoaded(false);
				audWarpManager::FadeCamera(true);
				sm_IsWaitingToInit = false;
				naDisplayf("Done");
				return;
			}
		}
		Vector3 centreFirstSector; 
		centreFirstSector.SetX(g_audWorldSectorsMinX + 0.5f * g_audWidthOfSector);
		centreFirstSector.SetY(g_audWorldSectorsMinY + 0.5f * g_audDepthOfSector);
		centreFirstSector.SetZ(100.f);
		sm_LogicStarted = true;
		naDisplayf("processing sector %d", (sm_CurrentSectorY * g_audNumWorldSectorsX) + sm_CurrentSectorX);
		Vector3 sectorCentre = centreFirstSector;
		sectorCentre.SetX(sectorCentre.GetX() + sm_CurrentSectorX*g_audWidthOfSector);
		sectorCentre.SetY(sectorCentre.GetY() + sm_CurrentSectorY*g_audDepthOfSector);
		naDisplayf("Wrapping player to the center sector");
		naDisplayf("Loading scene....");
		audWarpManager::WaitUntilTheSceneIsLoaded(true);
		audWarpManager::FadeCamera(false);
		audWarpManager::SetWarp(sectorCentre, sectorCentre, 0.f, true, true, 600.f);
		CheckForWaterSector();
	}
}
//-------------------------------------------------------------------------------------------------------------------
void naEnvironment::CleanUpWaterTest()
{
	for(s32 i=0; i<NUMBER_OF_WATER_TEST; i++)
	{
		m_WaterTestLines[i] = Vector3(0.f,0.f,0.f);
	}
	m_WaterLevel = 0.f;
}
//-------------------------------------------------------------------------------------------------------------------
void naEnvironment::ProcessWaterTest(const Vector3 &startPoint, const Vector3 &deltaMovement)
{
	naAssert(sm_NumWaterProbeSets < NUMBER_OF_WATER_TEST);
	bool lineHitWater[NUMBER_OF_WATER_TEST];
	m_WaterTestLines[0] = startPoint;
	lineHitWater[0] = false;
	for (s32 i = 1; i < sm_NumWaterProbeSets; i++)
	{
		lineHitWater[i] = false;
		m_WaterTestLines[i] = m_WaterTestLines[i -1] + deltaMovement;
	}
	// Process them 
	for (s32 i = 0; i < sm_NumWaterProbeSets ; ++i)
	{
		// first look for a map collision
		WorldProbe::CShapeTestProbeDesc probeTest;
		WorldProbe::CShapeTestHitPoint result;
		WorldProbe::CShapeTestResults results(result);
		probeTest.SetResultsStructure(&results);
		probeTest.SetContext(WorldProbe::EAudioLocalEnvironment);
		probeTest.SetIsDirected(false);
		probeTest.SetIncludeFlags(ArchetypeFlags::GTA_ALL_MAP_TYPES);
		probeTest.SetStartAndEnd(m_WaterTestLines[i], Vector3(m_WaterTestLines[i].GetX(),m_WaterTestLines[i].GetY(),-1.f));

		bool foundCollision = WorldProbe::GetShapeTestManager()->SubmitTest(probeTest);
		Vec3V vWaterPos = Vec3V(V_ZERO);
		if (CVfxHelper::TestLineAgainstWater(VECTOR3_TO_VEC3V(m_WaterTestLines[i]),Vec3V(m_WaterTestLines[i].GetX(),m_WaterTestLines[i].GetY(),-1.f), vWaterPos))
		{
			if(!foundCollision || (foundCollision && vWaterPos.GetZf() > result.GetHitPosition().GetZ()))
			{
				m_WaterLevel += 100.f/(sm_NumWaterProbeSets * sm_NumWaterProbeSets);// (sm_NumWaterProbeSets)  because is the number of line sets we send ( sests of 12 linnes ( NUMBER_OF_WATER_TESTS))
				lineHitWater[i] = true;
			}
		}
	}
	for (s32 i = 0; i < NUMBER_OF_WATER_TEST ; ++i)
	{
		Color32 color = Color_red;
		if(lineHitWater[i])
		{
			color = Color_green;
		}
		grcDebugDraw::Line(m_WaterTestLines[i],Vector3(m_WaterTestLines[i].GetX(),m_WaterTestLines[i].GetY(),-1.f), color,1);
	}
}
//-------------------------------------------------------------------------------------------------------------------
void naEnvironment::CheckForWaterSector()
{
	CleanUpWaterTest();
	// Calculate all the lines we need to process
	Vector3 centreFirstSector; 
	centreFirstSector.SetX(g_audWorldSectorsMinX + 0.5f * g_audWidthOfSector);
	centreFirstSector.SetY(g_audWorldSectorsMinY + 0.5f * g_audDepthOfSector);
	centreFirstSector.SetZ(810.f);
	Vector3 sectorCentre = centreFirstSector;
	sectorCentre.SetX(sectorCentre.GetX() + sm_CurrentSectorX*g_audWidthOfSector);
	sectorCentre.SetY(sectorCentre.GetY() + sm_CurrentSectorY*g_audDepthOfSector);

	Vector3 startPoint = sectorCentre;
	Vector3 deltaMovement = Vector3(0.f,0.f,0.f);
	if(sm_NumWaterProbeSets > 1)
	{
		deltaMovement = Vector3((g_audWidthOfSector/(sm_NumWaterProbeSets - 1)),0.f,0.f);
	}

	startPoint = sectorCentre;
	startPoint.SetX(sectorCentre.GetX() - (0.5f * g_audWidthOfSector));
	startPoint.SetY(sectorCentre.GetY() - (0.5f * g_audDepthOfSector));

	for (f32 i = 0.f; i < sm_NumWaterProbeSets; i ++)
	{
		// iterate over y
		ProcessWaterTest(startPoint,deltaMovement);
		startPoint.SetX(sectorCentre.GetX() - (0.5f * g_audWidthOfSector));
		if( sm_NumWaterProbeSets > 1)
		{
			startPoint.SetY(startPoint.GetY() + (g_audDepthOfSector/(sm_NumWaterProbeSets - 1)));
		}
	}

	m_WaterLevel = Min(m_WaterLevel, 100.f);
	naDisplayf("m_WaterLevel %f",m_WaterLevel);
	u32 idx = (sm_CurrentSectorY * g_audNumWorldSectorsX) + sm_CurrentSectorX;
	if(m_WaterLevel > 40.f)
	{
		g_AudioWorldSectors[idx].isWaterSector = true;
	}
	else
	{
		g_AudioWorldSectors[idx].isWaterSector = false;
	}
}
#endif 

void naEnvironment::UpdateInsideCarMetric()
{
	BANK_ONLY(m_PlayerInCarSmoother.SetRate(g_InCarRatioSmoothRate/1000.0f));

	bool isPlayerVehicleATrain = false;
	bool isInACar = true;
	CVehicle *veh = CGameWorld::FindLocalPlayerVehicle();
	if(veh)
	{
		CPed *player = CGameWorld::FindLocalPlayer();
		if(player->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_ENTER_VEHICLE))
		{
			CTaskEnterVehicle *task = (CTaskEnterVehicle*)player->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_ENTER_VEHICLE);
			if(task && task->GetState() < CTaskEnterVehicle::State_EnterSeat)
			{
				isInACar = false;	
			}
		}
		else if(player->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_EXIT_VEHICLE))
		{
			CTaskExitVehicle *task = (CTaskExitVehicle*)player->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_EXIT_VEHICLE);
			if(task && task->GetState() >= CTaskExitVehicle::State_ExitSeat)
			{
				isInACar = false;
			}
		}
		if (veh->GetVehicleType()==VEHICLE_TYPE_TRAIN && isInACar)
		{
			isPlayerVehicleATrain = true;
		}
	}
	else
	{
		if(audPedAudioEntity::GetBJVehicle())
		{
			isInACar = true;
		}
		else
		{
			isInACar = false;
		}
	}

	m_IsPlayerVehicleATrain = isPlayerVehicleATrain;
	m_IsPlayerInVehicle = isInACar;
	m_PlayerInCarRatio = m_PlayerInCarSmoother.CalculateValue((isInACar?1.0f:0.0f), audNorthAudioEngine::GetCurrentTimeInMs());
//	grcDebugDraw::AddDebugOutput("PlayerInCarRatio: %f", m_PlayerInCarRatio);
}

bool naEnvironment::IsPlayerInVehicle(f32 *inCarRatio, bool *isPlayerVehicleATrain) const
{
	if (inCarRatio)
	{
		*inCarRatio = m_PlayerInCarRatio;
	}
	if (isPlayerVehicleATrain)
	{
		*isPlayerVehicleATrain = m_IsPlayerVehicleATrain;
	}
	return m_IsPlayerInVehicle;
}


bool naEnvironment::AreWeInAnInterior(bool *isInteriorASubway, f32 *playerInteriorRatio)
{
	if (isInteriorASubway && m_PlayerIsInAnInterior)
	{
		*isInteriorASubway = m_PlayerIsInASubwayStationOrSubwayTunnel;
	}
	if (playerInteriorRatio)
	{
		*playerInteriorRatio = m_PlayerInteriorRatio;
	}
	return m_PlayerIsInAnInterior;
}

bool naEnvironment::AreWeAPassengerInATaxi() const
{
	CPed* player = CGameWorld::FindLocalPlayer();
	if(player && player->GetVehiclePedInside())
	{
		if(CVehicle::IsTaxiModelId(player->GetVehiclePedInside()->GetModelId()))
		{
			// see if the player has a car drive task, otherwise they are just a passenger
			if(!player->GetIsDrivingVehicle() && !player->GetPedResetFlag(CPED_RESET_FLAG_IsEnteringOrExitingVehicle))
			{
				return true;
			}
		}
	}

	return false;
}

void naEnvironment::RegisterDeafeningSound(Vector3 &pos, f32 volume)
{
	// Work out how loud it is at the listener.
	Vector3 positionRelativeToListener = VEC3V_TO_VECTOR3(g_AudioEngine.GetEnvironment().ComputePositionRelativeToPanningListener(VECTOR3_TO_VEC3V(pos)));
	// TODO: Need to convert the curve to a registered curve Id if its required
	f32 volumeAtListener = volume + g_AudioEngine.GetEnvironment().GetDistanceAttenuation(0,/*&m_DeafeningDistanceRolloffCurve, */g_DeafeningRolloff, g_AudioEngine.GetEnvironment().ComputeDistanceRelativeToVolumeListener(pos));
	// Map our volume into a deafening strength factor. I'm so making this shit up, with no idea where it's going...
	//	f32 linearVolume = audDriverUtil::ComputeLinearVolumeFromDb(volumeAtListener);
	f32 deafeningStrength = m_DeafeningVolumeToStrengthCurve.CalculateValue(volumeAtListener);//m_VolumeToDeafeningCurve(linearVolume);

	// Work out how much we hear in each made up ear. 
	Vector3 normalisedPosition = positionRelativeToListener;
	normalisedPosition.NormalizeSafe();
	f32 rightEarScaling = 0.5f * (1.0f + normalisedPosition.x); // 1 when full right, 0 when full left.
	f32 leftEarScaling = 1.0f - rightEarScaling;
	rightEarScaling = g_DeafeningEarLeak + rightEarScaling * (1.0f - g_DeafeningEarLeak);
	leftEarScaling = g_DeafeningEarLeak + leftEarScaling * (1.0f - g_DeafeningEarLeak);

	f32 rightEarStrength = deafeningStrength * rightEarScaling;
	f32 leftEarStrength = deafeningStrength * leftEarScaling;

	//	Warningf("RES:: %f; LES: %f; ", rightEarStrength, leftEarStrength);

	f32 earStrength[2];
	earStrength[0] = leftEarStrength;
	earStrength[1] = rightEarStrength;

	// For each independent ear, work out what state we're currently in, and transition as necessary
	u32 currentTime = fwTimer::GetTimeInMilliseconds();
	for (s32 i=0; i<2; i++)
	{
		switch (m_DeafeningState[i])
		{
		case AUD_DEAFENING_OFF:
			// Easy peasy - nothing to check
			m_DeafeningState[i] = AUD_DEAFENING_RAMP_UP;
			m_DeafeningMaxStrength[i] = earStrength[i];
			m_DeafeningMinStrength[i] = 0.0f;
			m_DeafeningPhaseEndTime[i] = currentTime + g_DeafeningRampUpTime;
			break;

		case AUD_DEAFENING_RAMP_UP:
			// If we're going to be louder, override, kicking off a new ramp-up phase, with old level as new min. If not, do nothing.
			if (earStrength[i] > m_DeafeningMaxStrength[i])
			{
				m_DeafeningState[i] = AUD_DEAFENING_RAMP_UP;
				// Get our current volume, to use as the min for the new ramp
				f32 proportionThrough = 1.0f - (((f32)m_DeafeningPhaseEndTime[i] - (f32)currentTime) / (f32)g_DeafeningRampUpTime);
				f32 min = (m_DeafeningMaxStrength[i] - m_DeafeningMinStrength[i]) * proportionThrough + m_DeafeningMinStrength[i];
				m_DeafeningMaxStrength[i] = earStrength[i];
				m_DeafeningMinStrength[i] = min;
				m_DeafeningPhaseEndTime[i] = currentTime + g_DeafeningRampUpTime;
			}
			break;

		case AUD_DEAFENING_HOLD:
			// If we're louder, override, kicking off a new ramp-up phase, with old level as new min. If not, do nothing.
			if (earStrength[i] > m_DeafeningMaxStrength[i])
			{
				m_DeafeningState[i] = AUD_DEAFENING_RAMP_UP;
				m_DeafeningMinStrength[i] = m_DeafeningMaxStrength[i];
				m_DeafeningMaxStrength[i] = earStrength[i];
				m_DeafeningPhaseEndTime[i] = currentTime + g_DeafeningRampUpTime;
			}
			break;

		case AUD_DEAFENING_RAMP_DOWN:
			// if we're louder than we are currently, kick off a new ramp up phase, otherwise ignore (which is a little cheap, but whatever)
			f32 proportionThrough = 1.0f - (((f32)m_DeafeningPhaseEndTime[i] - (f32)currentTime) / (f32)g_DeafeningRampDownTime);
			f32 currentStrength = m_DeafeningMaxStrength[i] * (1.0f - proportionThrough);
			if (earStrength[i] > currentStrength)
			{
				m_DeafeningState[i] = AUD_DEAFENING_RAMP_UP;
				m_DeafeningMinStrength[i] = currentStrength;
				m_DeafeningMaxStrength[i] = earStrength[i];
				m_DeafeningPhaseEndTime[i] = currentTime + g_DeafeningRampUpTime;
			}
		}
	}
}


void naEnvironment::UpdateDeafeningState()
{
	u32 currentTime = audNorthAudioEngine::GetCurrentTimeInMs();// This timer takes pausing into account fwTimer::GetTimeInMilliseconds();
	f32 earStrength[2];

	// Instantly stop any of this if we're in slowmo, or paused, or the screen's fully faded out
	f32 timeWarp = fwTimer::GetTimeWarp();
	f32 fadeLevel = camInterface::GetFadeLevel();
	bool playingCutscene = gVpMan.AreWidescreenBordersActive() ||
		(CutSceneManager::GetInstance() && CutSceneManager::GetInstance()->IsStreamedCutScene()); 
	if (timeWarp<0.9f || audNorthAudioEngine::IsAudioPaused() || fadeLevel>=1.0f || playingCutscene)
	{
		m_DeafeningState[0] = AUD_DEAFENING_OFF;
		m_DeafeningState[1] = AUD_DEAFENING_OFF;
	}

	if(fadeLevel >= 1.0f)
	{
		audNorthAudioEngine::ResetLoudSoundTime();
	}

	for (s32 i=0; i<2; i++)
	{
		switch (m_DeafeningState[i])
		{
		case AUD_DEAFENING_OFF:
			earStrength[i] = 0.0f;
			break;

		case AUD_DEAFENING_RAMP_UP:
			// Are we done with our ramping yet?
			if (currentTime >= m_DeafeningPhaseEndTime[i])
			{
				// Go into Hold phase, and set strength to max
				m_DeafeningState[i] = AUD_DEAFENING_HOLD;
				m_DeafeningPhaseEndTime[i] = currentTime + g_DeafeningHoldTime;
				earStrength[i] = m_DeafeningMaxStrength[i];
			}
			else
			{
				f32 proportionThrough = 1.0f - (((f32)m_DeafeningPhaseEndTime[i] - (f32)currentTime) / (f32)g_DeafeningRampUpTime);
				earStrength[i] = (m_DeafeningMaxStrength[i] - m_DeafeningMinStrength[i]) * proportionThrough + m_DeafeningMinStrength[i];
			}
			break;

		case AUD_DEAFENING_HOLD:
			// Are we done with the hold phase yet?
			if (currentTime >= m_DeafeningPhaseEndTime[i])
			{
				// Go into Ramp Down phase
				m_DeafeningState[i] = AUD_DEAFENING_RAMP_DOWN;
				m_DeafeningPhaseEndTime[i] = currentTime + g_DeafeningRampDownTime;
			}
			earStrength[i] = m_DeafeningMaxStrength[i];
			break;

		case AUD_DEAFENING_RAMP_DOWN:
			// Are we done with the ramp down phase yet?
			if (currentTime >= m_DeafeningPhaseEndTime[i])
			{
				// Go into nothing phase
				m_DeafeningState[i] = AUD_DEAFENING_OFF;
				earStrength[i] = 0.0f;
			}
			else
			{
				f32 proportionThrough = 1.0f - (((f32)m_DeafeningPhaseEndTime[i] - (f32)currentTime) / (f32)g_DeafeningRampDownTime);

				earStrength[i] = m_DeafeningMaxStrength[i] * (1.0f - proportionThrough);
			}
			break;
		}
	}
	g_AudioEngine.GetEnvironment().SetEarAttenuation(m_DeafeningAttenuationCurve.CalculateValue(earStrength[0]),m_DeafeningAttenuationCurve.CalculateValue(earStrength[1]));

	m_EarStrength[0] = earStrength[0];
	m_EarStrength[1] = earStrength[1];

	f32 deafeningStrength = Max(earStrength[0], earStrength[1]);

	// use the most restrictive filter cutoff from deafening computation and the car interior filter
	if(g_UseInteriorCarFilter)
	{
		g_AudioEngine.GetEnvironment().SetDeafeningFilterCutoff(Min<f32>(m_CarInteriorFilterCutoff, m_DeafeningFilterCurve.CalculateValue(deafeningStrength)));
	}
	else
	{
		g_AudioEngine.GetEnvironment().SetDeafeningFilterCutoff(m_DeafeningFilterCurve.CalculateValue(deafeningStrength));
	}
	g_AudioEngine.GetEnvironment().SetDeafeningReverbWetness(m_DeafeningReverbCurve.CalculateValue(deafeningStrength));
	if(m_DeafeningState[0] == AUD_DEAFENING_OFF && m_DeafeningState[1] == AUD_DEAFENING_OFF)
	{
		g_AudioEngine.GetEnvironment().SetDeafeningAffectsEverything(false);
	}
	else
	{
		// when we're doing real deafening we want to affect all sounds regardless of effect route
		g_AudioEngine.GetEnvironment().SetDeafeningAffectsEverything(true);
	}
}

void naEnvironment::GetDeafeningStrength(f32* leftEar, f32* rightEar)
{
	naAssertf (leftEar, "Null ptr leftEar (f32*) passed into GetDeafeningStrength");
	naAssertf (rightEar, "Null ptr rightEar (f32*) passed into GetDeafeningStrength");

	if (leftEar && rightEar)
	{
		*leftEar = m_EarStrength[0];
		*rightEar = m_EarStrength[1];
	}
}

bool naEnvironment::IsDeafeningOn()
{
	if (m_DeafeningState[0] != AUD_DEAFENING_OFF || m_DeafeningState[1] != AUD_DEAFENING_OFF)
	{
		return true;
	}
	return false;
}

bool naEnvironment::FindCloseObjectsCB(fwEntity* entity, void* )
{
	CEntity* pEntity = (CEntity*) entity;
	if ( naVerifyf(pEntity, "Null ptr pEntity (CEntity*) passed into FindClosestObjectsCB"))
	{
		CBaseModelInfo *modelInfo = pEntity->GetBaseModelInfo();
		if(modelInfo)
		{
			if(modelInfo->GetIsTree())
			{
				audNorthAudioEngine::GetGtaEnvironment()->AddLocalTree(pEntity);
			}
			else
			{
				const ModelAudioCollisionSettings * materialSettings = NULL;
				s32 component = -1;
				if(pEntity->GetType() == ENTITY_TYPE_OBJECT || pEntity->GetType() == ENTITY_TYPE_BUILDING)
				{
					if(pEntity->GetFragInst() && pEntity->GetFragInst()->GetPartBroken())
					{
						u32 numGroups = (pEntity->GetFragInst()) ? pEntity->GetFragInst()->GetTypePhysics()->GetNumChildGroups() : 0;
						bool isInteresting = false;
						component = 0;
						for(u32 j=0; j < numGroups; j++)
						{
							if(!pEntity->GetFragInst()->GetGroupBroken(j))
							{
								u32 numComponents = (pEntity->GetFragInst()) ? pEntity->GetFragInst()->GetTypePhysics()->GetGroup(j)->GetNumChildren() : 0;
								for(u32 k=0; k < numComponents; k++)
								{
									materialSettings = audCollisionAudioEntity::GetFragComponentMaterialSettings(pEntity, component,true);
									if(materialSettings && (materialSettings->RainLoop != g_NullSoundHash || materialSettings->RandomAmbient != g_NullSoundHash || materialSettings->ShockwaveSound != g_NullSoundHash || materialSettings->EntityResonance != g_NullSoundHash || materialSettings->WindSounds != 0))
									{
										isInteresting = true;
										break;
									}
									component++;							
								}
							}
							else 
							{
								component += (pEntity->GetFragInst()) ? pEntity->GetFragInst()->GetTypePhysics()->GetGroup(j)->GetNumChildren() : 0;
							}
							if(isInteresting)
							{
								break;
							}
						}
					}
					else
					{
						materialSettings = pEntity->GetBaseModelInfo()->GetAudioCollisionSettings();
					}

					if(materialSettings && (materialSettings->RainLoop != g_NullSoundHash || materialSettings->RandomAmbient != g_NullSoundHash || materialSettings->ShockwaveSound != g_NullSoundHash || materialSettings->EntityResonance != g_NullSoundHash || materialSettings->WindSounds != 0))
					{
						// add this to the list
						audNorthAudioEngine::GetGtaEnvironment()->AddInterestingObject(pEntity, materialSettings->ShockwaveSound, materialSettings->EntityResonance,component, materialSettings->RainLoop != g_NullSoundHash);
					}	
				}
			}
		}
	}
	return true;
}
void naEnvironment::UpdateSectorFitness(const u8 sector)
{
	// FIrst of all update each fitness for the current sector. 
	m_BackGroundTrees[sector].bushesFitness = (f32)(MAX_FB_SECTORS * m_BackGroundTrees[sector].numBushes)/(f32)g_MaxInterestingLocalTrees; 
	m_BackGroundTrees[sector].bushesFitness *= m_BackGroundTreesFitness[AUD_BUSHES]; 
	m_BackGroundTrees[sector].treesFitness = (f32)(MAX_FB_SECTORS * m_BackGroundTrees[sector].numTrees)/(f32)g_MaxInterestingLocalTrees; 
	m_BackGroundTrees[sector].treesFitness *= m_BackGroundTreesFitness[AUD_SMALL_TREES]; 
	m_BackGroundTrees[sector].bigTreesFitness = (f32)(MAX_FB_SECTORS * m_BackGroundTrees[sector].numBigTrees)/(f32)g_MaxInterestingLocalTrees; 
	m_BackGroundTrees[sector].bigTreesFitness *= m_BackGroundTreesFitness[AUD_BIG_TREES]; 
	// update current sector fitness
	switch((audTreeTypes)m_BackGroundTreesSounds[m_CurrentSoundIdx[sector]][sector].type)
	{
	case AUD_BUSHES:
		m_BackGroundTreesSounds[m_CurrentSoundIdx[sector]][sector].fitness = m_BackGroundTrees[sector].bushesFitness;
		break;
	case AUD_SMALL_TREES:
		m_BackGroundTreesSounds[m_CurrentSoundIdx[sector]][sector].fitness = m_BackGroundTrees[sector].treesFitness;
		break;
	case AUD_BIG_TREES:
		m_BackGroundTreesSounds[m_CurrentSoundIdx[sector]][sector].fitness = m_BackGroundTrees[sector].bigTreesFitness;
		break;
	default:
		break;
	}
}
void naEnvironment::PlayBgTreesWind(const u8 sector, const u8 soundIdx, const u8 newSoundIdx,const audMetadataRef soundRef,const u8 type,const f32 /*fitness*/,const f32 avgDistance,const u32 numTrees,Vec3V_In soundDir,bool change)
{
	if(g_FrontendAudioEntity.GetSpecialAbilityMode() == audFrontendAudioEntity::kSpecialAbilityModeTrevor)
	{
		return;
	}
	u32 currentIdx = !change ? soundIdx : newSoundIdx;
	//m_BackGroundTreesFitness[type] *= g_FitnessReduced;
	//m_BackGroundTreesSounds[soundIdx][sector].fitness = fitness;
	m_BackGroundTreesSounds[currentIdx][sector].type = (audTreeTypes)type;

	f32 playerInteriorRatio = 0.0f;
	audNorthAudioEngine::GetGtaEnvironment()->AreWeInAnInterior(NULL, &playerInteriorRatio);
	playerInteriorRatio = ClampRange(playerInteriorRatio,0.f,1.f);
	f32 linVol = m_NumTreesToVolume[type].CalculateValue((f32)numTrees) * m_TreesDistanceToVolume[type].CalculateValue(avgDistance) * (1.f - playerInteriorRatio);
	m_BackGroundTreesSounds[currentIdx][sector].volSmoother.SetRate(g_VolSmootherRate);
	linVol = m_BackGroundTreesSounds[currentIdx][sector].volSmoother.CalculateValue(linVol,g_AudioEngine.GetSoundManager().GetTimeInMilliseconds(0));
	BANK_ONLY(m_BackGroundTreesSounds[currentIdx][sector].linVol = linVol;)
	f32 vol = audDriverUtil::ComputeDbVolumeFromLinear(linVol);
	if(vol > g_SilenceVolume)
	{
		audSoundInitParams initParams;
		initParams.Volume = vol;
		initParams.Position = VEC3V_TO_VECTOR3(g_AudioEngine.GetEnvironment().GetVolumeListenerPositionLastFrame() + soundDir * ScalarV(sm_TreesSoundDist));

		if(!change)
		{
			g_WeatherAudioEntity.CreateAndPlaySound_Persistent(soundRef,&m_BackGroundTreesSounds[soundIdx][sector].sound,&initParams);
		}
		else
		{
			g_WeatherAudioEntity.CreateAndPlaySound_Persistent(soundRef,&m_BackGroundTreesSounds[newSoundIdx][sector].sound,&initParams);
			//Stop the old one 
			if( m_BackGroundTreesSounds[soundIdx][sector].sound)
			{
				m_BackGroundTreesSounds[soundIdx][sector].sound->StopAndForget();
				BANK_ONLY(m_BackGroundTreesSounds[soundIdx][sector].linVol = 0;)
			}
			m_BackGroundTreesSounds[soundIdx][sector].volSmoother.Reset();

			m_CurrentSoundIdx[sector] = newSoundIdx;
		}
	}
}
void naEnvironment::UpdateBgTreeSound(const u8 soundIdx, const u8 sector, const u32 numTrees,const u8 type, const f32 avgDistance,Vec3V_In soundDir)
{
	if (m_BackGroundTreesSounds[soundIdx][sector].sound)
	{
		f32 playerInteriorRatio = 0.0f;
		audNorthAudioEngine::GetGtaEnvironment()->AreWeInAnInterior(NULL, &playerInteriorRatio);
		playerInteriorRatio = ClampRange(playerInteriorRatio,0.f,1.f);
		f32 linVol = m_NumTreesToVolume[type].CalculateValue((f32)numTrees) * m_TreesDistanceToVolume[type].CalculateValue(avgDistance) * (1.f - playerInteriorRatio);
		f32 volSpike = 0.f;
		if(g_WeatherAudioEntity.IsGustInProgress())
		{
			Vec3V pos = m_BackGroundTreesSounds[soundIdx][sector].sound->GetRequestedPosition();
			Vector3 dirToGust =  g_WeatherAudioEntity.GetGustMiddlePoint() - VEC3V_TO_VECTOR3(pos);
			dirToGust.SetZ(0.f);
			Vector3 gustLine =  g_WeatherAudioEntity.GetGustDirection();
			gustLine.RotateZ(90.f*DtoR);
			gustLine.SetZ(0.f);
			dirToGust.Normalize();
			gustLine.Normalize();
			f32 dotProduct = gustLine.Dot(dirToGust);

			if(g_AmbientAudioEntity.IsPlayerInTheCity())
			{
				volSpike = sm_CityGustDotToVolSpikeForBgTrees.CalculateValue(fabs(dotProduct));
			}
			else
			{
				volSpike = sm_CountrySideGustDotToVolSpikeForBgTrees.CalculateValue(fabs(dotProduct));
			}

#if __BANK
			if(sm_DrawBgTreesInfo)
			{
				char text[256];
				formatf(text,sizeof(text),"DOT %f Vol %f", fabs(dotProduct),volSpike);
				grcDebugDraw::Text(pos + Vec3V(0.0f,0.0f,1.6f), Color_white,text);
				grcDebugDraw::Line(VEC3V_TO_VECTOR3(pos), VEC3V_TO_VECTOR3(pos) + gustLine *2.f, Color32(255, 0, 0, 255));
				grcDebugDraw::Line(VEC3V_TO_VECTOR3(pos), VEC3V_TO_VECTOR3(pos) + dirToGust *2.f, Color32(0, 255, 0, 255));
			}
#endif
		}
		linVol *= audDriverUtil::ComputeLinearVolumeFromDb(volSpike);
		m_BackGroundTreesSounds[soundIdx][sector].volSmoother.SetRate(g_VolSmootherRate);
		linVol = m_BackGroundTreesSounds[soundIdx][sector].volSmoother.CalculateValue(linVol,g_AudioEngine.GetSoundManager().GetTimeInMilliseconds(0));
		BANK_ONLY(m_BackGroundTreesSounds[soundIdx][sector].linVol = linVol);
		f32 vol = audDriverUtil::ComputeDbVolumeFromLinear(linVol);
		if(g_FrontendAudioEntity.GetSpecialAbilityMode() == audFrontendAudioEntity::kSpecialAbilityModeTrevor)
		{
			vol = g_SilenceVolume;
		}
		if(vol > g_SilenceVolume)
		{
			m_BackGroundTreesSounds[soundIdx][sector].sound->SetRequestedVolume(vol);
#if __BANK 
			if(sm_MuteBgFL && sector == AUD_SECTOR_FL)
			{
				m_BackGroundTreesSounds[soundIdx][sector].sound->SetRequestedVolume(-100.f);
				m_BackGroundTreesSounds[soundIdx][sector].linVol = 0.f;
			}
			if(sm_MuteBgFR && sector == AUD_SECTOR_FR)
			{
				m_BackGroundTreesSounds[soundIdx][sector].sound->SetRequestedVolume(-100.f);
				m_BackGroundTreesSounds[soundIdx][sector].linVol = 0.f;
			}
			if(sm_MuteBgRL && sector == AUD_SECTOR_RL)
			{
				m_BackGroundTreesSounds[soundIdx][sector].sound->SetRequestedVolume(-100.f);
				m_BackGroundTreesSounds[soundIdx][sector].linVol = 0.f;
			}
			if(sm_MuteBgRR && sector == AUD_SECTOR_RR)
			{
				m_BackGroundTreesSounds[soundIdx][sector].sound->SetRequestedVolume(-100.f);
				m_BackGroundTreesSounds[soundIdx][sector].linVol = 0.f;
			}

#endif
			m_BackGroundTreesSounds[soundIdx][sector].sound->SetRequestedPosition(VEC3V_TO_VECTOR3(g_AudioEngine.GetEnvironment().GetVolumeListenerPositionLastFrame() + soundDir * ScalarV(sm_TreesSoundDist)));
		}
		else 
		{
			m_BackGroundTreesSounds[soundIdx][sector].sound->StopAndForget();
		}
	}
}
void naEnvironment::GetWindTreeSectorInfo(audMetadataRef &soundRef, const u8 sector, audTreeTypes &type,f32 &fitness,f32 &avgDistance,Vec3V_InOut soundDir,u32 &numTrees)
{
	if( m_BackGroundTrees[sector].bushesFitness > 0.f)
	{
		type = AUD_BUSHES;
		fitness = m_BackGroundTrees[sector].bushesFitness;
		soundRef = m_WindBushesSoundset.Find(ATSTRINGHASH("Background1", 0x6FE7F076));
	}
	if ((m_BackGroundTrees[sector].treesFitness > 0.f) && (m_BackGroundTrees[sector].treesFitness > fitness))
	{
		type = AUD_SMALL_TREES;
		fitness = m_BackGroundTrees[sector].treesFitness;
		soundRef = m_WindTreesSoundset.Find(ATSTRINGHASH("Background1", 0x6FE7F076));
	}
	if(m_BackGroundTrees[sector].bigTreesFitness > 0.f && m_BackGroundTrees[sector].bigTreesFitness > fitness) 
	{
		type = AUD_BIG_TREES;
		fitness = m_BackGroundTrees[sector].bigTreesFitness;
		soundRef = m_WindBigTreesSoundset.Find(ATSTRINGHASH("Background1", 0x6FE7F076));

	}
	audSoundInitParams initParams;
	switch((audFBSectors)sector)
	{
	case AUD_SECTOR_FL : 
		//north west
		soundDir = Vec3V(-0.707f,0.707f,0.0f);
		break;
	case AUD_SECTOR_FR : 
		// north east
		soundDir = Vec3V(0.707f,0.707f,0.0f);
		break;
	case AUD_SECTOR_RL : 
		// south west
		soundDir = Vec3V(-0.707f,-0.707f,0.0f);
		break;
	case AUD_SECTOR_RR : 
		// south east
		soundDir = Vec3V(0.707f,-0.707f,0.0f);
		break;
	default:
		break;
	}
	m_BackGroundTreesFitness[type] *= g_FitnessReduced;
	switch((audTreeTypes)type)
	{
	case AUD_BUSHES:
		numTrees = m_BackGroundTrees[sector].numBushes;
		break;
	case AUD_SMALL_TREES:
		numTrees = m_BackGroundTrees[sector].numTrees;
		break;
	case AUD_BIG_TREES:
		numTrees = m_BackGroundTrees[sector].numBigTrees;
		break;
	default:
		break;
	}
	f32 clostestDist[5];//sm_NumTreesForAvgDist
	s8 clostestIdx[5];//sm_NumTreesForAvgDist
	u8 index = 0;
	for ( u8 k= 0; k < sm_NumTreesForAvgDist ; k++)
	{
		clostestDist[k] = LARGE_FLOAT;
		clostestIdx[k] = -1;
	}
	for(u8 treeIdx = 0; treeIdx < m_NumLocalTrees; treeIdx ++)
	{
		if(m_LocalTrees[treeIdx].tree)
		{
			if((m_LocalTrees[treeIdx].sector == (audFBSectors)sector) && (m_LocalTrees[treeIdx].type == type))
			{
				Vec3V treePos2D = m_LocalTrees[treeIdx].tree->GetTransform().GetPosition();
				treePos2D.SetZ(0);
				Vec3V listenerPos2D = g_AudioEngine.GetEnvironment().GetVolumeListenerMatrix().GetCol3();
				listenerPos2D.SetZ(0);
				Vec3V dirToTree = Subtract(treePos2D, listenerPos2D);
				f32 distance = fabs(((ScalarV)Mag(dirToTree)).Getf());
				if(index < sm_NumTreesForAvgDist)
				{
					clostestDist[index] = distance;
					clostestIdx[index ++] = treeIdx;
				}
				else
				{
					// replace for the bigger distance
					f32 biggerDist = 0.f;
					s8 biggerIndex = -1;
					for ( u8 k= 0; k < sm_NumTreesForAvgDist ; k++)
					{
						if(clostestDist[k] > biggerDist)
						{
							biggerIndex = k;
							biggerDist = clostestDist[k];
						}
					}
					if (biggerIndex != -1)
					{
						naAssertf(biggerIndex < sm_NumTreesForAvgDist, "wrong index when computing avg distance to trees");
						clostestDist[biggerIndex] = distance;
						clostestIdx[biggerIndex] = treeIdx;
					}
				}
			}
		}
	}
	for ( u8 k= 0; k < index ; k++)
	{
		avgDistance += clostestDist[k];
	}
	avgDistance /= sm_NumTreesForAvgDist;
	m_BackGroundTreesSounds[m_CurrentSoundIdx[sector]][sector].avgDistance = avgDistance;
}

void naEnvironment::UpdateInterestingObjects()
{
#if __BANK 
	u32 numRainObjects = 0;
#endif
	u32 numWindValidIdx = 0;
	s32 windObjectValidIdx[g_MaxInterestingLocalObjects];
	for(u8 i = 0; i < g_MaxInterestingLocalObjects; i ++)
	{
		windObjectValidIdx[i] = -1;
	}
	UpdateShockwaves();
	
	for(s32 i = g_MaxInterestingLocalObjects -1 ; i >=0; i--)
	{
		if(m_InterestingLocalObjects[i].object )
		{
			UpdateResonance(i);
			UpdateRainObject(i);
			if(!audNorthAudioEngine::GetGtaEnvironment()->AreWeInAnInterior() && !m_InterestingLocalObjects[i].object->GetIsInInterior())
			{
				if( audNorthAudioEngine::GetCurrentTimeInMs() > m_NextWindObjectTime )
				{
					const ModelAudioCollisionSettings* material = GetInterestingLocalObjectMaterial(i);
					if(material && material->WindSounds != 0)
					{
						Vec3V pos = m_InterestingLocalObjects[i].object->GetTransform().GetPosition();
						ScalarV distSqd = MagSquared(Subtract(pos,g_AudioEngine.GetEnvironment().GetVolumeListenerPositionLastFrame()));
						if(IsTrue(distSqd >= ScalarV(100)))
						{
							windObjectValidIdx[numWindValidIdx++] = i;			
						}
					}
				}
			}
#if __BANK
			if(m_InterestingLocalObjects[i].rainSound)
			{
				numRainObjects ++;
			}
#endif
		}
	}

	s32 indexToUse = windObjectValidIdx[(u32)audEngineUtil::GetRandomNumberInRange(0.f,(f32)numWindValidIdx)];
	if(indexToUse != -1)
	{
		const ModelAudioCollisionSettings* material = GetInterestingLocalObjectMaterial(indexToUse);
		if(material && material->WindSounds != 0)
		{
			// HL, hacky fix for url:bugstar:6743217 - Beach party : Lots of bottle rolling / rattle assets playing from the bar
			const bool beachPartyZoneActive = CNetwork::IsGameInProgress() && g_AmbientAudioEntity.IsZoneActive(ATSTRINGHASH("AZ_DLC_H4_IH_BEACH_01", 0x17265575));
			const bool suppressPropWindSound = beachPartyZoneActive && (material->WindSounds == ATSTRINGHASH("WIND_PROPS_BOTTLES_RATTLE", 0x6B1C1499) || material->WindSounds == ATSTRINGHASH("WIND_PROPS_BOTTLE_ROLL", 0x9818D730));

			audSoundSet windSounds;
			windSounds.Init(material->WindSounds);
			audMetadataRef soundRef = windSounds.Find(ATSTRINGHASH("WIND_REACTION", 0xD238F5E5));
			if(soundRef != g_NullSoundRef)
			{
				bool found = false;
				s32 index = -1;
				// look in the history : 
				for(u32 i = 0 ; i < g_MaxWindSoundsHistory; i++)
				{
					if(m_WindTriggeredSoundsHistory[i].soundRef == soundRef)
					{
						found = true;
						if(m_WindTriggeredSoundsHistory[i].time + sm_MinTimeToRetrigger > audNorthAudioEngine::GetCurrentTimeInMs() )
						{
							index = i;
						}
						break;
					}
					else if (m_WindTriggeredSoundsHistory[i].soundRef == g_NullSoundRef )
					{
						index = i;
					}
				}
				if (!found && index == -1)
				{
					u32 oldestTime = 0;
					for(u32 i = 0 ; i < g_MaxWindSoundsHistory; i++)
					{
						if (oldestTime <= m_WindTriggeredSoundsHistory[i].time)
						{
							oldestTime = m_WindTriggeredSoundsHistory[i].time;
							index = i;
						}
					}
				}
				if(index != -1)
				{
					if (!suppressPropWindSound)
					{
						audSoundInitParams initParams;
						initParams.Tracker = m_InterestingLocalObjects[indexToUse].object->GetPlaceableTracker();
						initParams.EnvironmentGroup = m_InterestingLocalObjects[indexToUse].envGroup;
						initParams.UpdateEntity = true;
						initParams.u32ClientVar = AUD_ENV_SOUND_WIND;
						g_AmbientAudioEntity.CreateAndPlaySound(soundRef, &initParams);
					}
					
					u32 windTimeVariance = (u32)(sm_WindTimeVariance * g_WeatherAudioEntity.GetWindStrength());
					m_NextWindObjectTime = audNorthAudioEngine::GetCurrentTimeInMs() + (u32)audEngineUtil::GetRandomNumberInRange((f32)(sm_MinWindObjTime - windTimeVariance),(f32)(sm_MaxWindObjtTime - windTimeVariance));
					m_WindTriggeredSoundsHistory[index].soundRef = soundRef;
					m_WindTriggeredSoundsHistory[index].time = audNorthAudioEngine::GetCurrentTimeInMs();
				}
			}
		}
	}
#if __BANK
	if(g_audDrawLocalObjects)
	{
		char txt[128];
		formatf(txt,"NUM RAIN OBJECTS : %u",numRainObjects);
		grcDebugDraw::AddDebugOutput(Color_green,txt);
	}
#endif
	UpdateInterestingTrees();
}
void naEnvironment::UpdateInterestingTrees()
{
	m_BackGroundTreesFitness[AUD_BIG_TREES] = sm_BigTreesFitness;
	m_BackGroundTreesFitness[AUD_SMALL_TREES] = sm_TreesFitness;
	m_BackGroundTreesFitness[AUD_BUSHES] = sm_BushesFitness;
	
	for (u8 i = 0; i < MAX_FB_SECTORS; i ++)
	{
		UpdateSectorFitness(i);

		audMetadataRef soundRef = g_NullSoundRef; 
		audTreeTypes type = AUD_BUSHES;
		f32 fitness = 0.f;
		f32 avgDistance = 0.f;
		u32 numTrees = 0;
		Vec3V soundDir = Vec3V(V_ZERO);
		GetWindTreeSectorInfo(soundRef,i,type,fitness,avgDistance,soundDir,numTrees);

		u8 nextIndex = (m_CurrentSoundIdx[i] + 1) & 1;
		if( !m_BackGroundTreesSounds[m_CurrentSoundIdx[i]][i].sound && !m_BackGroundTreesSounds[nextIndex][i].sound)
		{
			// if we are not playing any sound for the current sector yet, play it if we have to.
			PlayBgTreesWind(i,m_CurrentSoundIdx[i],nextIndex,soundRef,(u8)type,fitness,avgDistance,numTrees,soundDir,false);
		}
		else if (m_BackGroundTreesSounds[m_CurrentSoundIdx[i]][i].sound)
		{
			if( (type != m_BackGroundTreesSounds[m_CurrentSoundIdx[i]][i].type) && (fitness > m_BackGroundTreesSounds[m_CurrentSoundIdx[i]][i].fitness))
			{
				PlayBgTreesWind(i,m_CurrentSoundIdx[i],nextIndex,soundRef,(u8)type,fitness,avgDistance,numTrees,soundDir,true);
			}
		}
		UpdateBgTreeSound(m_CurrentSoundIdx[i], i,numTrees,(u8)type,avgDistance,soundDir);
	}
#if __USEDEBUGAUDIO
	if(sm_DrawBgTreesInfo)
	{
		for (u32 i = 0; i < m_NumLocalTrees; i ++)
		{
			if(m_LocalTrees[i].tree)
			{
				grcDebugDraw::Sphere(m_LocalTrees[i].tree->GetTransform().GetPosition(),2.f,Color32(255,0,0,128));

				char text[256];
				formatf(text,sizeof(text),"Size %f", m_LocalTrees[i].tree->GetBoundRadius());
				grcDebugDraw::Text(m_LocalTrees[i].tree->GetTransform().GetPosition()+ Vec3V(0.0f,0.0f,1.6f), Color_white,text);
			}
		}
		static audMeterList meterLists[8];
		static const char *namesList[24][1]={{"FL0: B"},{"FL0: ST"},{"FL0: T"},{"FL1: B"},{"FL1: ST"},{"FL1: T"},
											{"FR0: B"},{"FR0: ST"},{"FR0: T"},{"FR1: B"},{"FR1: ST"},{"FR1: T"},
											{"RL0: B"},{"RL0: ST"},{"RL0: T"},{"RL1: B"},{"RL1: ST"},{"RL1: T"},
											{"RR0: B"},{"RR0: ST"},{"RR0: T"},{"RR1: B"},{"RR1: ST"},{"RR1: T"}};
		for (u32 i = 0; i < MAX_FB_SECTORS; i ++)
		{
			char text[256];
			formatf(text,sizeof(text),"SECTOR : %u, Dist :%f [BUSHES %u] [TREES:%u] [BIG TREES %u]", i, m_BackGroundTreesSounds[m_CurrentSoundIdx[i]][i].avgDistance, m_BackGroundTrees[i].numBushes,m_BackGroundTrees[i].numTrees,m_BackGroundTrees[i].numBigTrees);
			grcDebugDraw::AddDebugOutput(Color_green,text);
			
			if( m_BackGroundTreesSounds[0][i].sound)
			{
				grcDebugDraw::Sphere(m_BackGroundTreesSounds[0][i].sound->GetRequestedPosition(),2.f,Color32(0,0,255,128));
				char text[256];
				formatf(text,sizeof(text),"[Sector: %u] [Sound %s] [Volume %f]",i, g_AudioEngine.GetSoundManager().GetFactory().GetMetadataManager().GetObjectName(m_BackGroundTreesSounds[0][i].sound->GetNameTableOffset(),0),m_BackGroundTreesSounds[0][i].sound->GetRequestedVolume());
				grcDebugDraw::AddDebugOutput(Color_white,text);
			}
			
			if( m_BackGroundTreesSounds[1][i].sound)
			{
				grcDebugDraw::Sphere(m_BackGroundTreesSounds[1][i].sound->GetRequestedPosition(),2.f,Color32(0,0,255,128));
				char text[256];
				formatf(text,sizeof(text),"[Sector: %u] [Sound %s] [Volume %f]",i, g_AudioEngine.GetSoundManager().GetFactory().GetMetadataManager().GetObjectName(m_BackGroundTreesSounds[1][i].sound->GetNameTableOffset(),0),m_BackGroundTreesSounds[1][i].sound->GetRequestedVolume());
				grcDebugDraw::AddDebugOutput(Color_white,text);
			}
			meterLists[i*2].left = 90.f;
			meterLists[i*2].width = 50.f;
			meterLists[i*2].bottom = 350.f + 100.f * i;
			meterLists[i*2].height = 50.f;
			meterLists[i*2].names = namesList[i*6 + m_BackGroundTreesSounds[0][i].type];
			meterLists[i*2].values = &m_BackGroundTreesSounds[0][i].linVol;
			meterLists[i*2].numValues = 1;
			audNorthAudioEngine::DrawLevelMeters(&meterLists[i*2]);

			meterLists[i*2 + 1].left = 200.f;
			meterLists[i*2 + 1].width = 50.f;
			meterLists[i*2 + 1].bottom = 350.f + 100.f *i;
			meterLists[i*2 + 1].height = 50.f;
			meterLists[i*2 + 1].names = namesList[i*6 + MAX_AUD_TREE_TYPES + m_BackGroundTreesSounds[1][i].type];
			meterLists[i*2 + 1].values = &m_BackGroundTreesSounds[1][i].linVol;
			meterLists[i*2 + 1].numValues = 1;
			audNorthAudioEngine::DrawLevelMeters(&meterLists[i*2 + 1]);
		}
	}
#endif
}
bool naEnvironment::ShouldTriggerWindGustEnd()
{
	Vec3V gustPos2D = VECTOR3_TO_VEC3V(g_WeatherAudioEntity.GetGustMiddlePoint());
	gustPos2D.SetZ(0);
	Vec3V listenerPos2D = g_AudioEngine.GetEnvironment().GetVolumeListenerMatrix().GetCol3();
	listenerPos2D.SetZ(0);
	Vec3V dirToGust = Subtract(gustPos2D, listenerPos2D);
	dirToGust = Normalize(dirToGust);
	float frontDot = Dot( Vec3V(0.0f,1.f,0.0f), dirToGust).Getf();
	float rightDot = Dot( Vec3V(1.f,0.0f,0.0f), dirToGust).Getf();

	audFBSectors sector = AUD_SECTOR_FL;

	if((frontDot >= 0.f))
	{
		if(rightDot >= 0.f)
			sector = AUD_SECTOR_FR;
		else
			sector = AUD_SECTOR_FL; 
	}
	else
	{
		if(rightDot >= 0.f)
			sector = AUD_SECTOR_RR; 
		else
			sector = AUD_SECTOR_RL; 
	}
	u32 numTreesInSector = m_BackGroundTrees[sector].numBigTrees + m_BackGroundTrees[sector].numTrees + m_BackGroundTrees[sector].numBushes;
	if( numTreesInSector >= sm_MinNumberOfTreesToTriggerGustEnd )
	{
		return true;
	}
	return false;
}
s32 naEnvironment::AllocateShockwave()
{
	for(s32 i = 0; i < kMaxRegisteredShockwaves; i++)
	{
		if(m_RegisteredShockwavesAllocation.IsClear(i))
		{
			m_RegisteredShockwavesAllocation.Set(i);
			return i;
		}
	}
	return kInvalidShockwaveId;
}

void naEnvironment::FreeShockwave(const s32 shockwaveId)
{
	naAssertf(shockwaveId != kInvalidShockwaveId, "Invalid shockwave id (%d) passed into FreeShockwave", shockwaveId);
	m_RegisteredShockwavesAllocation.Clear(shockwaveId);
}

void naEnvironment::UpdateShockwave(s32 &shockwaveId, const audShockwave &shockwaveData)
{
	if(shockwaveId == kInvalidShockwaveId)
	{
		shockwaveId = AllocateShockwave();
	}
	else
	{
		audAssertf(m_RegisteredShockwavesAllocation.IsSet(shockwaveId), "Updating a shockwave but we don't have one registered!");
	}

	if(shockwaveId != kInvalidShockwaveId && shockwaveData.intensity > 0.f)
	{
		m_RegisteredShockwaves[shockwaveId] = shockwaveData;
		m_RegisteredShockwaves[shockwaveId].lastUpdateTime = fwTimer::GetTimeInMilliseconds();
	}
	else if(shockwaveId != kInvalidShockwaveId)
	{
		FreeShockwave(shockwaveId);
		shockwaveId = kInvalidShockwaveId;
	}
}

void naEnvironment::UpdateShockwaves()
{
	f32 objectShockwaveVolumes[g_MaxInterestingLocalObjects];
	sysMemSet(&objectShockwaveVolumes, 0, sizeof(objectShockwaveVolumes));

	const f32 timeStep = fwTimer::GetTimeStep();
	for(u32 shockwaveId = 0; shockwaveId < kMaxRegisteredShockwaves; shockwaveId++)
	{
		if(m_RegisteredShockwavesAllocation.IsSet(shockwaveId))
		{
			m_RegisteredShockwaves[shockwaveId].delay -= timeStep;
			if(m_RegisteredShockwaves[shockwaveId].delay <= 0.f)
			{
				// update shockwave radius and intensity
				m_RegisteredShockwaves[shockwaveId].radius += m_RegisteredShockwaves[shockwaveId].radiusChangeRate * timeStep;
				m_RegisteredShockwaves[shockwaveId].intensity += m_RegisteredShockwaves[shockwaveId].intensityChangeRate * timeStep;
			}

			// compute per object shockwave volumes
			for(s32 objId = g_MaxInterestingLocalObjects -1 ; objId >= 0; objId--)
			{
				if(m_InterestingLocalObjects[objId].object && GetInterestingLocalObjectMaterial(objId) && GetInterestingLocalObjectMaterial(objId)->ShockwaveSound != g_NullSoundHash)
				{
					Vec3V objectPos = m_InterestingLocalObjects[objId].object->GetTransform().GetPosition();
					if(m_InterestingLocalObjects[objId].object->GetCurrentPhysicsInst())
					{
						objectPos = m_InterestingLocalObjects[objId].object->GetCurrentPhysicsInst()->GetCenterOfMass();
					}
					const f32 distToShockwaveCentre = (VEC3V_TO_VECTOR3(objectPos) - m_RegisteredShockwaves[shockwaveId].centre).Mag();
					f32 distToShockwave = distToShockwaveCentre * m_RegisteredShockwaves[shockwaveId].distanceToRadiusFactor;
					f32 linVol = 1.f - Min((distToShockwave/m_RegisteredShockwaves[shockwaveId].radius), 1.f);
					linVol *= m_RegisteredShockwaves[shockwaveId].intensity;
					 
					if (!m_RegisteredShockwaves[shockwaveId].centered)
					{
						distToShockwave = Abs((distToShockwaveCentre - m_RegisteredShockwaves[shockwaveId].radius)) * m_RegisteredShockwaves[shockwaveId].distanceToRadiusFactor;
						linVol = m_RegisteredShockwaves[shockwaveId].intensity * g_ShockwaveDistToVol.CalculateValue(distToShockwave);
					}

					const f32 attackTime = m_InterestingLocalObjects[objId].shockwaveSoundInfo.attackTime;
					const f32 releaseTime = m_InterestingLocalObjects[objId].shockwaveSoundInfo.releaseTime;

					if (attackTime >= 0.f && releaseTime >= 0.f)
					{
						objectShockwaveVolumes[objId] = Max(ProcessResoVolume(linVol, attackTime, releaseTime, m_InterestingLocalObjects[objId].shockwaveSoundInfo.prevVol), objectShockwaveVolumes[objId]);
						m_InterestingLocalObjects[objId].shockwaveSoundInfo.prevVol = objectShockwaveVolumes[objId];
					}
					else
					{
						objectShockwaveVolumes[objId] = Max(linVol, objectShockwaveVolumes[objId]);
					}					
				}
				if(m_InterestingLocalObjects[objId].object && GetInterestingLocalObjectMaterial(objId) && GetInterestingLocalObjectMaterial(objId)->EntityResonance != g_NullSoundHash)
				{
					if (!m_RegisteredShockwaves[shockwaveId].centered)
					{
						Vec3V objectPos = m_InterestingLocalObjects[objId].object->GetTransform().GetPosition();
						if(m_InterestingLocalObjects[objId].object->GetCurrentPhysicsInst())
						{
							objectPos = m_InterestingLocalObjects[objId].object->GetCurrentPhysicsInst()->GetCenterOfMass();
						}
						const f32 distToShockwaveCentre = (VEC3V_TO_VECTOR3(objectPos) - m_RegisteredShockwaves[shockwaveId].centre).Mag();
						f32 distToShockwave = Abs((distToShockwaveCentre - m_RegisteredShockwaves[shockwaveId].radius)) * m_RegisteredShockwaves[shockwaveId].distanceToRadiusFactor;
						f32 linVol = m_RegisteredShockwaves[shockwaveId].intensity * g_ShockwaveDistToVol.CalculateValue(distToShockwave);
						const f32 attackTime = m_InterestingLocalObjects[objId].resoSoundInfo.attackTime;
						const f32 releaseTime = m_InterestingLocalObjects[objId].resoSoundInfo.releaseTime;
						m_InterestingLocalObjects[objId].resoSoundInfo.resonanceIntensity = ProcessResoVolume(linVol,attackTime,releaseTime,m_InterestingLocalObjects[objId].resoSoundInfo.resonanceIntensity);
					}
				}
			}
			// player shockwave

			if(audNorthAudioEngine::ShouldTriggerPulseHeadset() && !m_RegisteredShockwaves[shockwaveId].centered)
			{
				CPed* pPlayer = CGameWorld::FindLocalPlayer();
				if(pPlayer)
				{

					const f32 distToShockwaveCentre = (VEC3V_TO_VECTOR3(pPlayer->GetTransform().GetPosition()) - m_RegisteredShockwaves[shockwaveId].centre).Mag();
					f32 distToShockwave = Abs((distToShockwaveCentre - m_RegisteredShockwaves[shockwaveId].radius)) * m_RegisteredShockwaves[shockwaveId].distanceToRadiusFactor;
					f32 intensity = m_RegisteredShockwaves[shockwaveId].intensity * g_ShockwaveDistToVol.CalculateValue(distToShockwave);
					if(distToShockwave <= 5.f)
					{
						audSoundInitParams initParams;
						initParams.SetVariableValue(ATSTRINGHASH("Intensity", 0xEFCD993D),intensity);
						g_FrontendAudioEntity.CreateAndPlaySound(g_FrontendAudioEntity.GetPulseHeadsetSounds().Find(ATSTRINGHASH("ExplosionShockwave", 0xAAB11A2A)),&initParams);
					}
				}
			}


			if((m_RegisteredShockwaves[shockwaveId].radiusChangeRate > 0.0f && m_RegisteredShockwaves[shockwaveId].radius > m_RegisteredShockwaves[shockwaveId].maxRadius) || 
			   (m_RegisteredShockwaves[shockwaveId].intensityChangeRate < 0.0f && m_RegisteredShockwaves[shockwaveId].intensity <= 0.1f))
			{
				m_RegisteredShockwavesAllocation.Clear(shockwaveId);
			}
		}
	}
	
	for(s32 objId = g_MaxInterestingLocalObjects -1 ; objId >= 0; objId--)
	{
		if(m_InterestingLocalObjects[objId].object && GetInterestingLocalObjectMaterial(objId)->ShockwaveSound != g_NullSoundHash)
		{
			Vec3V objectPos = m_InterestingLocalObjects[objId].object->GetTransform().GetPosition();
			if(m_InterestingLocalObjects[objId].object->GetCurrentPhysicsInst())
			{
				objectPos = m_InterestingLocalObjects[objId].object->GetCurrentPhysicsInst()->GetCenterOfMass();
			}
			const f32 dbVol = audDriverUtil::ComputeDbVolumeFromLinear(objectShockwaveVolumes[objId]);
			if(dbVol > g_SilenceVolume)
			{
				fwUniqueObjId effectUniqueID = fwIdKeyGenerator::Get(m_InterestingLocalObjects[objId].object, (s32)objId);
				g_AmbientAudioEntity.RegisterEffectAudio(GetInterestingLocalObjectMaterial(objId)->ShockwaveSound, effectUniqueID, VEC3V_TO_VECTOR3(objectPos), m_InterestingLocalObjects[objId].object, dbVol);
			}
		}
	}
}
void naEnvironment::UpdateResonance(const u32 objectIndex)
{
	const ModelAudioCollisionSettings* material = GetInterestingLocalObjectMaterial(objectIndex);
	if(material && material->EntityResonance != g_NullSoundHash)
	{
		const f32 attackTime = m_InterestingLocalObjects[objectIndex].resoSoundInfo.attackTime;
		const f32 releaseTime = m_InterestingLocalObjects[objectIndex].resoSoundInfo.releaseTime;
		m_InterestingLocalObjects[objectIndex].resoSoundInfo.resonanceIntensity = ProcessResoVolume(0.f,attackTime,releaseTime,m_InterestingLocalObjects[objectIndex].resoSoundInfo.resonanceIntensity);
		f32 resoLinVol = m_InterestingLocalObjects[objectIndex].resoSoundInfo.resonanceIntensity;

		UpdateMichaelSpeacialAbilityForResoObjects(objectIndex,resoLinVol);

		if(resoLinVol > 0.001f)
		{
			const bool resonanceLoopTracksListener = (AUD_GET_TRISTATE_VALUE(material->Flags, FLAG_ID_MODELAUDIOCOLLISIONSETTINGS_RESONANCELOOPTRACKSLISTENER) == AUD_TRISTATE_TRUE) BANK_ONLY(|| g_ForceResonanceTracksListener);

			Vec3V objectPos = m_InterestingLocalObjects[objectIndex].object->GetTransform().GetPosition();
			if(m_InterestingLocalObjects[objectIndex].object->GetCurrentPhysicsInst())
			{
				objectPos = m_InterestingLocalObjects[objectIndex].object->GetCurrentPhysicsInst()->GetCenterOfMass();
			}
			if(!m_InterestingLocalObjects[objectIndex].resoSoundInfo.resonanceSound)
			{
				audSoundInitParams initParams;
				initParams.EnvironmentGroup = m_InterestingLocalObjects[objectIndex].envGroup;

				if (!resonanceLoopTracksListener)
				{
					initParams.Position = VEC3V_TO_VECTOR3(objectPos);
					initParams.Tracker = m_InterestingLocalObjects[objectIndex].object->GetPlaceableTracker();
				}

				const f32 dbVol = audDriverUtil::ComputeDbVolumeFromLinear(resoLinVol);
				initParams.Volume = dbVol;
				g_AmbientAudioEntity.CreateAndPlaySound_Persistent(m_InterestingLocalObjects[objectIndex].resoSoundInfo.resoSoundRef, &m_InterestingLocalObjects[objectIndex].resoSoundInfo.resonanceSound, &initParams);
			}
			if(m_InterestingLocalObjects[objectIndex].resoSoundInfo.resonanceSound)
			{
				const f32 dbVol = audDriverUtil::ComputeDbVolumeFromLinear(resoLinVol);
				m_InterestingLocalObjects[objectIndex].resoSoundInfo.resonanceSound->SetRequestedVolume(dbVol);

				if (resonanceLoopTracksListener)
				{
					spdAABB boundingBox = m_InterestingLocalObjects[objectIndex].object->GetBaseModelInfo()->GetBoundingBox();
					const Mat34V objectMatrix = m_InterestingLocalObjects[objectIndex].object->GetMatrix();
					const Vec3V listenerPos = g_AudioEngine.GetEnvironment().GetVolumeListenerPosition();
					const Vec3V objectRelativePos = UnTransformOrtho(m_InterestingLocalObjects[objectIndex].object->GetMatrix(), listenerPos);
					const Vec3V closestPosition = FindClosestPointOnAABB(boundingBox, objectRelativePos);
					const Vec3V finalPosition = Transform(objectMatrix, closestPosition);
					m_InterestingLocalObjects[objectIndex].resoSoundInfo.resonanceSound->SetRequestedPosition(finalPosition);
				}
			}
		}
		else
		{
			if(m_InterestingLocalObjects[objectIndex].resoSoundInfo.resonanceSound)
			{
				m_InterestingLocalObjects[objectIndex].resoSoundInfo.resonanceSound->StopAndForget();
				m_InterestingLocalObjects[objectIndex].resoSoundInfo.resonanceSound = NULL;
			}
		}
#if __BANK
		if(sm_DisplayResoIntensity && m_InterestingLocalObjects[objectIndex].resoSoundInfo.resonanceSound )
		{
			Vec3V objectPos = m_InterestingLocalObjects[objectIndex].object->GetTransform().GetPosition();
			if(m_InterestingLocalObjects[objectIndex].object->GetCurrentPhysicsInst())
			{
				objectPos = m_InterestingLocalObjects[objectIndex].object->GetCurrentPhysicsInst()->GetCenterOfMass();
			}
			char txt[64];
			formatf(txt, "[Intensity : %f] [Vol: %f] ", m_InterestingLocalObjects[objectIndex].resoSoundInfo.resonanceIntensity,m_InterestingLocalObjects[objectIndex].resoSoundInfo.resonanceSound->GetRequestedVolume());
			grcDebugDraw::Text(objectPos,Color_white,txt);
		}
#endif
	}
}

void naEnvironment::UpdateMichaelSpeacialAbilityForResoObjects(const u32 objectIndex,f32 &resoLinVol)
{
	bool failed = true;
	CPed *pPlayer = CGameWorld::FindLocalPlayer();
	if( pPlayer &&  (g_FrontendAudioEntity.GetSpecialAbilityMode() == audFrontendAudioEntity::kSpecialAbilityModeMichael ||  g_FrontendAudioEntity.GetSpecialAbilityMode() == audFrontendAudioEntity::kSpecialAbilityModeMultiplayer))
	{
		const CPlayerInfo* playerInfo = pPlayer->GetPlayerInfo();
		if(playerInfo)
		{
			const CEntity* target = playerInfo->GetTargeting().GetTarget();
			if(target && (target == m_InterestingLocalObjects[objectIndex].object) )
			{
				Vec3V objectPos = target->GetTransform().GetPosition();
				if(target->GetCurrentPhysicsInst())
				{
					objectPos = target->GetCurrentPhysicsInst()->GetCenterOfMass();
				}
				failed = false;
				m_InterestingLocalObjects[objectIndex].resoSoundInfo.slowmoResoVolLin = 1.f;
				// Workout the intensity based on the angular distance to the camera front.
				Vector3 front = camInterface::GetFront();
				front.SetZ(0.f);
				front.Normalize();
				Vector3 cameraPos = camInterface::GetPos();
				Vector3 dirToObject = VEC3V_TO_VECTOR3(objectPos) - cameraPos;
				dirToObject.SetZ(0);
				dirToObject.Normalize();
				f32 horizontalDot = front.Dot(dirToObject);

				m_InterestingLocalObjects[objectIndex].resoSoundInfo.slowmoResoVolLin *= sm_HDotToSlowmoResoVol.CalculateValue(horizontalDot);
				//naDisplayf("HDot %f V %f ",horizontalDot,slomoResoIntensity);

				front = camInterface::GetUp();
				front.SetY(0.f);
				front.Normalize();
				cameraPos = camInterface::GetPos();
				dirToObject = VEC3V_TO_VECTOR3(objectPos) - cameraPos;
				dirToObject.SetY(0);
				dirToObject.Normalize();
				f32 verticalDot = front.Dot(dirToObject);
				verticalDot *= (verticalDot >=0.f ? -1.f : 1.f);
				verticalDot += 1.f;
				m_InterestingLocalObjects[objectIndex].resoSoundInfo.slowmoResoVolLin *= sm_VDotToSlowmoResoVol.CalculateValue(verticalDot);
				//naDisplayf("VDot %f V %f",verticalDot,slomoResoIntensity);

				f32 frequency = sm_SpecialAbToFrequency.CalculateValue(pPlayer->GetSpecialAbility()->GetRemainingNormalized());//,sm_SlowMoLFOFrequencyMin,sm_SlowMoLFOFrequencyMax);
				m_InterestingLocalObjects[objectIndex].resoSoundInfo.slowmoResoPhase += audNorthAudioEngine::GetTimeStep() * (1.f/frequency);
				m_InterestingLocalObjects[objectIndex].resoSoundInfo.slowmoResoPhase = fmodf(m_InterestingLocalObjects[objectIndex].resoSoundInfo.slowmoResoPhase,1);
				m_InterestingLocalObjects[objectIndex].resoSoundInfo.slowmoResoVolLin *= ((rage::Sin(ScalarV(2.f * PI * m_InterestingLocalObjects[objectIndex].resoSoundInfo.slowmoResoPhase)).Getf() + 1 ) * 0.5f);
				// Get the max 
				resoLinVol = Max(resoLinVol,m_InterestingLocalObjects[objectIndex].resoSoundInfo.slowmoResoVolLin);
			}
		}
	}
	if(failed && m_InterestingLocalObjects[objectIndex].resoSoundInfo.slowmoResoPhase != 0.f)  
	{
		m_InterestingLocalObjects[objectIndex].resoSoundInfo.slowmoResoPhase = 0.f;
		m_InterestingLocalObjects[objectIndex].resoSoundInfo.resonanceIntensity = Max(resoLinVol,m_InterestingLocalObjects[objectIndex].resoSoundInfo.slowmoResoVolLin);
		resoLinVol = m_InterestingLocalObjects[objectIndex].resoSoundInfo.resonanceIntensity;
		m_InterestingLocalObjects[objectIndex].resoSoundInfo.slowmoResoVolLin = 0.f;
	}
}
f32 naEnvironment::ProcessResoVolume(const f32 inputSignal, const f32 attack, const f32 release, const f32 state)
{
	if(naVerifyf(attack * release > 0.f,"Neither attack or release times can't be 0, please check the Reso envelope sound"))
	{
		float prevEnvelope = state;
		const f32 input = Abs(inputSignal);

		if(naVerifyf(fwTimer::GetTimeStep() > 0.f,"Got a 0 time step, aborting reso volume computation."))
		{
			const f32 effectiveSampleRate = 1.f / fwTimer::GetTimeStep();
			// prevent dbz
			const f32 attackTime = Max(0.00001f, attack);
			const f32 releaseTime = Max(0.00001f, release);
			const f32 a = Powf(0.01f, 1.0f/(attackTime * effectiveSampleRate)); 
			const f32 r = Powf(0.01f, 1.0f/(releaseTime * effectiveSampleRate)); 

			// envelope >= input ? release : attack
			const f32 factor = Selectf(input - prevEnvelope, a, r);

			// don't allow output to go denormal; clamp to 0
			const f32 unclampedVal = factor * (prevEnvelope - input) + input;
			prevEnvelope = unclampedVal <= 1e-17f ? 0.f : unclampedVal;
			return prevEnvelope;
		}
	}
	return 0.f;
}
void naEnvironment::CalculateUnderCoverFactor()
{
	f32 underCoverFactor = 0.f;
	for (u32 i = 0; i < MAX_SURROUNDING_POLYS_FOR_AUDIO_PROPERTIES; i++)
	{
		if(m_CoveredPolys.IsSet(i))
		{
			underCoverFactor += sqrt(m_SourceEnvironmentPolyAreas[i]/PI) * sm_RadiusScale;
		}
	}
	f32 factor = 0.f;
	CPed* pPlayer = CGameWorld::FindLocalPlayer();
	if (pPlayer)
	{
		// This was added as a way to detect when in a vehicle and going undercover.
		if (audNorthAudioEngine::GetGtaEnvironment()->IsPlayerInVehicle())
		{
			CVehicle* vehicle = pPlayer->GetVehiclePedInside();
			if (vehicle && vehicle->GetAudioEnvironmentGroup())
			{
				if (((naEnvironmentGroup*)vehicle->GetAudioEnvironmentGroup())->IsUnderCover())
				{
					factor = underCoverFactor;
				}
			}
		}
		if (pPlayer->GetAudioEnvironmentGroup())
		{
			if (((naEnvironmentGroup*)pPlayer->GetAudioEnvironmentGroup())->IsUnderCover())
			{
				factor = underCoverFactor;
			}
		}
	}
	m_PlayerUnderCoverFactor = m_PlayerUnderCoverSmoother.CalculateValue(factor,audNorthAudioEngine::GetCurrentTimeInMs());
}
void naEnvironment::CreateRainObjectEnvironmentGroup(const u32 objectIndex)
{
	BANK_ONLY(if(!sm_UseCoverAproximation))		
	{
		Vec3V objectPos = m_InterestingLocalObjects[objectIndex].object->GetTransform().GetPosition();
		if(m_InterestingLocalObjects[objectIndex].object->GetCurrentPhysicsInst())
		{
			objectPos = m_InterestingLocalObjects[objectIndex].object->GetCurrentPhysicsInst()->GetCenterOfMass();
		}
		if(!m_InterestingLocalObjects[objectIndex].envGroup)
		{
			m_InterestingLocalObjects[objectIndex].envGroup = naEnvironmentGroup::Allocate("InterestingObj"); 
			if(m_InterestingLocalObjects[objectIndex].envGroup) 
			{ 
				m_InterestingLocalObjects[objectIndex].envGroup->Init(NULL, 20.0f,sm_ObjOcclusionUpdateTime); 
				m_InterestingLocalObjects[objectIndex].envGroup->SetSource(objectPos); 
				m_InterestingLocalObjects[objectIndex].envGroup->ForceSourceEnvironmentUpdate(m_InterestingLocalObjects[objectIndex].object); 
				m_InterestingLocalObjects[objectIndex].envGroup->SetUsePortalOcclusion(true); 
				m_InterestingLocalObjects[objectIndex].envGroup->SetUseInteriorDistanceMetrics(true); 
				m_InterestingLocalObjects[objectIndex].envGroup->SetMaxPathDepth(2); 
				m_InterestingLocalObjects[objectIndex].envGroup->AddSoundReference(); 
			} 
		}
	}
#if __BANK
	else
	{
		m_InterestingLocalObjects[objectIndex].underCover = false;
		for (u32 i = 0; i < MAX_SURROUNDING_POLYS_FOR_AUDIO_PROPERTIES; i++)
		{
			if(m_CoveredPolys.IsSet(i))
			{
				f32 distToObj = fabs((VEC3V_TO_VECTOR3(m_InterestingLocalObjects[objectIndex].object->GetTransform().GetPosition()) - m_SourceEnvironmentPolyCentroids[i]).Mag());
				f32 approximatedRadius = sqrt(m_SourceEnvironmentPolyAreas[i]/PI) * sm_RadiusScale;
				if(distToObj < approximatedRadius)
				{
					m_InterestingLocalObjects[objectIndex].underCover = true;
					break;
				}
			}
		}
	}
#endif
}
void naEnvironment::UpdateRainObject(const u32 objectIndex)
{	
	if (g_weather.GetTimeCycleAdjustedRain() != 0 && m_InterestingLocalObjects[objectIndex].object && !audNorthAudioEngine::GetGtaEnvironment()->AreWeInAnInterior())
	{
		Vec3V objectPos = m_InterestingLocalObjects[objectIndex].object->GetTransform().GetPosition();
		if (m_InterestingLocalObjects[objectIndex].object->GetCurrentPhysicsInst())
		{
			objectPos = m_InterestingLocalObjects[objectIndex].object->GetCurrentPhysicsInst()->GetCenterOfMass();
		}

		const ModelAudioCollisionSettings* material = GetInterestingLocalObjectMaterial(objectIndex);

		const bool rainLoopTracksListener = (AUD_GET_TRISTATE_VALUE(material->Flags, FLAG_ID_MODELAUDIOCOLLISIONSETTINGS_RAINLOOPTRACKSLISTENER) == AUD_TRISTATE_TRUE) BANK_ONLY(|| g_ForceRainLoopTracksListener);
		const bool placeRainLoopOnTopOfBounds = (AUD_GET_TRISTATE_VALUE(material->Flags, FLAG_ID_MODELAUDIOCOLLISIONSETTINGS_PLACERAINLOOPONTOPOFBOUNDS) == AUD_TRISTATE_TRUE) BANK_ONLY(|| g_ForceRainLoopPlayFromTopOfBounds);

		if (rainLoopTracksListener)
		{
			spdAABB boundingBox = m_InterestingLocalObjects[objectIndex].object->GetBaseModelInfo()->GetBoundingBox();

			if (placeRainLoopOnTopOfBounds)
			{
				Vec3V min = boundingBox.GetMin();
				min.SetZ(boundingBox.GetMax().GetZ());
				boundingBox.SetMin(min);
			}

			const Mat34V objectMatrix = m_InterestingLocalObjects[objectIndex].object->GetMatrix();
			const Vec3V listenerPos = g_AudioEngine.GetEnvironment().GetVolumeListenerPosition();
			const Vec3V objectRelativePos = UnTransformOrtho(m_InterestingLocalObjects[objectIndex].object->GetMatrix(), listenerPos);
			const Vec3V closestPosition = FindClosestPointOnAABB(boundingBox, objectRelativePos);
			const Vec3V finalPosition = Transform(objectMatrix, closestPosition);
			objectPos = finalPosition;
		}
		else if (placeRainLoopOnTopOfBounds)
		{
			const spdAABB boundingBox = m_InterestingLocalObjects[objectIndex].object->GetBaseModelInfo()->GetBoundingBox();
			const Mat34V objectMatrix = m_InterestingLocalObjects[objectIndex].object->GetMatrix();
			const Vec3V finalPosition = Transform(objectMatrix, Vec3V(ScalarV(V_ZERO), ScalarV(V_ZERO), boundingBox.GetMax().GetZ()));
			objectPos = finalPosition;
		}

		ScalarV sqdDistance = MagSquared(objectPos - g_AudioEngine.GetEnvironment().GetVolumeListenerPositionLastFrame());
		if (sqdDistance.Getf() <= sm_SqdDistanceToPlayRainSounds)
		{			
			if(material && material->RainLoop != g_NullSoundHash)
			{
				bool allowPlay = (m_NumRainPropSounds < sm_MaxNumRainProps);
				if(allowPlay)
				{
					CreateRainObjectEnvironmentGroup(objectIndex);
					if(m_InterestingLocalObjects[objectIndex].envGroup)
					{
						allowPlay = m_InterestingLocalObjects[objectIndex].envGroup->GetLastUpdateTime() != 0;
					}
				}
				if( allowPlay && !m_InterestingLocalObjects[objectIndex].rainSound && !IsObjectUnderCover(objectIndex))
				{
					audSoundInitParams initParams;
					initParams.AttackTime = 1000;
					initParams.EnvironmentGroup = m_InterestingLocalObjects[objectIndex].envGroup;
					initParams.Position = VEC3V_TO_VECTOR3(objectPos);

					if (!rainLoopTracksListener && !placeRainLoopOnTopOfBounds)
					{
						initParams.Tracker = m_InterestingLocalObjects[objectIndex].object->GetPlaceableTracker();
					}

					// By default don't modulate the props rain Vol through code.
					f32 vol = g_WeatherAudioEntity.GetRainVolume();
					if(audNorthAudioEngine::GetGtaEnvironment()->AreWeInAnInterior())// || audNorthAudioEngine::GetGtaEnvironment()->IsCameraShelteredFromRain())
					{
						vol = -100.f;
					}

					f32 volLinear = audDriverUtil::ComputeLinearVolumeFromDb(vol);
					f32 smoothedVolLinear = m_InterestingObjectRainSmoother.CalculateValue(volLinear, g_AudioEngine.GetSoundManager().GetTimeInMilliseconds(0));
					f32 smoothedVolDb = audDriverUtil::ComputeDbVolumeFromLinear(smoothedVolLinear);
					if(AUD_GET_TRISTATE_VALUE(material->Flags, FLAG_ID_MODELAUDIOCOLLISIONSETTINGS_TURNOFFRAINVOLONPROPS) != AUD_TRISTATE_TRUE)
					{
						initParams.Volume = smoothedVolDb;
						initParams.Volume += sm_RainOnPropsVolumeOffset;
						initParams.Predelay = sm_RainOnPopsPreDealy;
					}
					m_NumRainPropSounds++;
					g_WeatherAudioEntity.CreateAndPlaySound_Persistent(material->RainLoop, &m_InterestingLocalObjects[objectIndex].rainSound, &initParams);
				}
				else if(IsObjectUnderCover(objectIndex))
				{
					if( m_InterestingLocalObjects[objectIndex].rainSound)
					{
						if (m_NumRainPropSounds > 0)
						{
							m_NumRainPropSounds--;
						}
						m_InterestingLocalObjects[objectIndex].rainSound->StopAndForget();
						m_InterestingLocalObjects[objectIndex].rainSound = NULL;
					}
				}
				if( m_InterestingLocalObjects[objectIndex].rainSound)
				{
					// By default don't modulate the props rain Vol through code.
					f32 vol = g_WeatherAudioEntity.GetRainVolume();
					if(audNorthAudioEngine::GetGtaEnvironment()->AreWeInAnInterior())
					{
						vol = -100.f;
					}

					f32 volLinear = audDriverUtil::ComputeLinearVolumeFromDb(vol);
					f32 smoothedVolLinear = m_InterestingObjectRainSmoother.CalculateValue(volLinear, g_AudioEngine.GetSoundManager().GetTimeInMilliseconds(0));
					f32 smoothedVolDb = audDriverUtil::ComputeDbVolumeFromLinear(smoothedVolLinear);
					if(AUD_GET_TRISTATE_VALUE(material->Flags, FLAG_ID_MODELAUDIOCOLLISIONSETTINGS_TURNOFFRAINVOLONPROPS) != AUD_TRISTATE_TRUE)
					{
						m_InterestingLocalObjects[objectIndex].rainSound->SetRequestedVolume(smoothedVolDb +sm_RainOnPropsVolumeOffset);
					}

					if (rainLoopTracksListener || placeRainLoopOnTopOfBounds)
					{
						m_InterestingLocalObjects[objectIndex].rainSound->SetRequestedPosition(objectPos); 
					}
				}
			}
		}
		else
		{
			 if(m_InterestingLocalObjects[objectIndex].rainSound)
			 {
				if (m_NumRainPropSounds > 0)
				{
					m_NumRainPropSounds--;
				}
				m_InterestingLocalObjects[objectIndex].rainSound->StopAndForget();
				m_InterestingLocalObjects[objectIndex].rainSound = NULL;
			 }
			 if( m_InterestingLocalObjects[objectIndex].envGroup )
				{
					m_InterestingLocalObjects[objectIndex].envGroup->RemoveSoundReference();
					m_InterestingLocalObjects[objectIndex].envGroup = NULL;
				}
		}
	}
	else 
	{
		if(m_InterestingLocalObjects[objectIndex].rainSound)
		{
			if (m_NumRainPropSounds > 0)
			{
				m_NumRainPropSounds--;
			}
			m_InterestingLocalObjects[objectIndex].rainSound->StopAndForget();
			m_InterestingLocalObjects[objectIndex].rainSound = NULL;
		}
		if( m_InterestingLocalObjects[objectIndex].envGroup )
		{
			m_InterestingLocalObjects[objectIndex].envGroup->RemoveSoundReference();
			m_InterestingLocalObjects[objectIndex].envGroup = NULL;
		}
	}
}
void naEnvironment::UpdateLocalEnvironment()
{
	// JB: I moved this stuff into a separate function so it can be shared whatever async/sync approach we use above.
	UpdateLocalEnvironmentObjects();
}

void naEnvironment::LocalObjectsList_StartAsyncUpdate()
{
	if(g_EnableLocalEnvironmentMetrics && !(fwTimer::GetSystemFrameCount() & 15))
	{
		PF_PUSH_TIMEBAR("LocalObjectsList_StartAsyncUpdate");
		spdSphere searchSphere(g_AudioEngine.GetEnvironment().GetVolumeListenerPosition(), ScalarV(30.f));
		fwIsSphereIntersecting searchVol(searchSphere);

		// start spu search
		ms_localObjectSearch.Start(
			fwWorldRepMulti::GetSceneGraphRoot(),								// root scene graph node
			&searchVol,															// search volume
			ENTITY_TYPE_MASK_OBJECT | ENTITY_TYPE_MASK_BUILDING,				// entity types
			SEARCH_LOCATION_EXTERIORS | SEARCH_LOCATION_INTERIORS,				// location flags
			SEARCH_LODTYPE_HIGHDETAIL,											// lod flags
			SEARCH_OPTION_SKIP_PROCEDURAL_ENTITIES,								// option flags
			WORLDREP_SEARCHMODULE_AUDIO,										// module
			sysDependency::kPriorityCritical									// priority
			);

		ms_localObjectSearchLaunched = true;
		PF_POP_TIMEBAR();
	}
}

void naEnvironment::LocalObjectsList_EndAsyncUpdate()
{
	if(ms_localObjectSearchLaunched)
	{
		PF_PUSH_TIMEBAR("LocalObjectsList_EndAsyncUpdate");
		ms_localObjectSearch.Finalize();

		for (u32 i = 0 ; i < m_NumLocalTrees; i ++)
		{
			m_LocalTrees[i].tree = NULL;
			m_LocalTrees[i].sector = AUD_SECTOR_FL;
		}
		m_NumLocalTrees = 0;
		for (u32 i = 0 ; i < MAX_FB_SECTORS; i ++)
		{
			m_BackGroundTrees[i].numBushes = 0;
			m_BackGroundTrees[i].numTrees = 0;
			m_BackGroundTrees[i].numBigTrees = 0;
			m_BackGroundTrees[i].bushesFitness = 0.f;
			m_BackGroundTrees[i].treesFitness = 0.f;
			m_BackGroundTrees[i].bigTreesFitness = 0.f;
		}
		// Before recalculating the new objects, all the current ones are marked up to delete.
		for (u32 i = 0; i < g_MaxInterestingLocalObjects; i ++ )
		{
			m_InterestingLocalObjects[i].stillValid = false;
			if( !m_InterestingLocalObjects[i].object )
			{
				m_InterestingLocalObjects[i].macsComponent = -1;
				if( m_InterestingLocalObjects[i].envGroup )
				{
					m_InterestingLocalObjects[i].envGroup->RemoveSoundReference();
					m_InterestingLocalObjects[i].envGroup = NULL;
				}
				BANK_ONLY(m_InterestingLocalObjects[i].underCover = true);
				if(m_InterestingLocalObjects[i].rainSound)
				{
					m_InterestingLocalObjects[i].rainSound->StopAndForget();
					m_InterestingLocalObjects[i].rainSound = NULL;
					if (m_NumRainPropSounds > 0)
					{
						m_NumRainPropSounds--;
					}
				}
				if (m_InterestingLocalObjects[i].randomAmbientSound)
				{
					m_InterestingLocalObjects[i].randomAmbientSound->StopAndForget();
					m_InterestingLocalObjects[i].randomAmbientSound = NULL;					
				}
				if(m_InterestingLocalObjects[i].resoSoundInfo.resonanceSound)
				{
					m_InterestingLocalObjects[i].resoSoundInfo.resonanceSound->StopAndForget();
					m_InterestingLocalObjects[i].resoSoundInfo.resonanceSound = NULL;
				}
				m_InterestingLocalObjects[i].resoSoundInfo.resonanceIntensity = 0.f;
				m_InterestingLocalObjects[i].resoSoundInfo.attackTime = 0.f;
				m_InterestingLocalObjects[i].resoSoundInfo.releaseTime = 0.f;
				m_InterestingLocalObjects[i].resoSoundInfo.slowmoResoPhase = 0.f;
				m_InterestingLocalObjects[i].resoSoundInfo.slowmoResoVolLin = 0.f;
				m_InterestingLocalObjects[i].resoSoundInfo.resoSoundRef = g_NullSoundRef;
			}
			else
			{
				if (m_InterestingLocalObjects[i].randomAmbientSound && IsObjectDestroyed(m_InterestingLocalObjects[i].object))
				{
					m_InterestingLocalObjects[i].randomAmbientSound->StopAndForget();
					m_InterestingLocalObjects[i].randomAmbientSound = NULL;					
				}
			}
		}
		for(u32 i = 0; i < m_NumInterestingLocalObjectsCached; i++)
		{
			m_InterestingLocalObjectsCached[i].object = NULL;
			m_InterestingLocalObjectsCached[i].envGroup = NULL;
			m_InterestingLocalObjectsCached[i].rainSound = NULL;
			m_InterestingLocalObjectsCached[i].windSound = NULL;
			m_InterestingLocalObjectsCached[i].randomAmbientSound  = NULL;
			m_InterestingLocalObjectsCached[i].macsComponent = -1;
			BANK_ONLY(m_InterestingLocalObjectsCached[i].underCover = true);
			m_InterestingLocalObjectsCached[i].resoSoundInfo.resonanceSound = NULL;
			m_InterestingLocalObjectsCached[i].resoSoundInfo.resonanceIntensity = 0.f;
			m_InterestingLocalObjectsCached[i].resoSoundInfo.attackTime = 0.f;
			m_InterestingLocalObjectsCached[i].resoSoundInfo.releaseTime = 0.f;
			m_InterestingLocalObjectsCached[i].resoSoundInfo.slowmoResoVolLin = 0.f;
			m_InterestingLocalObjectsCached[i].resoSoundInfo.slowmoResoPhase = 0.f;
			m_InterestingLocalObjectsCached[i].resoSoundInfo.resoSoundRef = g_NullSoundRef;
		}
		m_NumInterestingLocalObjectsCached = 0;
		ms_localObjectSearch.ExecuteCallbackOnResult(FindCloseObjectsCB, NULL);
		ms_localObjectSearchLaunched = false;
		// Go through the cached objects and remove the occlusion group for those that aren't valid anymore.
		for(u32 i = 0; i < m_NumInterestingLocalObjectsCached; i++)
		{
			bool alreadyAdded = false; 
			s32 freeIdx = -1;
			for (u32 j = 0; j < g_MaxInterestingLocalObjects; j++)
			{
				if(m_InterestingLocalObjectsCached[i].object == m_InterestingLocalObjects[j].object)
				{
					m_InterestingLocalObjects[j].stillValid = true;
					alreadyAdded = true; 
					break;
				}
				else if (!m_InterestingLocalObjects[j].object)
				{
					freeIdx = j;
				}
			}
			if(!alreadyAdded && freeIdx != -1 && freeIdx < g_MaxInterestingLocalObjects)
			{
				m_InterestingLocalObjects[freeIdx] = m_InterestingLocalObjectsCached[i];
				m_InterestingLocalObjects[freeIdx].stillValid = true;
			}
		}
		// Free up the objects that aren't valid.
		for (u32 i = 0; i < g_MaxInterestingLocalObjects; i++)
		{
			if(!m_InterestingLocalObjects[i].stillValid)
			{
				m_InterestingLocalObjects[i].object = NULL;
				m_InterestingLocalObjects[i].macsComponent = -1;
				if( m_InterestingLocalObjects[i].envGroup )
				{
					m_InterestingLocalObjects[i].envGroup->RemoveSoundReference();
					m_InterestingLocalObjects[i].envGroup = NULL;
				}
				BANK_ONLY(m_InterestingLocalObjects[i].underCover = true);
				if(m_InterestingLocalObjects[i].rainSound)
				{
					m_InterestingLocalObjects[i].rainSound->StopAndForget();
					m_InterestingLocalObjects[i].rainSound = NULL;
					if (m_NumRainPropSounds > 0)
					{
						m_NumRainPropSounds--;
					}
				}
				if(m_InterestingLocalObjects[i].resoSoundInfo.resonanceSound)
				{
					m_InterestingLocalObjects[i].resoSoundInfo.resonanceSound->StopAndForget();
					m_InterestingLocalObjects[i].resoSoundInfo.resonanceSound = NULL;
				}
				m_InterestingLocalObjects[i].resoSoundInfo.resonanceIntensity = 0.f;
				m_InterestingLocalObjects[i].resoSoundInfo.attackTime = 0.f;
				m_InterestingLocalObjects[i].resoSoundInfo.releaseTime = 0.f;
				m_InterestingLocalObjects[i].resoSoundInfo.slowmoResoPhase = 0.f;
				m_InterestingLocalObjects[i].resoSoundInfo.slowmoResoVolLin = 0.f;
				m_InterestingLocalObjects[i].resoSoundInfo.resoSoundRef = g_NullSoundRef;
			}
		}
		PF_POP_TIMEBAR();
	}
}

void naEnvironment::UpdateLocalEnvironmentObjects()
{
	if(g_EnableLocalEnvironmentMetrics)
	{
		//Offset the resonance objects processing from the interesting objects
		if(!((fwTimer::GetSystemFrameCount() + 7) & 15))
		{
			for(u32 i=0; i< g_MaxResonatingObjects; i++)
			{
				//If the sound is no longer playing or the entity has been deleted, clear the entry out
				if(!m_ResonatingObjects[i].entity && !m_ResonatingObjects[i].resoSnd)
				{
					m_ResonatingObjects[i].Clear();
				}
			}	 
		}

		// update city ambiance metrics
		if(!(fwTimer::GetSystemFrameCount() & 31) || m_ForceAmbientMetricsUpdate)
		{
			const Vector3 north = Vector3(0.0f, 1.0f, 0.0f);
			const Vector3 east = Vector3(1.0f,0.0f,0.0f);
			const Matrix34 listenerMatrix = MAT34V_TO_MATRIX34(g_AudioEngine.GetEnvironment().GetPanningListenerMatrix());

			for(u32 i = 0 ; i < 4; i++)
			{
				m_VehicleSegmentCounts[i] = 0.f;
				m_HostilePedSegmentCount[i] = 0.f;
				m_ExteriorPedSegmentCounts[i] = 0.f;
				m_InteriorPedSegmentCounts[i] = 0.f;
				m_InteriorOccludedPedSegmentCounts[i] = 0.0f;
				m_ExteriorPedPanicCount[i] = 0.0f;
				m_InteriorPedPanicCount[i] = 0.0f;
			}

			u32 segment = ~0U;
			m_VehicleSegmentCounts[4] = 0.f;
			CVehicle::Pool* vehiclePool = CVehicle::GetPool();

			for(s32 i = 0; i < vehiclePool->GetSize(); i++)
			{
				CVehicle *veh = vehiclePool->GetSlot(i);
				if(veh)
				{
					f32 vehicleAmount = 1.0f;
					const Vector3 pos = VEC3V_TO_VECTOR3(veh->GetTransform().GetPosition());
					Vector3 vehToListener = pos - listenerMatrix.d;
					vehicleAmount *= m_VehicleDistanceSqToVehicleAmount.CalculateValue(vehToListener.Mag2());
					vehicleAmount *= m_VehicleSpeedToVehicleAmount.CalculateValue(veh->GetVehicleAudioEntity()->GetFwdSpeedLastFrame());

					if(vehToListener.Mag2() < (100.f*100.f))
					{
						m_VehicleSegmentCounts[4]+=vehicleAmount;
					}
					else
					{
						vehToListener.Normalize();
						const f32 dp = DotProduct(vehToListener, north);
						const f32 angle = AcosfSafe(dp);
						const f32 degrees = RtoD*angle;
						f32 actualDegrees;
						if(vehToListener.Dot(east) <= 0.0f)
						{
							actualDegrees = 360.0f - degrees;
						}
						else
						{
							actualDegrees = degrees;
						}
						if(segment != ~0U)
						{
							m_VehicleSegmentCounts[segment]+=vehicleAmount;
						}
						// defer using segment to prevent LHS
						segment = static_cast<u32>(((actualDegrees+45.f) / 90.0f)) % 4;
					}

				}
			}
			if(segment != ~0U)
			{
				m_VehicleSegmentCounts[segment]+=1.f;
			}

			fwPool<CPed> *pedPool = CPed::GetPool();
			f32 totalPedZPos = 0.0f;
			u32 totalNumPeds = 0;
			u32 totalMalePeds = 0;

			CInteriorProxy* pIntProxy = CPortalVisTracker::GetPrimaryInteriorProxy();
			s32 roomIdx = CPortalVisTracker::GetPrimaryRoomIdx();
			
			spdAABB roomBoundingBox;
			roomBoundingBox.Invalidate();

			if (pIntProxy)
			{
				CInteriorInst* pIntInst = pIntProxy->GetInteriorInst();
				if (pIntInst && roomIdx>0 && roomIdx!=INTLOC_INVALID_INDEX)
				{
					if(roomIdx < pIntInst->GetNumRooms())
					{
						pIntInst->GetRoomBoundingBox(roomIdx, roomBoundingBox);
					}
				}
			}

			for(s32 i = 0; i < pedPool->GetSize(); i++)
			{
				CPed *ped = pedPool->GetSlot(i);

				// Don't count law enforcement peds, otherwise we'll start getting eg. loads of walla during 5* wanted level shootouts
				// Also don't count animals, for obvious reasons
				if(ped && !ped->IsLocalPlayer() && ped->GetHealth() > 0.0f && ped->GetPedModelInfo()->GetPersonalitySettings().GetIsHuman())
				{
					const Vector3 pos = VEC3V_TO_VECTOR3(ped->GetTransform().GetPosition());
					Vector3 pedToListener = pos - listenerMatrix.d;

					// Bias against from peds at different heights to the listener when in an interior
					if(ped->GetIsInInterior())
					{
						pedToListener.SetZ(pedToListener.GetZ() * 4.0f);
					}

					if (pedToListener.Mag2() < (60.f*60.f)) // only include peds that are reasonably close
					{
						Vector3 normalisedPedToListener = pedToListener;
						normalisedPedToListener.Normalize();
						const f32 dp = DotProduct(normalisedPedToListener, north);
						const f32 angle = AcosfSafe(dp);
						const f32 degrees = RtoD*angle;
						f32 actualDegrees;
						if(normalisedPedToListener.Dot(east) <= 0.0f)
						{
							actualDegrees = 360.0f - degrees;
						}
						else
						{
							actualDegrees = degrees;
						}
						const u32 segment = static_cast<u32>(((actualDegrees+45.f) / 90.0f)) % 4;

						if(ped->GetPedIntelligence()->GetQueriableInterface()->GetHostileTarget() != NULL)
						{
							m_HostilePedSegmentCount[segment]+=1.f;
						}
						else if(ped->GetIsInInterior())
						{
							if(ped->GetAudioEntity()->GetEnvironmentGroup())
							{
								f32 occludedPedFactor = 1.0f - (Clamp(ped->GetAudioEntity()->GetEnvironmentGroup()->GetOcclusionValue()/0.5f, 0.0f, 1.0f));

								// Fade off the ped contribution as we approach the edge of the range
								occludedPedFactor *= (1.0f - Clamp((pedToListener.Mag() - 30.0f)/30.0f, 0.0f, 1.0f));
								m_InteriorOccludedPedSegmentCounts[segment] += occludedPedFactor;
								m_InteriorPedSegmentCounts[segment]+=1.f;
							}

							if(ped->GetIsPanicking())
							{
								m_InteriorPedPanicCount[segment]+=1.f;
							}
						}
						else if(ped->GetIsInExterior())
						{
							m_ExteriorPedSegmentCounts[segment]+=1.f;

							if(ped->GetIsPanicking())
							{
								m_ExteriorPedPanicCount[segment]+=1.f;
							}
						}

						if(ped->IsMale())
						{
							totalMalePeds++;
						}

						totalNumPeds++;
						totalPedZPos += pos.z;
					}
				}
			}

			if(totalNumPeds > 0)
			{
				m_AveragePedZPos = totalPedZPos/totalNumPeds;
				m_PedFractionMale = totalMalePeds/(f32)totalNumPeds;
			}

			m_ForceAmbientMetricsUpdate = false;
		}
	}
}

void naEnvironment::UpdateLinkedRoomList(CInteriorInst* intInst)
{
	ClearLinkedRooms();

	if (intInst)
	{
		CMloModelInfo *modelInfo = intInst->GetMloModelInfo();
		s32 roomPortalIdx = intInst->GetNumPortalsInRoom(INTLOC_ROOMINDEX_LIMBO) - 1;

		while(roomPortalIdx >= 0)
		{
			// Need the global portal index in order to get the portal data
			s32 globalPortalIdx = intInst->GetPortalIdxInRoom(INTLOC_ROOMINDEX_LIMBO, roomPortalIdx);

			CPortalFlags portalFlags = modelInfo->GetPortalFlags(globalPortalIdx);

			if(portalFlags.GetIsLinkPortal())
			{
				// If this is a link portal, update our list of rooms to not occlude from
				// this is a valid linked portal to another MLO - update tracker accordingly
				// Need to use the room specific portal index in this function
				CPortalInst* portalInst = intInst->GetMatchingPortalInst(INTLOC_ROOMINDEX_LIMBO, roomPortalIdx);
				if(portalInst)
				{
					// update using info from the linked portal
					CInteriorInst* destIntInst = NULL;
					s32	destPortalIdx = -1;

					// get data about linked interior through this portal 
					portalInst->GetDestinationData(intInst, destIntInst, destPortalIdx);
					if(destIntInst)
					{
						// need to get the room index from the new interior and portal
						s32 linkedRoomIdx = destIntInst->GetDestThruPortalInRoom(INTLOC_ROOMINDEX_LIMBO, destPortalIdx);		//entry/exit portal so we know it's room INTLOC_ROOMINDEX_LIMBO
						if(linkedRoomIdx != INTLOC_INVALID_INDEX)
						{
							AddLinkedRoom(destIntInst, linkedRoomIdx);
						}
					}
				}
			}

			roomPortalIdx--;
		}
	}
}

void naEnvironment::AddLinkedRoom(CInteriorInst* intInst, s32 roomIdx)
{
	if(intInst)
	{
		for (u32 i=0; i<AUD_MAX_LINKED_ROOMS; i++)
		{
			if(CInteriorProxy::GetFromLocation(m_LinkedRoom[i]) == intInst->GetProxy() && m_LinkedRoom[i].GetRoomIndex() == roomIdx)
			{
				// already stored this frame
				return;
			}
			else if (!m_LinkedRoom[i].IsValid())
			{
				m_LinkedRoom[i] = CInteriorInst::CreateLocation(intInst, roomIdx);
				break;
			}
		}
	}
}

void naEnvironment::ClearLinkedRooms()
{
	for (u32 i=0; i<AUD_MAX_LINKED_ROOMS; i++)
	{
		m_LinkedRoom[i].MakeInvalid();
	}
}

bool naEnvironment::IsRoomInLinkedRoomList(const InteriorSettings* linkedInteriorSettings, const InteriorRoom* linkedIntRoom)
{
	if(naVerifyf(linkedInteriorSettings && linkedIntRoom, "Querying invalid room in audio check"))
	{
		for (u32 i=0; i<AUD_MAX_LINKED_ROOMS; i++)
		{
			if(m_LinkedRoom[i].IsValid())
			{
				const InteriorSettings* intSettings = NULL;
				const InteriorRoom* intRoom = NULL;
				GetInteriorSettingsForInteriorLocation(m_LinkedRoom[i], intSettings, intRoom);
				if(intSettings == linkedInteriorSettings && intRoom == linkedIntRoom)
				{
					return true;
				}
			}
		}
	}
	return false;
}
 
void naEnvironment::SpikeResonanceObjects(const f32 impulse, const Vector3 &pos, const f32 maxDist,const CEntity* entity)
{
	for(u32 i = 0; i < g_MaxResonatingObjects ; i++)
	{
		if(m_InterestingLocalObjects[i].object && m_InterestingLocalObjects[i].resoSoundInfo.resoSoundRef != g_NullSoundRef)
		{
			Vec3V objectPos = m_InterestingLocalObjects[i].object->GetTransform().GetPosition();
			if(m_InterestingLocalObjects[i].object->GetCurrentPhysicsInst())
			{
				objectPos = m_InterestingLocalObjects[i].object->GetCurrentPhysicsInst()->GetCenterOfMass();
			}
			if(entity && entity != m_InterestingLocalObjects[i].object)
			{		
				// if we have an entity is because we only want to spike that entity alone.
				continue;
			}
			const f32 dist = (VEC3V_TO_VECTOR3(objectPos) - pos).Mag();
			// Spike the resonanceIntensity based on the distance
			f32 intensitySpike = 1.f - Min((dist <= sm_DistancePlateau ? 0.f : dist)/maxDist, 1.f);
			intensitySpike *= impulse;
			intensitySpike = Min(intensitySpike, 1.f);
			const f32 attackTime = m_InterestingLocalObjects[i].resoSoundInfo.attackTime;
			const f32 releaseTime = m_InterestingLocalObjects[i].resoSoundInfo.releaseTime;
			m_InterestingLocalObjects[i].resoSoundInfo.resonanceIntensity = ProcessResoVolume(intensitySpike,attackTime,releaseTime,m_InterestingLocalObjects[i].resoSoundInfo.resonanceIntensity);
		}
	}
}

f32 naEnvironment::GetVectorToNorthXYAngle(const Vector3& v)
{
	Vector3 XYVecNormalized = v;
	XYVecNormalized.z = 0.0f;
	XYVecNormalized.NormalizeFast();
	f32 cosAngle = XYVecNormalized.Dot(g_Directions[AUD_AMB_DIR_NORTH]);
	cosAngle = Max(Min(cosAngle, 1.0f), -1.0f);
	f32 angle = AcosfSafe(cosAngle);
	angle *= (360.0f*0.5f/PI); // Switch from radians to degrees
	// But it might be 360-that if we're in the left side - do a cos with right to see
	f32 right = XYVecNormalized.Dot(g_Directions[AUD_AMB_DIR_EAST]);
	if (right<0.0f)
	{
		angle = 359.9f - angle;
	}
	return angle;
}

f32 naEnvironment::GetVectorToUpAngle(const Vector3& v)
{
	const Vector3 up = Vector3(0.0f, 0.0f, 1.0f);
	Vector3 vNormalized = v;
	vNormalized.NormalizeFast();
	f32 cosUp = vNormalized.Dot(up);
	cosUp = Max(Min(cosUp, 1.0f), -1.0f);
	f32 angleUp = AcosfSafe(cosUp);
	angleUp *= (360.0f*0.5f/PI); // convert radians to degrees
	return angleUp;
}

void naEnvironment::UpdateEchoPositions()
{
// @OCC TODO - Determine best echo metrics calculation
/*
	// Update the suitability of each direction to having echoes from it
	// Centered around each direction, look at lower and upper, and each side, with half importance.
	Vector3 centrePosition = g_AudioEngine.GetEnvironment().GetPanningListenerPosition();

	for (s32 i=0; i<8; i++)
	{
		f32 echoGoodnessFactor = 0.0f;
		f32 bestDistance = 0.0f;
		for (s32 j=-1; j<2; j++)
		{
			u32 index = (i+j+8) % 8;
			f32 lowerContribution = m_DistanceToEchoCurve.CalculateValue(m_LevelWallDistances[index*3]); 
			f32 upperContribution = m_DistanceToEchoCurve.CalculateValue(m_UpperWallDistances[index]);
			if (j!=0)
			{
				lowerContribution *= 0.33f;
				upperContribution *= 0.33f;
			}
			else
			{
				// Average our normals
				Vector3 lowerNormal = m_LevelReflectionNormal[i];
				lowerNormal.z = 0.0f;
				lowerNormal.NormalizeSafe();
				Vector3 upperNormal = m_UpperReflectionNormal[i];
				upperNormal.z = 0.0f;
				upperNormal.NormalizeSafe();
				if ((lowerContribution+upperContribution)>0.01f)
				{
					upperNormal.Scale(upperContribution/(lowerContribution+upperContribution));
					lowerNormal.Scale(lowerContribution/(lowerContribution+upperContribution));
					m_EchoDirection[i] = lowerNormal + upperNormal;
				}
				else
				{
					m_EchoDirection[i].Set(1.0f);
					m_EchoDirection[i].Normalize();
				}
			}
			echoGoodnessFactor += (lowerContribution + upperContribution);
			bestDistance += (lowerContribution * m_LevelWallDistances[index]);
			bestDistance += (upperContribution * m_UpperWallDistances[index]);
		}
		// That's up-and-down, 1/3 from each side, so a total of (1+2/3)*2 = 3.33 zero-to-ones
		echoGoodnessFactor /= 3.33f;
		m_EchoSuitability[i] = echoGoodnessFactor;
		bestDistance /= 3.33f;
		// But we also need to scale the distance up by the total echo goodness, to normalise it :-/
		bestDistance = Selectf(echoGoodnessFactor*-1.0f, 25.0f, bestDistance/echoGoodnessFactor);

		if(bestDistance < 0)
		{
			bestDistance = 25.0;
			m_EchoSuitability[i] = 0.0f;
		}

		m_EchoDistance[i] = bestDistance;
		Vector3 offset = m_RelativeScanPositions[i*3];
		offset.Scale(bestDistance);
		m_EchoPosition[i] = centrePosition + offset;
	}
*/


	// Update the suitability of each direction to having echoes from it
	// Centered around each direction, look at lower and upper, and each side, with half importance.
	Vector3 centrePosition = VEC3V_TO_VECTOR3(g_AudioEngine.GetEnvironment().GetPanningListenerPosition());

	for (u8 i=0; i<8; i++)
	{
		f32 interpolatedDistance = 0.0f;
		Vector3 vEchoDirection(0.0f, 0.0f, 0.0f);

		// We use 4 probes per N, NE, E, ...direction.  1 on 15 degrees to either side of the main level radial, and the upper probe on the main radial.
		// So for N, we use the NNW, N, NNE level probes, and the N upper probe
		s32 levelLeftProbeIdx = (i*3 + -1 + 24) % 24;
		s32 levelCenterProbeIdx = (i*3 + 24) % 24;
		s32 levelRightProbeIdx = (i*3 + 1 + 24) % 24;
		s32 upperProbeIdx = i;

		Vector3 vProbeNormals[4];
		vProbeNormals[0] = m_LevelReflectionNormal[levelLeftProbeIdx];
		vProbeNormals[1] = m_LevelReflectionNormal[levelCenterProbeIdx];
		vProbeNormals[2] = m_LevelReflectionNormal[levelRightProbeIdx];
		vProbeNormals[3] = m_UpperReflectionNormal[upperProbeIdx];

		f32 probeDistances[4];
		probeDistances[0] = audNorthAudioEngine::GetOcclusionManager()->GetWallDistance(AUD_OCC_PROBE_ELEV_W, levelLeftProbeIdx);
		probeDistances[1] = audNorthAudioEngine::GetOcclusionManager()->GetWallDistance(AUD_OCC_PROBE_ELEV_W, levelCenterProbeIdx);
		probeDistances[2] = audNorthAudioEngine::GetOcclusionManager()->GetWallDistance(AUD_OCC_PROBE_ELEV_W, levelRightProbeIdx);
		probeDistances[3] = audNorthAudioEngine::GetOcclusionManager()->GetWallDistance(AUD_OCC_PROBE_ELEV_WWN, upperProbeIdx);

		f32 totalWeight = 0.0f;
		f32 probeWeights[4];
		for(s32 probe = 0; probe < 4; probe++)
		{
			probeWeights[probe] = m_DistanceToEchoCurve.CalculateValue(probeDistances[probe]); 
			totalWeight += probeWeights[probe];
		}

		// Interpolate based on weights to get the rest of the data (distance and normal)
		if(totalWeight > 0.0f)
		{
			for(s32 probe = 0; probe < 4; probe++)
			{
				// Should we factor in the z component?
				f32 probeWeight = probeWeights[probe] / totalWeight;

				interpolatedDistance += probeDistances[probe] * probeWeight;
				if(probeWeight > 0.0f)
				{
					vProbeNormals[probe].Scale(probeWeight);
					vEchoDirection += vProbeNormals[probe];
				}
			}
		}
		else
		{
			vEchoDirection = audNorthAudioEngine::GetOcclusionManager()->GetRelativeScanPosition(i*3);
			vEchoDirection.Scale(-1.0f);
			interpolatedDistance = (probeDistances[0] + probeDistances[1] + probeDistances[2] + probeDistances[3]) * 0.25f;
		}

		Vector3 offset = audNorthAudioEngine::GetOcclusionManager()->GetRelativeScanPosition(i*3);
		offset.Scale(interpolatedDistance);
		m_EchoPosition[i] = centrePosition + offset;

		// Base the suitability on all the probes, so if only 1 is suitable it's not that suitable a location overall
		m_EchoSuitability[i] = totalWeight * 0.25f;
		m_EchoDistance[i] = interpolatedDistance;
		m_EchoDirection[i] = vEchoDirection;

		audWeaponAudioEntity::SetReportGoodness(i, m_EchoSuitability[i]);
	}

#if __BANK
	DrawWeaponEchoes();
#endif
}

void naEnvironment::GetEchoSuitablilityForSourcePosition(const Vector3& sourcePosition, f32* suitability)
{
	if (!suitability)
	{
		naErrorf("Null ptr suitability passed into GetEchoSuitabilityForSourcePosition");
		return;
	}

	Vector3 listenerPosition = VEC3V_TO_VECTOR3(g_AudioEngine.GetEnvironment().GetVolumeListenerPosition());
	// Work out angle of incidence stuff, and multiply those factors by the standard suitability factors.
	for (s32 i=0; i<8; i++)
	{
		Vector3 echoPoint = m_EchoPosition[i];
		Vector3 echoPointNormal = m_EchoDirection[i];
		Vector3 echoToListener = listenerPosition - echoPoint;
		Vector3 echoToSource = sourcePosition - echoPoint;
		// Flatten it - we do all this in 2d
		echoToListener.z = 0.0f;
		echoToListener.NormalizeSafe();
		echoToSource.z = 0.0f;
		echoToSource.NormalizeSafe();
		// Reflect listener direction in echo normal
		// Reflect listener direction in echo normal
		Quaternion listenerToNormalRotation;
		bool success = listenerToNormalRotation.FromVectors(echoToListener, echoPointNormal);
		if (success)
		{
			Vector3 listenerReflection; // I think this is wrong in rage
			listenerToNormalRotation.Transform(echoPointNormal, listenerReflection);
			// Now see how similar that direction is to the source direction
			f32 sourceAndReflectedListenerDotProduct = echoToSource.Dot(listenerReflection);
			//rk ported the next 2 lines from RDR2  11/12/12
			if(sourceAndReflectedListenerDotProduct < 0)
				sourceAndReflectedListenerDotProduct *= -1;
			// Map the dot product to a suitability scaling factor
			f32 angleSuitability = m_AngleOfIncidenceToSuitabilityCurve.CalculateValue(sourceAndReflectedListenerDotProduct);
			suitability[i] = m_EchoSuitability[i] * angleSuitability;
		}
		else
		{
			suitability[i] = 0.0f;
		}
	}
}

s32 naEnvironment::GetBestEchoForSourcePosition(const Vector3& sourcePosition, s32* oppositeEcho)
{
	if (!g_EnableLocalEnvironmentMetrics)
	{
		if (oppositeEcho)
		{
			*oppositeEcho = -1;
		}
		return -1;
	}

	f32 echoSuitablility[8];
	GetEchoSuitablilityForSourcePosition(sourcePosition, &echoSuitablility[0]);
	// Pick the best one
	s32 bestEcho = -1;
	f32 bestSuitability = 0.0f;
	s32 bestOppositeEcho = -1;
	for (s32 i=0; i<8; i++)
	{
		if (echoSuitablility[i] >= bestSuitability)
		{
			bestSuitability = echoSuitablility[i];
			bestEcho = i;
		}
	}
	if (bestEcho>-1)
	{
		// Once we've found our best echo, look for the best on the 'other side' and play one from there too.
		f32 bestOppositeSuitability = 0.0f;
		for (s32 i=3; i<6; i++)
		{
			u32 echoIndex = (bestEcho + i) % 8;
			if (echoSuitablility[echoIndex] >= bestOppositeSuitability)
			{
				bestOppositeSuitability = echoSuitablility[echoIndex];
				bestOppositeEcho = echoIndex;
			}
		}
	}
	if (oppositeEcho)
	{
		*oppositeEcho = bestOppositeEcho;
	}
	return bestEcho;
}

void naEnvironment::CalculateEchoDelayAndAttenuation(u32 echoIndex, const Vector3 &sourcePosition, u32* returnPredelay, f32* returnExtraAttenuation, Vector3* projectedPosition, u32 depth)
{
	f32		LocalEchoSuitability[8];
	f32		LocalEchoDistance[8];
	Vector3 LocalEchoPositions[8];

	const Vector3 vListenerPos(VEC3V_TO_VECTOR3(g_AudioEngine.GetEnvironment().GetVolumeListenerPosition()));

	if(depth==0)
	{
		for(s32 i=0; i<8; ++i)
		{
			LocalEchoSuitability[i] = m_EchoSuitability[i];
			LocalEchoDistance[i] = m_EchoDistance[i];
			LocalEchoPositions[i].Set(m_EchoPosition[i]);
		}
	}
	else
	{
		s32 offset = audEngineUtil::GetRandomInteger()%8;
		for(s32 i=0; i<8; ++i)
		{
			s32 index = (i + offset)%8;
			LocalEchoSuitability[i] = m_EchoSuitability[index];
			LocalEchoDistance[i] = Min(20.0f, m_EchoDistance[index]);
			LocalEchoPositions[i].Set(m_EchoPosition[index]-vListenerPos);
			LocalEchoPositions[i].Scale(LocalEchoDistance[i]);
			LocalEchoPositions[i].Add(sourcePosition);
		}
	}

	f32 listenerDistanceToEcho = LocalEchoDistance[echoIndex];
	f32 sourceDistanceToEcho = sourcePosition.Dist(LocalEchoPositions[echoIndex]);
	f32 sourceDistanceToListener = sourcePosition.Dist(vListenerPos);

	f32 distanceDifference = listenerDistanceToEcho + sourceDistanceToEcho - sourceDistanceToListener;

	// Work out predelay
	u32 predelay = (u32) m_DistanceToDelayCurve.CalculateValue(distanceDifference);

	// Work out extra attenuation - want to make it silent if the source is co-located with the echo position, otherwise it'll sound odd.
	// Plus, we attenuate echoes depending on how suitable they are.
	// We project the echo back past the echo point, so that it gets distance attenuated correctly anyway - that way we get reverb and filtering for free.
	//	f32 extraDistanceAttenuation = m_DeafeningDistanceRolloffCurve.CalculateValue(sourceDistanceToEcho / rolloffScale);
	f32 coLocatedAttenuation = m_SilentWhenEchoCoLocatedCurve.CalculateValue(sourceDistanceToEcho);
	f32 suitabilityAttenuation = m_EchoGoodnessDbVolumeCurve.CalculateValue(m_EchoSuitability[echoIndex]);
	f32 extraAttenuation = coLocatedAttenuation + suitabilityAttenuation;

	Vector3 projectedEcho = LocalEchoPositions[echoIndex] - vListenerPos;
	projectedEcho.Normalize();
	projectedEcho.Scale(sourceDistanceToEcho);
	projectedEcho.Add(LocalEchoPositions[echoIndex]);

	if (returnPredelay && returnExtraAttenuation)
	{
		*returnPredelay = predelay;
		*returnExtraAttenuation = extraAttenuation;
		*projectedPosition = projectedEcho;
		//		Warningf("dd: %f; eda: %f; cla: %f; sa: %f; ex: %f; delay: %d", distanceDifference, extraDistanceAttenuation, coLocatedAttenuation, suitabilityAttenuation, extraAttenuation, predelay);
	}
}

void naEnvironment::GetInteriorGunSoundMetricsForExterior(f32& returnVolume, f32& returnHold)
{
#if __BANK
	m_OutdoorInteriorWeaponVolCurve.Init("REV_WET_TO_OUTDOOR_INTERIOR_WEAPON_VOLUME");
	m_OutdoorInteriorWeaponHoldCurve.Init("REV_SIZE_TO_OUTDOOR_INTERIOR_WEAPON_HOLD");
#endif

	f32 normalizedReverbWet = ((f32)((m_SourceEnvironmentMetrics[0] & 12) >> 2))/3.0f;
	f32 normalizedReverbSize = ((f32)((m_SourceEnvironmentMetrics[0] & 3)))/3.0f;

	if(m_SourceEnvironmentMetrics[0] == 0)
	{
		returnVolume = m_OutdoorInteriorWeaponVolCurve.CalculateValue(0.0f);
		returnHold = m_OutdoorInteriorWeaponHoldCurve.CalculateValue(0.0f);
	}
	else if(normalizedReverbWet == 0)
	{
		returnVolume = m_OutdoorInteriorWeaponVolCurve.CalculateValue(0.15f);
		returnHold = m_OutdoorInteriorWeaponHoldCurve.CalculateValue(normalizedReverbSize);
	}
	else
	{
		returnVolume = m_OutdoorInteriorWeaponVolCurve.CalculateValue(normalizedReverbWet);
		returnHold = m_OutdoorInteriorWeaponHoldCurve.CalculateValue(normalizedReverbSize);
	}
}


void naEnvironment::SetLevelReflectionNormal(const Vector3& levelReflectionNormal, s32 radialIndex)
{
	if(naVerifyf(radialIndex >= 0 && radialIndex < g_audNumLevelProbeRadials, 
		"naEnvironment::SetLevelReflectionNormal Trying to set reflectionNormal data out of bounds at index: %d", radialIndex))
	{
		m_LevelReflectionNormal[radialIndex] = levelReflectionNormal;
	}
}

void naEnvironment::SetUpperReflectionNormal(const Vector3& upperReflectionNormal, s32 radialIndex)
{
	if(naVerifyf(radialIndex >= 0 && radialIndex < g_audNumMidProbeRadials, 
		"naEnvironment::SetUpperReflectionNormal Trying to set reflectionNormal data out of bounds at index: %d", radialIndex))
	{
		m_UpperReflectionNormal[radialIndex] = upperReflectionNormal;
	}
}

void naEnvironment::UpdateLocalReverbEnvironment()
{
	// Get up-to-date navmesh numbers
	UpdateNavmeshSourceEnvironment();
	// Work out how reverby each speaker direction is.
	// Draw lines out from the camera in the direction of the speaker - then dot that vector with each of the local offsets, to see how much
	// they contribute - quite a lot like the panning algorithm
	
	f32 speakerReverbWets[4][3];
	for (u32 i=0; i<4; i++)
	{
		for (u32 j=0; j<g_NumSourceReverbs; j++)
		{
			speakerReverbWets[i][j] = 0.0f;
		}
	}

	Vector3 speakerPosition[4];
	speakerPosition[0] = Vector3(-0.7071f, 0.7071f, 0.0f);
	speakerPosition[1] = Vector3(0.7071f, 0.7071f, 0.0f);
	speakerPosition[2] = Vector3(-0.7071f, -0.7071f, 0.0f);
	speakerPosition[3] = Vector3(0.7071f, -0.7071f, 0.0f);
	Matrix34 fullListenerMatrix = MAT34V_TO_MATRIX34(g_AudioEngine.GetEnvironment().GetPanningListenerMatrix());
//	listenerMatrix.d.Zero();
//	Matrix34 fullListenerMatrix = FindPlayerPed()->GetMatrix();
	Matrix34 listenerMatrix = fullListenerMatrix;
	listenerMatrix.d.Zero();

	f32 elevation[MAX_SURROUNDING_POLYS_FOR_AUDIO_PROPERTIES];
	Vector3 positionOnUnitCircle[MAX_SURROUNDING_POLYS_FOR_AUDIO_PROPERTIES];
	f32 contributionScaling[MAX_SURROUNDING_POLYS_FOR_AUDIO_PROPERTIES];
	for (u32 k=0; k < m_NumValidPolys; k++)
	{
		// Where is the poly relative to us
		Vector3 polyRelativePosition = m_SourceEnvironmentPolyCentroids[k];
		fullListenerMatrix.UnTransform(polyRelativePosition);

		f32 distanceFromCamera = polyRelativePosition.Mag();
		Vector3 positionOnUnitSphere = polyRelativePosition;
		positionOnUnitSphere.NormalizeSafe();
		positionOnUnitCircle[k] = positionOnUnitSphere;
		positionOnUnitCircle[k].z = 0.f;
		positionOnUnitCircle[k].NormalizeSafe();
		// Get the angle between the pos on unit sphere and unit circle - and try linearly interping between our headline contr.
		elevation[k] = positionOnUnitCircle[k].Dot(positionOnUnitSphere);
		if (elevation[k] < -0.01f || elevation[k] > 1.01f)
		{
			elevation[k] = 0.0f; // it's almost certainly directly above or below, which is elevation=0.0f
		}
		// Scale contribution by the distance away from the camera
		f32 distanceScale = m_DistanceToListenerReverbCurve.CalculateValue(distanceFromCamera);
		f32 areaScale     = m_AreaToListenerReverbCurve.CalculateValue(m_SourceEnvironmentPolyAreas[k]);
		contributionScaling[k] = distanceScale * areaScale;
	}

	const float scriptOverrideScalar = g_ScriptAudioEntity.IsFlagSet(audScriptAudioFlags::ListenerReverbDisabled) ? 0.f : 1.f;

	for (u32 i=0; i<4; i++)
	{
		// For this speaker, sum all our neighbouring poly's reverb contribution, scaling the contribution up with poly area,
		// up with angle alignment, and down with distance - all done with curves.
		// Probably also need to add the one you're actually over in a special cased way - currently just include it as normal,
		// since it's centre won't be totally aligned with the listener.
		f32 contribution[MAX_SURROUNDING_POLYS_FOR_AUDIO_PROPERTIES];
		f32 totalContribution = 0.0f;
		for (u32 k=0; k<m_NumValidPolys; k++)
		{
			// Work out this poly's contribution for this speaker
			f32 dotProd = positionOnUnitCircle[k].Dot(speakerPosition[i]);
			// Don't care about ones more than 90degs away
			dotProd = Max(0.0f, dotProd);
			if (k==0)
			{
				// it's the poly we're actually in
				dotProd = 1.0f;
			}
			// Linearly interp between 1/num_speakers and primary contribution as elevation goes from 0 (90degs) to 1 (flat)
			contribution[k] = ((1.0f - elevation[k]) * (1.0f/4.0f)) + (elevation[k] * dotProd);

			// Scale contribution by area and distance scales
			contribution[k] *= contributionScaling[k];
			totalContribution += contribution[k];
		}
		for (u32 k=0; k<m_NumValidPolys; k++)
		{
			for (u32 j=0; j<g_NumSourceReverbs; j++)
			{
				speakerReverbWets[i][j] += (contribution[k] * m_SourceEnvironmentPolyReverbWet[k][j] / totalContribution);
			}
		}
	}

	// Factor in the ambient environment zones.
	const EnvironmentRule* avgEnvRule = g_AmbientAudioEntity.GetAmbientEnvironmentRule();
	const f32 envRuleReverbScale = g_AmbientAudioEntity.GetAmbientEnvironmentRuleReverbScale();
	if(avgEnvRule && envRuleReverbScale != 0.0f)
	{
		const f32 scaledReverbDamp = avgEnvRule->ReverbDamp * envRuleReverbScale;

		for (u32 i=0; i<4; i++)
		{
			speakerReverbWets[i][0] *= 1.0f - scaledReverbDamp;	// Small
			speakerReverbWets[i][1] *= 1.0f - scaledReverbDamp;	// Medium
			speakerReverbWets[i][2] *= 1.0f - scaledReverbDamp;	// Large

			speakerReverbWets[i][0] = Max(speakerReverbWets[i][0], avgEnvRule->ReverbSmall * envRuleReverbScale);
			speakerReverbWets[i][1] = Max(speakerReverbWets[i][1], avgEnvRule->ReverbMedium * envRuleReverbScale);
			speakerReverbWets[i][2] = Max(speakerReverbWets[i][2], avgEnvRule->ReverbLarge * envRuleReverbScale);
		}
	}

	// Smooth our final stored ones
	for (u32 i=0; i<4; i++)
	{
		for (u32 j=0; j<g_NumSourceReverbs; j++)
		{
			m_SpeakerReverbWets[i][j] = g_SpeakerReverbDamping * m_SpeakerReverbWetSmoothers[i][j].CalculateValue(scriptOverrideScalar * speakerReverbWets[i][j], audNorthAudioEngine::GetCurrentTimeInMs());
		}
	}

#if __BANK
	if (g_DebugListenerReverb)
	{
		for (u32 i=0; i<4; i++)
		{
			for (u32 j=0; j<3; j++)
			{
				m_SpeakerReverbWetSmoothers[i][j].SetRate(g_SpeakerReverbSmootherRate);
			}
		}

		static const char *meterNames[] = {"s", "m", "l"};
		static f32 x[4] = {300, 700, 300, 700};
		static f32 y[4] = {300, 300, 600, 600};
		static audMeterList meterList[4];
		for (u32 i=0; i<4; i++)
		{
			meterList[i].left = x[i];
			meterList[i].bottom = y[i];
			meterList[i].width = 100.f;
			meterList[i].height = 200.f;
			meterList[i].values = &(m_SpeakerReverbWets[i][0]);
			meterList[i].names = meterNames;
			meterList[i].numValues = 3;
			audNorthAudioEngine::DrawLevelMeters(&(meterList[i])); 
		}

		for(u32 k = 0; k < m_NumValidPolys; k++)
		{
			grcDebugDraw::Sphere(m_SourceEnvironmentPolyCentroids[k], 0.25f, Color_AliceBlue);
			char buf[128];
			formatf(buf, "%d: %f, %f", k, m_SourceEnvironmentPolyReverbWet[k], m_SourceEnvironmentPolyAreas[k]);
			grcDebugDraw::Text(m_SourceEnvironmentPolyCentroids[k], Color_AliceBlue, buf);
		}
	}
	if(sm_DebugCoveredObjects)
	{
		for (u32 i = 0; i< m_InterestingLocalObjects.GetMaxCount(); i++)
		{
			if(m_InterestingLocalObjects[i].object && m_InterestingLocalObjects[i].envGroup)// && m_InterestingLocalObjects[i].underCover)
			{
				Vec3V objectPos = m_InterestingLocalObjects[i].object->GetTransform().GetPosition();
				if(m_InterestingLocalObjects[i].object->GetCurrentPhysicsInst())
				{
					objectPos = m_InterestingLocalObjects[i].object->GetCurrentPhysicsInst()->GetCenterOfMass();
				}
				char buf[128];
				formatf(buf, "%s COVER", (m_InterestingLocalObjects[i].envGroup->IsUnderCover()) ? "UNDER " : "NOT UNDER ");
				//formatf(buf, "%s COVER", (m_InterestingLocalObjects[i].underCover) ? "UNDER " : "NOT UNDER ");
				grcDebugDraw::Text(objectPos, Color_AliceBlue, buf);
				//grcDebugDraw::Sphere(m_InterestingLocalObjects[i].object->GetTransform().GetPosition(),1.f,Color_green);
			}
		}
		for(u32 k = 0; k < m_NumValidPolys; k++)
		{
			if(m_CoveredPolys.IsSet(k))
			{
				grcDebugDraw::Sphere(m_SourceEnvironmentPolyCentroids[k],sqrt(m_SourceEnvironmentPolyAreas[k]/PI)*sm_RadiusScale, Color32(0,255,0,100));
			}
		}
	}
#endif

	g_AudioEngine.GetEnvironment().SetSpeakerReverbWets(m_SpeakerReverbWets);
}

void naEnvironment::UpdateNavmeshSourceEnvironment()
{
	u32 roomToneHash = g_NullSoundHash;
	u32 rainType = RAIN_TYPE_NONE;
	// update our default one

    if (!g_DebugRoomSettings)
    {
        return;
    }

	InteriorRoom* room = audNorthAudioEngine::GetObject<InteriorRoom>(g_DebugRoomSettings->Room[0].Ref);

	if(room)
	{
		room->ReverbSmall = g_DebugRoomSettingsReverbSmall;
		room->ReverbMedium = g_DebugRoomSettingsReverbMedium;
		room->ReverbLarge = g_DebugRoomSettingsReverbLarge;
	}

	// This is almost identical to the occlusion group source environment calculations, but we need to store off
	// a bunch more info, and it's wasteful to keep that in pooled OcGroups, just for this one thing.
	u32 timeInMs = audNorthAudioEngine::GetCurrentTimeInMs();
//	Matrix34 listenerMatrix = g_AudioEngine.GetEnvironment().GetPanningListenerMatrix();
	bool playerIsInAnInterior = false;
	bool playerIsInASubwayStationOrSubwayTunnel = false;
	bool playerIsInASubwayOrTunnel = false;
	Vector3 listenerPosition = audNorthAudioEngine::GetMicrophones().GetLocalEnvironmentScanPosition();
	
	CInteriorInst* pIntInst = NULL;
	s32 roomIdx = -1;
#if __DEV
    bool usingPlayer = false;
#endif
	if ((m_IsListenerInHead || g_UsePlayer) && !audNorthAudioEngine::GetOcclusionManager()->GetIsPortalOcclusionEnabled())
	{
#if __DEV
        usingPlayer = true;
#endif
		CPed* player = CGameWorld::FindLocalPlayer();
		if(player && player->GetIsInInterior())
		{
			pIntInst = CInteriorInst::GetInteriorForLocation(player->GetInteriorLocation());
			roomIdx = player->GetInteriorLocation().GetRoomIndex();
		}
	}
	else
	{
		pIntInst = CPortalVisTracker::GetPrimaryInteriorInst();
		roomIdx = CPortalVisTracker::GetPrimaryRoomIdx();
	}

	if (pIntInst && roomIdx>0 && roomIdx!=INTLOC_INVALID_INDEX)
	{
		playerIsInAnInterior = true;
		if (pIntInst->IsSubwayMLO())
		{
			playerIsInASubwayOrTunnel = true;
		}
	}
	/*
	int32 interiorId = -1;
	if (player)
	{
		interiorId = player->GetInteriorInstId();
	}
	CInteriorInst* pIntInst = NULL;
	if (player && player->m_nFlags.bInMloRoom)
	{
		int32 roomId = player->GetInteriorRoomId();
		if (roomId>=0 && roomId!=INTLOC_INVALID_INDEX)
		{
			playerIsInAnInterior = true;
			pIntInst = CInteriorInst::GetPool()->GetAt(interiorId);
			if (pIntInst && pIntInst->IsSubwayMLO())
			{
				playerIsInASubway = true;
			}
		}
	}
	*/
	if (pIntInst && pIntInst->IsPopulated() && roomIdx>0 && roomIdx!=INTLOC_INVALID_INDEX)// && !pIntInst->IsSubwayMLO())
	{
		g_AudioEngine.GetEnvironment().SetIsListenerInInterior(true, 0);
		if (m_SourceEnvironmentPathHandle != PATH_HANDLE_NULL)
		{
			CPathServer::CancelRequest(m_SourceEnvironmentPathHandle);
			m_SourceEnvironmentPathHandle = PATH_HANDLE_NULL;
		}
		m_NumValidPolys = 1;
		m_SourceEnvironmentPolyCentroids[0] = VEC3V_TO_VECTOR3(g_AudioEngine.GetEnvironment().GetPanningListenerPosition(0));
		m_SourceEnvironmentPolyAreas[0] = 1.0f;
		// Get the room we're in
		const InteriorSettings* interiorSettings = NULL;
		const InteriorRoom* room = NULL;
		GetInteriorSettingsForInterior(pIntInst, roomIdx, interiorSettings, room);
		naCErrorf(interiorSettings, "Couldn't get InteriorSettings for the room we're in");

		if (interiorSettings && room)
		{
			m_SourceEnvironmentMetrics[0] = 0;
			f32 reverbWets[g_NumSourceReverbs];
			reverbWets[0] = room->ReverbSmall;
			reverbWets[1] = room->ReverbMedium;
			reverbWets[2] = room->ReverbLarge;
			for (u32 j=0; j<g_NumSourceReverbs; j++)
			{
				m_SourceEnvironmentPolyReverbWet[0][j] = reverbWets[j];
			}
			roomToneHash = room->RoomToneSound;
			
			rainType = room->RainType;
			if (room->InteriorType==INTERIOR_TYPE_ROAD_TUNNEL ||
				room->InteriorType==INTERIOR_TYPE_SUBWAY_TUNNEL ||
				room->InteriorType==INTERIOR_TYPE_SUBWAY_STATION)
			{
				// override the sourceEnvironment, to save setting it, and still get speech echos
				m_SourceEnvironmentMetrics[0] = 15;
			}
			else if (room->InteriorType==INTERIOR_TYPE_SUBWAY_ENTRANCE)
			{
				// override the sourceEnvironment, to save setting it, and still get speech echos, but a bit less than in the subway proper
				m_SourceEnvironmentMetrics[0] = 10;
			}
			if (room->InteriorType==INTERIOR_TYPE_SUBWAY_TUNNEL ||
				room->InteriorType==INTERIOR_TYPE_SUBWAY_STATION)
			{
				playerIsInASubwayStationOrSubwayTunnel = true;
			}
		}

		// @OCC TODO - Should we switch the CInteriorInst* and InteriorRoomIdx to instead use the fwInteriorLocation?
		// Do not update the listener interior settings if the microphone is frozen.
		if(!audNorthAudioEngine::GetMicrophones().IsMicFrozen())
		{
			m_ListenerInteriorInstance = pIntInst;
			m_InteriorRoomId = roomIdx;
			m_ListenerInteriorSettings = interiorSettings;
			m_ListenerInteriorRoom = room;
		}

		// Update Interior info
		UpdateLinkedRoomList(pIntInst);
//		UpdateLinkedRoomList(NULL);
	}
	else
	{
		// Update Interior info
		UpdateLinkedRoomList(NULL);

		m_ListenerInteriorInstance = NULL;
		m_InteriorRoomId = 0;
		m_ListenerInteriorSettings = NULL;
		m_ListenerInteriorRoom = NULL;

		if (m_SourceEnvironmentPathHandle != PATH_HANDLE_NULL)
		{
			// we're waiting on a request, so let's see if it's done yet.
			EPathServerRequestResult ret = CPathServer::IsRequestResultReady(m_SourceEnvironmentPathHandle);
			if (ret==ERequest_Ready)
			{
				u32 numValidPolys;
				u32 sourceEnvironmentMetrics[MAX_SURROUNDING_POLYS_FOR_AUDIO_PROPERTIES];
				f32 sourceEnvironmentPolyAreas[MAX_SURROUNDING_POLYS_FOR_AUDIO_PROPERTIES];
				Vector3 sourceEnvironmentPolyCentroids[MAX_SURROUNDING_POLYS_FOR_AUDIO_PROPERTIES];
				BANK_ONLY(m_CoveredPolys.Reset());
				u8 sourceEnvironmentFlags[MAX_SURROUNDING_POLYS_FOR_AUDIO_PROPERTIES];
				EPathServerErrorCode errorCode = CPathServer::GetAudioPropertiesResult(m_SourceEnvironmentPathHandle, numValidPolys, 
					sourceEnvironmentMetrics, sourceEnvironmentPolyAreas, sourceEnvironmentPolyCentroids, sourceEnvironmentFlags);
				if (errorCode == PATH_NO_ERROR && numValidPolys>0)
				{
					m_NumValidPolys = numValidPolys;
					for (u32 i= 0; i<numValidPolys; i++)
					{
						m_SourceEnvironmentMetrics[i] = sourceEnvironmentMetrics[i];
						const f32 reverbSize = GetSourceEnvironmentReverbSizeFromSEMetric(m_SourceEnvironmentMetrics[i]);
						const f32 reverbWet  = GetSourceEnvironmentReverbWetFromSEMetric(m_SourceEnvironmentMetrics[i]);
						if (sourceEnvironmentFlags[i] & CAudioRequest::FLAG_POLYGON_IS_SHELTERED)
						{
							m_CoveredPolys.Set(i);
						}
						for (u32 j=0; j<g_NumSourceReverbs; j++)
						{
							m_SourceEnvironmentPolyReverbWet[i][j] = reverbWet * m_SourceReverbCrossfadeCurves[j].CalculateValue(reverbSize);
						}
						m_SourceEnvironmentPolyAreas[i] = sourceEnvironmentPolyAreas[i];
						m_SourceEnvironmentPolyCentroids[i] = sourceEnvironmentPolyCentroids[i];
					}
				}
				m_SourceEnvironmentPathHandle = PATH_HANDLE_NULL;
				// And now update our time and position, so we don't fire in another request too soon. 
				// (You could argue these should be set when the request is made, but better to update too slowly than thrash the pathserver.)
				// (Actually, I'll ALSO set them when the request goes in, to be super friendly.)
				m_SourceEnvironmentTimeLastUpdated = timeInMs;
				m_ListenerPositionLastSourceEnvironmentUpdate = listenerPosition;
			}
			else if(ret == ERequest_NotFound)
			{
				m_SourceEnvironmentPathHandle = PATH_HANDLE_NULL;
			}
			else
			{
				// If we've been waiting more than 2 seconds, something's broken - we've probably lost track of our path handle somehow
				// Or you've paused the game... or things have got really busy and there's nothing we can do... or it's a full moon.
				// I'll enable this locally and keep an eye on it.
				// naAssertf(timeInMs < (m_SourceEnvironmentTimeLastUpdated + 2000) || g_TempFakedPaused || fwTimer::IsGamePaused(), "We've had to wait more than two seconds in UpdateNavmeshSourceEnvironment...could be something broken....or just a full moon");
			}
		}
		else
		{
			// We don't have a pending request, so see if it's about time we did.
			u32 minimumTimeToUpdate = 200;
			f32 distanceMovedSqrd = (listenerPosition - m_ListenerPositionLastSourceEnvironmentUpdate).Mag2();
			if ((timeInMs > (m_SourceEnvironmentTimeLastUpdated + minimumTimeToUpdate)) &&
				(distanceMovedSqrd > 0.25f))
			{
				// It's been a while, and we've moved, so fire in an update request
				m_SourceEnvironmentPathHandle = CPathServer::RequestAudioProperties(listenerPosition, NULL, g_SourceEnvironmentRadius, true);
				if (m_SourceEnvironmentPathHandle!=PATH_HANDLE_NULL)
				{
					// And make sure we don't update again for a while - we also update these number when we get the results back,
					// doing it here is mainly to ensure that a failed request doesn't start thrashing.
					m_SourceEnvironmentTimeLastUpdated = timeInMs;
					m_ListenerPositionLastSourceEnvironmentUpdate = listenerPosition;
				}
				else
				{
					// We got a KB. 
				}
			}
		}
	}

	// See if we've just left a subway/tunnel
	if (m_PlayerIsInASubwayOrTunnel && !playerIsInASubwayOrTunnel)
	{
		g_ScriptAudioEntity.PlayMobileGetSignal(0.6f);
	}
	m_PlayerIsInASubwayStationOrSubwayTunnel = playerIsInASubwayStationOrSubwayTunnel;
	m_PlayerIsInASubwayOrTunnel = playerIsInASubwayOrTunnel;
	m_PlayerIsInAnInterior = playerIsInAnInterior;
	BANK_ONLY(m_PlayerInteriorRatioSmoother.SetRate(g_InteriorRatioSmoothRate/1000.0f));
	if (m_PlayerIsInAnInterior)
	{
		m_PlayerInteriorRatio = m_PlayerInteriorRatioSmoother.CalculateValue(1.0f, timeInMs);
	}
	else
	{
		m_PlayerInteriorRatio = m_PlayerInteriorRatioSmoother.CalculateValue(0.0f, timeInMs);
	}

	// Update our room tone hash
	m_RoomToneHash = roomToneHash;
	m_RainType = rainType;

	if(m_PlayerIsInASubwayOrTunnel && !m_PlayerWasInASubwayOrTunnelLastFrame)
		g_AudioScannerManager.TriggerCopDispatchInteraction(SCANNER_CDIT_REPORT_SUSPECT_ENTERED_METRO);

	m_PlayerWasInASubwayOrTunnelLastFrame = m_PlayerIsInASubwayOrTunnel;
	CalculateUnderCoverFactor();
	m_NavmeshInformationUpdatedThisFrame = true;
	/*
#if __DEV
	if (1)
	{
		char occlusionDebug[256] = "";
		sprintf(occlusionDebug, "t:%d", timeInMs - m_SourceEnvironmentTimeLastUpdated);
		grcDebugDraw::PrintToScreenCoors(occlusionDebug, 35,40);
	}
#endif
	*/
}

void naEnvironment::GetReverbFromSEMetric(const f32 sourceEnvironmentWet, const f32 sourceEnvironmentSize, f32* reverbSmall, f32* reverbMedium, f32* reverbLarge)
{
	static const audThreePointPiecewiseLinearCurve revCurve0(0.0f, 1.0f, 0.25f, 0.71f, 0.5f, 0.0f);
	(*reverbSmall) = sourceEnvironmentWet * revCurve0.CalculateValue(sourceEnvironmentSize);
	if(sourceEnvironmentSize <= 0.5f)
	{
		static const audThreePointPiecewiseLinearCurve revCurve1sm(0.0f, 0.0f, 0.25f, 0.71f, 0.5f, 1.0f);
		(*reverbMedium) = sourceEnvironmentWet * revCurve1sm.CalculateValue(sourceEnvironmentSize);
	}
	else
	{
		static const audThreePointPiecewiseLinearCurve revCurve1big(0.5f, 1.0f, 0.75f, 0.71f, 1.0f, 0.0f);
		(*reverbMedium) = sourceEnvironmentWet * revCurve1big.CalculateValue(sourceEnvironmentSize);
	}
	static const audThreePointPiecewiseLinearCurve revCurve2(0.5f, 0.0f, 0.75f, 0.71f, 1.0f, 1.0f);
	(*reverbLarge) = sourceEnvironmentWet * revCurve2.CalculateValue(sourceEnvironmentSize);
}

f32 naEnvironment::GetSourceEnvironmentReverbWetFromSEMetric(const u32 sourceEnvironment)
{
	if (sourceEnvironment==0)
	{
		return 0.0f;
	}
	u32 quantisedReverbWet = (sourceEnvironment & 12) >> 2;
	u32 quantisedReverbSize = (sourceEnvironment & 3);
	f32 wet = m_SourceEnvironmentToReverbWetCurve.CalculateValue((f32)quantisedReverbWet);
	// Now scale the wet value by size - we want to make small spaces 'wetter' - we could do 16 individual pairings, if this isn't good enough
	wet *= m_SourceEnvironmentSizeToWetScaling.CalculateValue((f32)quantisedReverbSize);
	return wet;
}

f32 naEnvironment::GetSourceEnvironmentReverbSizeFromSEMetric(const u32 sourceEnvironment)
{
	if (sourceEnvironment==0)
	{
		return 0.0f;
	}
	u32 quantisedReverbSize = (sourceEnvironment & 3);
	return m_SourceEnvironmentToReverbSizeCurve.CalculateValue((f32)quantisedReverbSize);
}

fwInteriorLocation naEnvironment::GetRoomIntLocFromPortalIntLoc(const fwInteriorLocation portalIntLoc) const
{
	// Initialize to invalid
	fwInteriorLocation roomIntLoc;
	const CInteriorInst* intInst = NULL;
	s32 roomIdx = INTLOC_INVALID_INDEX;

	if(naVerifyf(portalIntLoc.IsValid(), "Passed Invalid fwInteriorLocation")
		&& naVerifyf(portalIntLoc.IsAttachedToPortal(), "Passed non portal attached interior location when trying to get a room attached interior location"))
	{
		const CInteriorInst* portalIntInst = CInteriorInst::GetInteriorForLocation(portalIntLoc);
		if(portalIntInst)
		{
			const s32 portalIdx = portalIntLoc.GetPortalIndex();

			CPortalFlags portalFlags;
			u32 roomToUnsignedIdx, roomFromUnsignedIdx;
			portalIntInst->GetMloModelInfo()->GetPortalData(portalIdx, roomToUnsignedIdx, roomFromUnsignedIdx, portalFlags);
			// naAssertf(!portalFlags.GetIsMirrorPortal(), "We have an entity attached to a mirror portal, not sure why, but need to handle that case");
			const s32 roomToIdx = static_cast<s32>(roomToUnsignedIdx);
			const s32 roomFromIdx = static_cast<s32>(roomFromUnsignedIdx);

			const CInteriorInst* playerIntInst = audNorthAudioEngine::GetGtaEnvironment()->GetListenerInteriorInstance();
			const s32 playerRoomIdx = audNorthAudioEngine::GetGtaEnvironment()->GetInteriorRoomId();

			if(playerIntInst && playerRoomIdx != INTLOC_ROOMINDEX_LIMBO)
			{
				if(portalIntInst == playerIntInst)
				{
					intInst = playerIntInst;

					// Set the roomIdx to be the same room as the player, so doors don't occlude themselves and get the correct rooms reverb
					if(playerRoomIdx == roomToIdx)
						roomIdx = roomToIdx;
					else if(playerRoomIdx == roomFromIdx)
						roomIdx = roomFromIdx;
					// We're in the same interior but neither of the rooms the door belongs to, so just prefer to use an interior room over outside
					else
						roomIdx = roomToIdx != INTLOC_ROOMINDEX_LIMBO ? roomToIdx : roomFromIdx;
				}
				else
				{
					// Check for a linked portal in which case we need to go through and get the destination to see if it matches the player
					if(portalFlags.GetIsLinkPortal())
					{
						// If the the portal's interior isn't loaded, then we can't see what interiors are linked, so just return what we can.
						// Worst case scenario is a door is occluded when it shouldn't be because we're not using the same room index as the player
						// but this really shouldn't be an issue in normal gameplay since doors that you see will have their interiors loaded.
						// This is to fix a crash probably due to the interior's being reset/reloaded/swapped after a checkpoint restart and
						// we end up accessing the GetRoomZeroPortalInsts() when that array is empty because it's not populated.
						if(!portalIntInst->IsPopulated())
						{
							intInst = portalIntInst;
							roomIdx = roomToIdx != INTLOC_ROOMINDEX_LIMBO ? roomToIdx : roomFromIdx;
						}
						else
						{
							CPortalInst* portalInst = portalIdx < portalIntInst->GetRoomZeroPortalInsts().GetCount() ? portalIntInst->GetRoomZeroPortalInsts()[portalIdx] : NULL;
							if(naVerifyf(portalInst, "Couldn't get the CPortalInst* when getting the linked interior destination") && portalInst->IsLinkPortal())
							{
								CInteriorInst* linkedIntInst = NULL;
								s32 linkedIntRoomLimboPortalIdx = INTLOC_INVALID_INDEX;
								portalInst->GetDestinationData(portalIntInst, linkedIntInst, linkedIntRoomLimboPortalIdx);
								// Is it linked to the interior that the player is in?
								if(linkedIntInst && linkedIntInst == playerIntInst && linkedIntRoomLimboPortalIdx != INTLOC_INVALID_INDEX)
								{
									intInst = playerIntInst;
									roomIdx = linkedIntInst->GetDestThruPortalInRoom(INTLOC_ROOMINDEX_LIMBO, linkedIntRoomLimboPortalIdx);		//entry/exit portal so we know it's room 0
								}
								// it's linked but not to us, so grab the non-limbo roomIdx, otherwise we'll be treating it as though it's outside
								else
								{
									intInst = portalIntInst;
									roomIdx = roomToIdx != INTLOC_ROOMINDEX_LIMBO ? roomToIdx : roomFromIdx;
								}
							}
						}
					}
					// That means it's in a different interior that's not connected, so prefer to use outside or the limbo room, as that's the closest "room" to the player
					else
					{
						intInst = portalIntInst;
						roomIdx = roomToIdx == INTLOC_ROOMINDEX_LIMBO ? roomToIdx : roomFromIdx;
					}
				}
			}
			// The player is outside
			else
			{
				// If it's a link portal, then don't use outside as roomIdx of 0 is actually the interior it's leading to
				// Otherwise we'll be playing doors to linked interiors as though they're outside
				if(portalFlags.GetIsLinkPortal())
				{
					intInst = portalIntInst;
					roomIdx = roomToIdx != INTLOC_ROOMINDEX_LIMBO ? roomToIdx : roomFromIdx;
				}
				// Prefer to use outside, so doors don't occlude themselves because they're inside and the player is outside
				else
				{
					intInst = portalIntInst;
					roomIdx = roomToIdx == INTLOC_ROOMINDEX_LIMBO ? roomToIdx : roomFromIdx;
				}
			}
		}
	}

	if(roomIdx != INTLOC_INVALID_INDEX)
	{
		roomIntLoc = CInteriorInst::CreateLocation(intInst, roomIdx);
	}

	return roomIntLoc;
}

void naEnvironment::FindInteriorSettingsForRoom(const u32 interiorHash, const u32 roomHash, const InteriorSettings*& interior, const InteriorRoom*& intRoom) const
{
	interior = NULL;
	intRoom = NULL;

	// Get the gameobject with the interior name, then look through for the room.
	// If the game object doesn't exist, store NULL, and we'll use defaults from higher-level code
	interior = audNorthAudioEngine::GetObject<InteriorSettings>(interiorHash);
	if(interior && interior->numRooms > 0)
	{
		naAssertf(interior->ClassId == InteriorSettings::TYPE_ID, "Attempting to use an interior settings object but it has type %u when it should be %u", interior->ClassId, InteriorSettings::TYPE_ID);
		// Look for the right room - use the first one if we don't find a match
		for (u8 i=0; i<interior->numRooms; i++)
		{
			const InteriorRoom* loopRoom = audNorthAudioEngine::GetObject<InteriorRoom>(interior->Room[i].Ref);
			if(loopRoom && loopRoom->RoomName == roomHash)
			{
				intRoom = loopRoom;
				break;
			}
		}
	}
}

void naEnvironment::GetInteriorSettingsForEntity(const CEntity* entity, const InteriorSettings*& intSettings, const InteriorRoom*& intRoom)
{
	intSettings = NULL;
	intRoom = NULL;

	// If we're debugging, override with our default global room settings
	if (g_UseDebugRoomSettings)
	{
		GetDebugInteriorSettings(intSettings, intRoom);
		return;
	}

	if(naVerifyf(entity, "Null entity pointer in GetInteriorSettingsForEntity"))
	{
		const fwInteriorLocation intLoc = entity->GetAudioInteriorLocation();
		GetInteriorSettingsForInteriorLocation(intLoc, intSettings, intRoom);
	}
}

void naEnvironment::GetInteriorSettingsForInteriorLocation(const fwInteriorLocation intLoc, const InteriorSettings*& intSettings, const InteriorRoom*& intRoom)
{
	intSettings = NULL;
	intRoom = NULL;

	// If we're debugging, override with our default global room settings
	if (g_UseDebugRoomSettings)
	{
		GetDebugInteriorSettings(intSettings, intRoom);
		return;
	}

	// If we're attached to a portal, then we need to figure out the best roomIdx to use based on the player location
	fwInteriorLocation roomAttachedIntLoc = intLoc;
	if(intLoc.IsAttachedToPortal())
	{
		roomAttachedIntLoc = GetRoomIntLocFromPortalIntLoc(intLoc);
	}
	
	const CInteriorInst* intInst = CInteriorInst::GetInteriorForLocation(roomAttachedIntLoc);
	if(intInst)
	{
		const s32 roomIdx = roomAttachedIntLoc.GetRoomIndex();
		if(roomIdx != INTLOC_INVALID_INDEX && roomIdx < (s32)intInst->GetNumRooms())
		{
			GetInteriorSettingsForInterior(intInst, roomIdx, intSettings, intRoom);
		}
	}
}

void naEnvironment::GetInteriorSettingsForInterior(const CInteriorInst* intInst, const s32 roomIdx, const InteriorSettings*& intSettings, const InteriorRoom*& intRoom)
{
	intSettings = NULL;
	intRoom = NULL;

	// If we're debugging, override with our default global room settings
	if (g_UseDebugRoomSettings)
	{
		GetDebugInteriorSettings(intSettings, intRoom);
		return;
	}

	if (intInst && roomIdx!=INTLOC_INVALID_INDEX
		&& naVerifyf(roomIdx < intInst->GetNumRooms(), "Requesting InteriorSettings with an invalid roomIdx.  Int: %s roomIdx: %d", intInst->GetModelName(), roomIdx))
	{
		// Get the room we're in
		CMloModelInfo* pModelInfo = intInst->GetMloModelInfo();
		if(naVerifyf(pModelInfo, "Failed to get model info from interior instance model index %d", intInst->GetModelId().GetModelIndex()))
		{
			pModelInfo->GetAudioSettings(roomIdx, intSettings, intRoom);

			// If we don't find one, return a sensible default
			if (!intSettings)
			{
				// perhaps try again - in debug mode, we might have created the object after the room was init'd. 
				if (g_TryRoomsEveryTime)
				{
					audNorthAudioEngine::GetGtaEnvironment()->FindInteriorSettingsForRoom(pModelInfo->GetHashKey(), intInst->GetRoomHashcode(roomIdx), intSettings, intRoom);
					// And store it, so next time we find it straight away
					pModelInfo->SetAudioSettings(roomIdx, intSettings, intRoom);
				}
				// If the default doesn't exist, use the debug one
				if (!intSettings)
				{
					GetDebugInteriorSettings(intSettings, intRoom);
				}
			}
		}
	}
}

void naEnvironment::GetDebugInteriorSettings(const InteriorSettings*& intSettings, const InteriorRoom*& intRoom) const
{
	intSettings = g_DebugRoomSettings;
	intRoom = audNorthAudioEngine::GetObject<InteriorRoom>(g_DebugRoomSettings->Room[0].Ref);
}

#if __BANK
void naEnvironment::AddBuildingToWorld(const Vector3 &pos, CEntity* entity)
{
	if(entity)
	{
		const Vector3 boundingBoxMax = entity->GetBoundingBoxMax();
		const Vector3 boundingBoxMin = entity->GetBoundingBoxMin();

		f32 width = abs(boundingBoxMax.x - boundingBoxMin.x);
		f32 depth = abs(boundingBoxMax.y - boundingBoxMin.y);
		f32 baseArea = (depth * width);

		// Discount buildings with a massive or tiny footprint- they're probably actually huge chunks of terrain or
		// little electricity pylons respectively
		if(baseArea > g_MinBuildingFootprintArea &&
		   baseArea < g_MaxBuildingFootprintArea)
		{
			f32 height = abs(boundingBoxMax.z - boundingBoxMin.z);

			if(height >= g_MinBuildingHeight)
			{
				if(m_NumBuildingAddsThisFrame >= g_audMaxCachedSectorOps)
				{
					ProcessCachedWorldSectorOps();
				}

				m_WSBuildingAddHeights[m_NumBuildingAddsThisFrame] = static_cast<u32>(height);
				m_WSBuildingAddIndices[m_NumBuildingAddsThisFrame++] = static_cast<u32>(ComputeWorldSectorIndex(pos));
			}
		}
	}
}

void naEnvironment::AddTreeToWorld(const Vector3 &pos, CEntity* entity)
{
	if(entity)
	{
		f32 height = entity->GetBoundingBoxMax().z - entity->GetBoundingBoxMin().z;

		// Small trees don't count. Its a shrubbery! etc.
		if(height > g_MinHeightForTree)
		{
			// if theres space in the cached list then add it there to save a LHS
			if(m_NumTreeAddsThisFrame >= g_audMaxCachedSectorOps)
			{
				ProcessCachedWorldSectorOps();
			}
			m_WSTreeAddIndices[m_NumTreeAddsThisFrame++] = static_cast<u32>(ComputeWorldSectorIndex(pos));
		}
	}
}

void naEnvironment::RemoveTreeFromWorld(const Vector3 &pos)
{
	// if theres space in the cached list then add it there to save a LHS
	if(m_NumTreeRemovesThisFrame >= g_audMaxCachedSectorOps)
	{
		ProcessCachedWorldSectorOps();
	}
	m_WSTreeRemoveIndices[m_NumTreeRemovesThisFrame++] = static_cast<u32>(ComputeWorldSectorIndex(pos));
}

void naEnvironment::RemoveBuildingFromWorld(const Vector3 &pos)
{
	// if theres space in the cached list then add it there to save a LHS
	if(m_NumBuildingRemovesThisFrame >= g_audMaxCachedSectorOps)
	{
		ProcessCachedWorldSectorOps();
	}
	m_WSBuildingRemoveIndices[m_NumBuildingRemovesThisFrame++] = static_cast<u32>(ComputeWorldSectorIndex(pos));
}

void naEnvironment::AddWaterQuad(const Vector3 &v1,const Vector3 &v2,const Vector3 &v3,const Vector3 &v4)
{
	const u32 index1 = static_cast<u32>(ComputeWorldSectorIndex(v1));
	const u32 index2 = static_cast<u32>(ComputeWorldSectorIndex(v2));
	const u32 index3 = static_cast<u32>(ComputeWorldSectorIndex(v3));
	const u32 index4 = static_cast<u32>(ComputeWorldSectorIndex(v4));

	const u32 secX1 = index1 % g_audNumWorldSectorsX;
	const u32 secY1 = (index1-secX1)/g_audNumWorldSectorsX;
	const u32 secX2 = index2 % g_audNumWorldSectorsX;
	const u32 secY2 = (index2-secX2)/g_audNumWorldSectorsX;
	const u32 secX3 = index3 % g_audNumWorldSectorsX;
	const u32 secY3 = (index3-secX3)/g_audNumWorldSectorsX;
	const u32 secX4 = index4 % g_audNumWorldSectorsX;
	const u32 secY4 = (index4-secX4)/g_audNumWorldSectorsX;

	// find the min and max sector index
	const u32 minIndex = Min(index1, index2, index3, index4);
	const u32 maxIndex = Max(index1, index2, index3, index4);

	// and min/max X/Y
	const u32 minSecX = Max(Min(secX1, secX2, secX3, secX4), (u32)0);
	const u32 maxSecX = Min(Max(secX1, secX2, secX3, secX4), (u32)(g_audNumWorldSectorsX-1));
	const u32 minSecY = Max(Min(secY1, secY2, secY3, secY4), (u32)0);
	const u32 maxSecY = Min(Max(secY1, secY2, secY3, secY4), (u32)(g_audNumWorldSectorsY-1));

	for(u32 i = minIndex; i <= maxIndex; i++)
	{
		const u32 curX = i % g_audNumWorldSectorsX;
		const u32 curY = (i-curX)/g_audNumWorldSectorsX;
		// does this sector intersect
		if(curX >= minSecX && curX <= maxSecX && curY >= minSecY && curY <= maxSecY)
		{
			//g_AudioWorldSectors[i].isWaterSector = false;
		}
	}
}

void naEnvironment::ProcessCachedWorldSectorOps()
{
	for(u32 i =0; i < m_NumBuildingAddsThisFrame; i++)
	{
		f32 fBuildingHeight = (f32)(m_WSBuildingAddHeights[i]/284.f);
		s32 buildingHeight = (s32)(fBuildingHeight * 254.f);
		buildingHeight = (u8)Clamp(buildingHeight,0,254);
		if(g_AudioWorldSectors[m_WSBuildingAddIndices[i]].tallestBuilding < buildingHeight)
		{
			Assign(g_AudioWorldSectors[m_WSBuildingAddIndices[i]].tallestBuilding, buildingHeight);
		}

		if(g_AudioWorldSectors[m_WSBuildingAddIndices[i]].numBuildings < 255)
		{
			g_AudioWorldSectors[m_WSBuildingAddIndices[i]].numBuildings++;
		}
	}

	for(u32 i =0; i < m_NumBuildingRemovesThisFrame; i++)
	{
		if(g_AudioWorldSectors[m_WSBuildingRemoveIndices[i]].numBuildings > 0)
		{
			g_AudioWorldSectors[m_WSBuildingRemoveIndices[i]].numBuildings--;
		}
	}

	for(u32 i =0; i < m_NumTreeAddsThisFrame; i++)
	{
		if(g_AudioWorldSectors[m_WSTreeAddIndices[i]].numTrees < 127)
		{
			g_AudioWorldSectors[m_WSTreeAddIndices[i]].numTrees++;
		}
	}

	for(u32 i =0; i < m_NumTreeRemovesThisFrame; i++)
	{
		if(g_AudioWorldSectors[m_WSTreeRemoveIndices[i]].numTrees > 0)
		{
			g_AudioWorldSectors[m_WSTreeRemoveIndices[i]].numTrees--;
		}
	}

	m_NumBuildingAddsThisFrame = m_NumBuildingRemovesThisFrame = m_NumTreeAddsThisFrame = m_NumTreeRemovesThisFrame = 0;
}

#endif
f32 naEnvironment::ComputeWorldSectorIndex(const Vector3 &pos) const
{
	// NOTE: this function returns an f32 rather than u32 index, so that a LHS can be avoided in higher level code in some cases
	// NOTE: assumes world is centered at origin
	f32 x = floorf((pos.x / g_audWidthOfSector) + (g_fAudNumWorldSectorsX * 0.5f));
	f32 y = floorf((pos.y / g_audDepthOfSector) + (g_fAudNumWorldSectorsY * 0.5f));
	x = Clamp(x, 0.0f, g_fAudNumWorldSectorsX-1);
	y = Clamp(y, 0.0f, g_fAudNumWorldSectorsY-1);
	return x + (y * g_fAudNumWorldSectorsX);
}	

void naEnvironment::ComputeRainMixForSector(const u32 sectorIndex, const f32 rainVol, audRainXfade *directionMixes)
{
	audRainXfade sectorRain[9];
	const u32 sectorX = sectorIndex % g_audNumWorldSectorsX, sectorY = (sectorIndex-sectorX)/g_audNumWorldSectorsY;
	u32 i = 0;

	ComputeRainMixForSector(sectorX-1,sectorY+1,sectorRain[i++]);
	ComputeRainMixForSector(sectorX,sectorY+1,sectorRain[i++]);
	ComputeRainMixForSector(sectorX+1,sectorY+1,sectorRain[i++]);
	ComputeRainMixForSector(sectorX-1,sectorY,sectorRain[i++]);
	ComputeRainMixForSector(sectorX,sectorY,sectorRain[i++]);
	ComputeRainMixForSector(sectorX+1,sectorY,sectorRain[i++]);
	ComputeRainMixForSector(sectorX-1,sectorY-1,sectorRain[i++]);
	ComputeRainMixForSector(sectorX,sectorY-1,sectorRain[i++]);
	ComputeRainMixForSector(sectorX+1,sectorY-1,sectorRain[i++]);

	const Matrix34 listenerMatrix = audNorthAudioEngine::GetMicrophones().GetAmbienceMic();//g_AudioEngine.GetEnvironment().GetPanningListenerMatrix();

	const Vector3 north = Vector3(0.0f, 1.0f, 0.0f);
	const Vector3 east = Vector3(1.0f,0.0f,0.0f);
	Vector3 right = listenerMatrix.a;
	right.NormalizeSafe();
	const f32 cosangle = right.Dot(north);
	const f32 angle = AcosfSafe(cosangle);
	const f32 degrees = RtoD*angle;
	f32 actualDegrees;
	if(right.Dot(east) <= 0.0f)
	{
		actualDegrees = 360.0f - degrees;
	}
	else
	{
		actualDegrees = degrees;
	}

	s32 basePan = 360 - ((s32)actualDegrees - 90);

	const Vector2 listenerPos(listenerMatrix.d.x, listenerMatrix.d.y);
	const Vector2 emitterPositions[] = {Vector2(sectorRain[1].x,sectorRain[1].y), // NORTH
										Vector2(sectorRain[3].x,sectorRain[3].y), // EAST
										Vector2(sectorRain[5].x,sectorRain[5].y), // SOUTH
										Vector2(sectorRain[7].x,sectorRain[7].y)}; // WEST

	for(u32 emitterIdx = 0; emitterIdx < 4; emitterIdx++)
	{
		Vector2 list2emitter = (listenerPos - emitterPositions[emitterIdx]);
		list2emitter.NormalizeSafe();

		f32 factor[9];
		for(u32 i = 0; i < 9; i++)
		{
			const Vector2 sectorPos = Vector2(sectorRain[i].x, sectorRain[i].y);
			Vector2 list2sector = (listenerPos - sectorPos);
			list2sector.NormalizeSafe();
			// ignore angle for sector that the listener is in
			const f32 angleContrib = (i==4?1.0f:Clamp(list2emitter.Dot(list2sector), 0.0f, 1.0f));
			static f32 distScaler = 0.05f;
			const f32 distFactor = Clamp(1.0f / ((listenerPos - sectorPos) * distScaler).Mag2(), 0.0f, 1.0f);

			factor[i] = angleContrib * distFactor;
		}

		f32 sqSum = 0.0f;
		for(u32 i = 0; i < 9; i++)
		{
			sqSum += factor[i] * factor[i];
		}
		f32 sqFactor = sqrt(sqSum);

		directionMixes[emitterIdx].buildingRain = directionMixes[emitterIdx].waterRain = directionMixes[emitterIdx].treeRain = 0.0f;
		for(u32 i = 0; i < 9; i++)
		{
			const f32 sectorFactor = (factor[i]/sqFactor);
			directionMixes[emitterIdx].buildingRain += (sectorFactor * sectorRain[i].buildingRain);
			directionMixes[emitterIdx].treeRain += (sectorFactor * sectorRain[i].treeRain);
			directionMixes[emitterIdx].waterRain += (sectorFactor * sectorRain[i].waterRain);
		}

		directionMixes[emitterIdx].buildingRain *= rainVol;
		directionMixes[emitterIdx].treeRain *= rainVol;
		directionMixes[emitterIdx].waterRain *= rainVol;
		directionMixes[emitterIdx].pan = (basePan + emitterIdx*90) % 360;
	}
}

void naEnvironment::ComputeRainMixForSector(const u32 sectorX, const u32 sectorY, audRainXfade &rain) const
{
	if(sectorX < g_audNumWorldSectorsX && sectorY < g_audNumWorldSectorsY)
	{
		const u32 sector = sectorX + (sectorY*g_audNumWorldSectorsX);
		const bool isWaterSector = g_AudioWorldSectors[sector].isWaterSector;
		rain.x = g_audWidthOfSector * (sectorX - (g_fAudNumWorldSectorsX/2.f));
		rain.y = g_audDepthOfSector * (sectorY - (g_fAudNumWorldSectorsY/2.f));
		if(g_AmbientAudioEntity.IsPlayerInTheCity())
		{
			rain.treeRain = (Min( g_NumTreesForRain, static_cast<f32>(g_AudioWorldSectors[sector].numTrees)) / g_NumTreesForRainBuiltUp);
		}
		else
		{
			rain.treeRain = (Min( g_NumTreesForRain, static_cast<f32>(g_AudioWorldSectors[sector].numTrees)) / g_NumTreesForRain);
		}
		rain.buildingRain = 1.0f - rain.treeRain;
		
		const Vector3 v1(rain.x,rain.y,100.0f), v2(rain.x+g_audWidthOfSector,rain.y+g_audDepthOfSector,100.f);
		// const Vector3 centre = (v1+v2) * 0.5f;
			
		if(isWaterSector)
		{
			rain.waterRain = 1.0f;
			rain.buildingRain = 0.0f;
			rain.treeRain = 0.0f;
		}
		else
		{
			rain.waterRain = 0.0f;
		}
	}
	else
	{
		// invalid sector - default to water (since the map is surrounded by water)
		rain.waterRain = 1.0f;
		rain.buildingRain = 0.0f;
		rain.treeRain = 0.0f;
		rain.x = 0.0f;
		rain.y = 0.0f;
	}

	f32 sqFactor = sqrt(rain.waterRain*rain.waterRain + rain.buildingRain*rain.buildingRain + rain.treeRain*rain.treeRain);
	rain.waterRain /= sqFactor;
	rain.buildingRain /= sqFactor;
	rain.treeRain /= sqFactor;
}
f32	 naEnvironment::GetBuiltUpFactorOfSector(u32 sector){
	return m_BuiltUpFactorPerZone[sector];
}

void naEnvironment::UpdateMetrics()
{
	const Matrix34 listenerMatrix = MAT34V_TO_MATRIX34(g_AudioEngine.GetEnvironment().GetPanningListenerMatrix());
	const u32 sectorIndex = static_cast<u32>(ComputeWorldSectorIndex(listenerMatrix.d));
	const u32 sectorX = sectorIndex % g_audNumWorldSectorsX;
	const u32 sectorY = (sectorIndex-sectorX)/g_audNumWorldSectorsY;

	if(!m_RefreshHighwayFactor)
	{
		if(m_LastHighwaySectorX != sectorX ||
			m_LastHighwaySectorY != sectorY)
		{
			m_RefreshHighwayFactor = true;
			m_HighwayZoneToRefresh = 0;

			m_LastHighwaySectorX = sectorX;
			m_LastHighwaySectorY = sectorY;
		}
	}
		for (u32 y=0; y<AUD_NUM_SECTORS_ACROSS; y++)
		{
			for (u32 x=0; x<AUD_NUM_SECTORS_ACROSS; x++)
			{
				u32 xSector = sectorX+x-AUD_HALF_NUM_SECTORS_ACROSS;
				u32 ySector = sectorY-y+AUD_HALF_NUM_SECTORS_ACROSS;

				if(xSector < g_audNumWorldSectorsX && ySector < g_audNumWorldSectorsY)
				{
					const u32 sector = xSector + (ySector*g_audNumWorldSectorsX);
					m_BuildingHeights[x+AUD_NUM_SECTORS_ACROSS*y] = GetBuildingHeightForSector(sector);
					m_BuildingCount[x+AUD_NUM_SECTORS_ACROSS*y] = GetBuildingCountForSector(sector);
					m_TreeCount[x+AUD_NUM_SECTORS_ACROSS*y] = GetTreeCountForSector(sector);
					m_WaterAmount[x+AUD_NUM_SECTORS_ACROSS*y] = GetWaterAmountForSector(sector);
					m_HighwayAmount[x+AUD_NUM_SECTORS_ACROSS*y] = GetHighwayAmountForSector(sector);

#if __BANK 
					// Just update one zone at a time, no need for uber-accuracy
					if(sm_ProcessSectors && m_RefreshHighwayFactor && 
						(y + (x * AUD_NUM_SECTORS_ACROSS)) == m_HighwayZoneToRefresh)
					{
						Vector3 zoneMin,zoneMax;
						zoneMin.x = g_audWidthOfSector * (xSector - (g_fAudNumWorldSectorsX/2.f));
						zoneMin.y = g_audDepthOfSector * (ySector - (g_fAudNumWorldSectorsY/2.f));
						zoneMax.x = zoneMin.x + g_audWidthOfSector;
						zoneMax.y = zoneMin.y + g_audDepthOfSector;

						Vector3 centre = (zoneMin + zoneMax) * 0.5f;
						centre.z = CGameWorldHeightMap::GetMinHeightFromWorldHeightMap(centre.x, centre.y);
						zoneMin.z = centre.z - 50.0f;
						zoneMax.z = centre.z + 50.0f;

						audParseNodeCallbackData callbackData;
						callbackData.numHighwayNodes = 0;
						callbackData.numNodes = 0;
						callbackData.minPos = zoneMin;
						callbackData.maxPos = zoneMax;
						ThePaths.ForAllNodesInArea(zoneMin, zoneMax, ParseNodeCallback, &callbackData);

						if(callbackData.numNodes > 0)
						{
							Assign(g_AudioWorldSectors[sector].numHighwayNodes, Clamp(callbackData.numHighwayNodes, 0, 256));
						}
					}
#endif
				}

				if(g_UpdateBuiltUpFactor)
				{
					f32 builtUpFactor = m_HeightToBuiltUpFactor.CalculateValue(m_BuildingHeights[x+AUD_NUM_SECTORS_ACROSS*y]);
					m_BuiltUpFactorPerZone[x+AUD_NUM_SECTORS_ACROSS*y] = m_BuiltUpSmoother.CalculateValue(builtUpFactor, audNorthAudioEngine::GetCurrentTimeInMs());
				}
			}
		}
	// Work out where the centre of the main sector is, so we know how much of the others to factor in
	f32 mainSectorX = g_audWidthOfSector * (sectorX - (g_fAudNumWorldSectorsX/2.f) + 0.5f);
	f32 mainSectorY = g_audDepthOfSector * (sectorY - (g_fAudNumWorldSectorsY/2.f) + 0.5f);
	f32 ourX = listenerMatrix.d.x;
	f32 ourY = listenerMatrix.d.y;
	f32 ratioCtoR = (ourX - mainSectorX)/(g_audWidthOfSector); // this goes from -0.5 to 0.5 - we never want to be fully in the next sector
	f32 ratioCtoU = (ourY - mainSectorY)/(g_audDepthOfSector); // this goes from -0.5 to 0.5 - we never want to be fully in the next sector
	u32 diagonalCrossfadeIndex = AUD_CENTER_SECTOR;
	u32 yCrossfadeIndex = AUD_CENTER_SECTOR + AUD_NUM_SECTORS_ACROSS;
	if (ratioCtoU>0.0f)
	{
		yCrossfadeIndex = AUD_CENTER_SECTOR - AUD_NUM_SECTORS_ACROSS;
	}
	u32 xCrossfadeIndex = AUD_CENTER_SECTOR - 1;
	if (ratioCtoR>0.0f)
	{
		xCrossfadeIndex = AUD_CENTER_SECTOR + 1;
		diagonalCrossfadeIndex = yCrossfadeIndex+1;
	}
	else
	{
		diagonalCrossfadeIndex = yCrossfadeIndex-1;
	}

	UpdateBuiltUpMetrics(ratioCtoR, ratioCtoU, xCrossfadeIndex, yCrossfadeIndex, diagonalCrossfadeIndex);
	UpdateBuildingDensityMetrics(ratioCtoR, ratioCtoU, xCrossfadeIndex, yCrossfadeIndex, diagonalCrossfadeIndex);
	UpdateTreeDensityMetrics(ratioCtoR, ratioCtoU, xCrossfadeIndex, yCrossfadeIndex, diagonalCrossfadeIndex);
	UpdateWaterMetrics(ratioCtoR, ratioCtoU, xCrossfadeIndex, yCrossfadeIndex, diagonalCrossfadeIndex);
	UpdateHighwayMetrics(ratioCtoR, ratioCtoU, xCrossfadeIndex, yCrossfadeIndex, diagonalCrossfadeIndex);

	if(m_RefreshHighwayFactor)
	{
		m_HighwayZoneToRefresh++;

		if(m_HighwayZoneToRefresh >= AUD_NUM_SECTORS)
		{
			m_RefreshHighwayFactor = false;
		}
	}
}

void naEnvironment::UpdateBuiltUpMetrics(f32 ratioCtoR, f32 ratioCtoU, u32 xCrossfadeIndex, u32 yCrossfadeIndex, u32 diagonalCrossfadeIndex)
{
	m_BuiltUpSmoother.SetRate(g_BuiltUpSmootherRate);
	m_BuiltUpDampingFactorSmoother.SetRates(g_BuiltUpDampingFactorSmoothUpRate/1000.0f, g_BuiltUpDampingFactorSmoothDownRate/1000.0f);

	f32 xRatio = Abs(ratioCtoR);
	f32 yRatio = Abs(ratioCtoU);
	f32 centreHeight = GetAveragedBuildingHeightForIndex(AUD_CENTER_SECTOR);
	f32 xOffsetHeight = GetAveragedBuildingHeightForIndex(xCrossfadeIndex);
	f32 yOffsetHeight = GetAveragedBuildingHeightForIndex(yCrossfadeIndex);
	f32 diagOffsetHeight = GetAveragedBuildingHeightForIndex(diagonalCrossfadeIndex);

	f32 baseXOffsetHeight = audCurveRepository::GetLinearInterpolatedValue(centreHeight, xOffsetHeight, 0.0f, 1.0f, xRatio);
	f32 yOffsetXOffsetHeight = audCurveRepository::GetLinearInterpolatedValue(yOffsetHeight, diagOffsetHeight, 0.0f, 1.0f, xRatio);
	f32 finalHeight = audCurveRepository::GetLinearInterpolatedValue(baseXOffsetHeight, yOffsetXOffsetHeight, 0.0f, 1.0f, yRatio);

	f32 builtUpFactor = m_HeightToBuiltUpFactor.CalculateValue(finalHeight);
	f32 timeSmoothedBuiltUpFactor = m_BuiltUpSmoother.CalculateValue(builtUpFactor, audNorthAudioEngine::GetCurrentTimeInMs());

	m_BuiltUpFactor = timeSmoothedBuiltUpFactor;

	f32 directionalBuildingHeight[4];

	directionalBuildingHeight[0] = GetAveragedBuildingHeightFromXYOffset(0.0f+ratioCtoR, g_NSEW_Offset+ratioCtoU);
	directionalBuildingHeight[1] = GetAveragedBuildingHeightFromXYOffset(g_NSEW_Offset+ratioCtoR, 0.0f+ratioCtoU);
	directionalBuildingHeight[2] = GetAveragedBuildingHeightFromXYOffset(0.0f+ratioCtoR, -g_NSEW_Offset+ratioCtoU);
	directionalBuildingHeight[3] = GetAveragedBuildingHeightFromXYOffset(-g_NSEW_Offset+ratioCtoR, 0.0f+ratioCtoU);

	for (u32 i=0; i<4; i++)
	{
		m_DirectionalBuiltUpFactor[i] = m_BuiltUpDirectionalSmoother[i].CalculateValue(m_HeightToBuiltUpFactor.CalculateValue(directionalBuildingHeight[i]), audNorthAudioEngine::GetCurrentTimeInMs());
	}

#if __BANK
	if (g_DebugBuiltUpInfo)
	{
		static f32 x[AUD_NUM_SECTORS];
		static f32 y[AUD_NUM_SECTORS];
		for (u32 i=0; i<AUD_NUM_SECTORS_ACROSS; i++)
		{
			for (u32 j=0; j<AUD_NUM_SECTORS_ACROSS; j++)
			{
				x[i*AUD_NUM_SECTORS_ACROSS+j] = 100.0f + (800.0f/AUD_NUM_SECTORS_ACROSS)*j;
				y[i*AUD_NUM_SECTORS_ACROSS+j] = 100.0f + (700.0f/AUD_NUM_SECTORS_ACROSS)*i;
			}
		}
		static f32 heights[AUD_NUM_SECTORS][2];
		static audMeterList meterList[AUD_NUM_SECTORS];
		static const char* noName[2] = {"",""};
		for (u32 i=0; i<AUD_NUM_SECTORS; i++)
		{
			heights[i][0] = m_BuildingHeights[i] / 256.0f;
			heights[i][1] = m_HeightToBuiltUpFactor.CalculateValue(m_BuildingHeights[i]);
			meterList[i].left = x[i];
			meterList[i].bottom = y[i];
			meterList[i].width = 10.f;
			meterList[i].height = 40.f;
			meterList[i].values = &(heights[i][0]);
			meterList[i].names = &noName[0];
			meterList[i].numValues = 2;
			audNorthAudioEngine::DrawLevelMeters(&(meterList[i])); 
		}

		static f32 localBuiltUpValue;
		static audMeterList localBuiltUpMeter;
		static const char* localBuiltUpMeterName = "C";
		localBuiltUpValue = m_BuiltUpFactor;
		localBuiltUpMeter.left = x[AUD_NUM_SECTORS-1] + 150;
		localBuiltUpMeter.bottom = y[AUD_NUM_SECTORS/2];
		localBuiltUpMeter.width = 10.f;
		localBuiltUpMeter.height = 40.f;
		localBuiltUpMeter.values = &localBuiltUpValue;
		localBuiltUpMeter.numValues = 1;
		localBuiltUpMeter.names = &localBuiltUpMeterName;
		audNorthAudioEngine::DrawLevelMeters(&localBuiltUpMeter); 

		const Matrix34 listenerMatrix = MAT34V_TO_MATRIX34(g_AudioEngine.GetEnvironment().GetPanningListenerMatrix());
		Vector3 right = listenerMatrix.a;

		right.NormalizeFast();
		const f32 cosangle = right.Dot(g_Directions[AUD_AMB_DIR_NORTH]);
		const f32 angle = AcosfSafe(cosangle);
		const f32 degrees = RtoD*angle;
		f32 actualDegrees;
		if(right.Dot(g_Directions[AUD_AMB_DIR_EAST]) <= 0.0f)
		{
			actualDegrees = 360.0f - degrees;
		}
		else
		{
			actualDegrees = degrees;
		}

		//Adjust the degrees so North is 0 degrees and get it between 0-360
		actualDegrees += 270.f;
		while(actualDegrees > 360.0f)
		{
			actualDegrees -= 360.0f;
		}

		static audMeterList dirMeterLists[4];
		static const char *dirNamesList[4] = {"N", "E", "S", "W"};
		static const f32 meterBasePan[4] = {0, 90, 180, 270};
		static f32 dirBuiltUpValues[4];

		for(u32 i = 0; i < 4; i++)
		{
			dirBuiltUpValues[i] = m_DirectionalBuiltUpFactor[i];

			f32 cirleDegrees = meterBasePan[i] + (360.0f - actualDegrees) + 270.0f;
			while(cirleDegrees > 360.0f)
			{
				cirleDegrees -= 360.0f;
			}
			const f32 angle = cirleDegrees * (PI/180);

			dirMeterLists[i].left = x[AUD_NUM_SECTORS-1] + 150 + (75.f * rage::Cosf(angle));
			dirMeterLists[i].width = 10.f;
			dirMeterLists[i].bottom = y[AUD_NUM_SECTORS/2] + (75.f * rage::Sinf(angle));
			dirMeterLists[i].height = 40.f;
			dirMeterLists[i].names = &dirNamesList[i];
			dirMeterLists[i].values = &dirBuiltUpValues[i];
			dirMeterLists[i].numValues = 1;
			audNorthAudioEngine::DrawLevelMeters(&dirMeterLists[i]);
		}
	}
	if (g_DisplayBuiltUpFactors)
	{
		char builtUpFactor[256] = "";
//		sprintf(builtUpFactor, "bUF: %f; ht: %f; ySec: %d, xSec: %d; diagSec: %d; yR: %f; xR: %f", m_BuiltUpFactor, finalHeight, 
//			yCrossfadeIndex, xCrossfadeIndex, diagonalCrossfadeIndex, yRatio, xRatio);
		sprintf(builtUpFactor, "Centre: %f; North: %f; East: %f; South: %f; West: %f", m_BuiltUpFactor, m_DirectionalBuiltUpFactor[0], m_DirectionalBuiltUpFactor[1], m_DirectionalBuiltUpFactor[2], m_DirectionalBuiltUpFactor[3]);
		grcDebugDraw::PrintToScreenCoors(builtUpFactor, 25,30);
	}
#endif

	// Until I can do an audio sync, override this good stuff with something shit
	if (!g_UseProperBuiltUpFactor)
	{
		if (g_ShouldOverrideBuiltUpFactor)
		{
			m_BuiltUpFactor = g_OverrideBuiltUpFactor;
			for (u32 i=0; i<4; i++)
			{
				m_DirectionalBuiltUpFactor[i] = g_OverrideBuiltUpFactor;
			}
		}
//		else if (CMapAreas::IsPositionInNamedArea(g_AudioEngine.GetEnvironment().GetVolumeListenerPosition()), atStringhash("Manhattan")))
//		{
//			m_BuiltUpFactor = 1.0f;
//		}
		else
		{
			m_BuiltUpFactor = 0.5f;
		}
	}

	// If we're in an interior, damp down all our built-up factors.
	f32 builtUpDampingFactor = 1.0f;
	bool isInteriorASubway = false;
	if (AreWeInAnInterior(&isInteriorASubway))
	{
		if (!isInteriorASubway)
		{
			builtUpDampingFactor = 0.0f;
		}
	}
	f32 builtUpDampingFactorSmoothed = m_BuiltUpDampingFactorSmoother.CalculateValue(builtUpDampingFactor, audNorthAudioEngine::GetCurrentTimeInMs());

	// Do useful stuff with the metric
	g_AudioEngine.GetEnvironment().SetBuiltUpFactor(m_BuiltUpFactor);
	g_AudioEngine.GetEnvironment().SetBuiltUpDirectionFactors(m_DirectionalBuiltUpFactor);
	const f32 builtUpRolloff = m_BuiltUpToRolloff.CalculateValue(m_BuiltUpFactor);
	f32 plateauScale = 1.0f;
	if(naVerifyf(builtUpRolloff != 0.0f, "'BUILT_UP_TO_ROLLOFF' curve returned 0.0f, needs to be non-zero"))
	{
		plateauScale = 1.0f / builtUpRolloff;
	}
	g_AudioEngine.GetEnvironment().SetGlobalRolloffPlateauScale(plateauScale);
	g_AudioEngine.GetEnvironment().SetRolloffDampingFactor(builtUpDampingFactorSmoothed*g_BuiltUpDampingFactorOverride);
}

void naEnvironment::UpdateBuildingDensityMetrics(f32 ratioCtoR, f32 ratioCtoU, u32 xCrossfadeIndex, u32 yCrossfadeIndex, u32 diagonalCrossfadeIndex)
{
	m_BuildingDensitySmoother.SetRate(g_BuiltUpSmootherRate);

	f32 xRatio = Abs(ratioCtoR);
	f32 yRatio = Abs(ratioCtoU);
	f32 centreCount = GetAveragedBuildingCountForIndex(AUD_CENTER_SECTOR);
	f32 xOffsetCount = GetAveragedBuildingCountForIndex(xCrossfadeIndex);
	f32 yOffsetCount = GetAveragedBuildingCountForIndex(yCrossfadeIndex);
	f32 diagOffsetCount = GetAveragedBuildingCountForIndex(diagonalCrossfadeIndex);

	f32 baseXOffsetCount = audCurveRepository::GetLinearInterpolatedValue(centreCount, xOffsetCount, 0.0f, 1.0f, xRatio);
	f32 yOffsetXOffsetCount = audCurveRepository::GetLinearInterpolatedValue(yOffsetCount, diagOffsetCount, 0.0f, 1.0f, xRatio);
	f32 finalCount = audCurveRepository::GetLinearInterpolatedValue(baseXOffsetCount, yOffsetXOffsetCount, 0.0f, 1.0f, yRatio);

	f32 buldingDensity = m_BuildingNumToDensityFactor.CalculateValue(finalCount);
	f32 timeSmoothedDensity = m_BuildingDensitySmoother.CalculateValue(buldingDensity, audNorthAudioEngine::GetCurrentTimeInMs());

	m_BuildingDensityFactor = timeSmoothedDensity;

	f32 directionalBuildingDensity[4];

	directionalBuildingDensity[0] = GetAveragedBuildingCountFromXYOffset(0.0f+ratioCtoR, g_NSEW_Offset+ratioCtoU);
	directionalBuildingDensity[1] = GetAveragedBuildingCountFromXYOffset(g_NSEW_Offset+ratioCtoR, 0.0f+ratioCtoU);
	directionalBuildingDensity[2] = GetAveragedBuildingCountFromXYOffset(0.0f+ratioCtoR, -g_NSEW_Offset+ratioCtoU);
	directionalBuildingDensity[3] = GetAveragedBuildingCountFromXYOffset(-g_NSEW_Offset+ratioCtoR, 0.0f+ratioCtoU);

	for (u32 i=0; i<4; i++)
	{
		m_DirectionalBuildingDensity[i] = m_BuildingDensityDirectionalSmoother[i].CalculateValue(m_BuildingNumToDensityFactor.CalculateValue(directionalBuildingDensity[i]), audNorthAudioEngine::GetCurrentTimeInMs());
	}
}

void naEnvironment::UpdateTreeDensityMetrics(f32 ratioCtoR, f32 ratioCtoU, u32 xCrossfadeIndex, u32 yCrossfadeIndex, u32 diagonalCrossfadeIndex)
{
	m_TreeDensitySmoother.SetRate(g_BuiltUpSmootherRate);

	f32 xRatio = Abs(ratioCtoR);
	f32 yRatio = Abs(ratioCtoU);
	f32 centreCount = GetAveragedTreeCountForIndex(AUD_CENTER_SECTOR);
	f32 xOffsetCount = GetAveragedTreeCountForIndex(xCrossfadeIndex);
	f32 yOffsetCount = GetAveragedTreeCountForIndex(yCrossfadeIndex);
	f32 diagOffsetCount = GetAveragedTreeCountForIndex(diagonalCrossfadeIndex);

	f32 baseXOffsetCount = audCurveRepository::GetLinearInterpolatedValue(centreCount, xOffsetCount, 0.0f, 1.0f, xRatio);
	f32 yOffsetXOffsetCount = audCurveRepository::GetLinearInterpolatedValue(yOffsetCount, diagOffsetCount, 0.0f, 1.0f, xRatio);
	f32 finalCount = audCurveRepository::GetLinearInterpolatedValue(baseXOffsetCount, yOffsetXOffsetCount, 0.0f, 1.0f, yRatio);

	f32 treeDensity = m_TreeNumToDensityFactor.CalculateValue(finalCount);
	f32 timeSmoothedDensity = m_TreeDensitySmoother.CalculateValue(treeDensity, audNorthAudioEngine::GetCurrentTimeInMs());

	m_TreeDensityFactor = timeSmoothedDensity;

	f32 directionalTreeDensity[4];

	directionalTreeDensity[0] = GetAveragedTreeCountFromXYOffset(0.0f+ratioCtoR, g_NSEW_Offset+ratioCtoU);
	directionalTreeDensity[1] = GetAveragedTreeCountFromXYOffset(g_NSEW_Offset+ratioCtoR, 0.0f+ratioCtoU);
	directionalTreeDensity[2] = GetAveragedTreeCountFromXYOffset(0.0f+ratioCtoR, -g_NSEW_Offset+ratioCtoU);
	directionalTreeDensity[3] = GetAveragedTreeCountFromXYOffset(-g_NSEW_Offset+ratioCtoR, 0.0f+ratioCtoU);

	for (u32 i=0; i<4; i++)
	{
		m_DirectionalTreeDensity[i] = m_TreeDensityDirectionalSmoother[i].CalculateValue(m_TreeNumToDensityFactor.CalculateValue(directionalTreeDensity[i]), audNorthAudioEngine::GetCurrentTimeInMs());
	}
}

void naEnvironment::UpdateWaterMetrics(f32 ratioCtoR, f32 ratioCtoU, u32 xCrossfadeIndex, u32 yCrossfadeIndex, u32 diagonalCrossfadeIndex)
{
	m_WaterSmoother.SetRate(g_BuiltUpSmootherRate);

	f32 xRatio = Abs(ratioCtoR);
	f32 yRatio = Abs(ratioCtoU);
	f32 centreCount = GetAveragedWaterAmountForIndex(AUD_CENTER_SECTOR);
	f32 xOffsetCount = GetAveragedWaterAmountForIndex(xCrossfadeIndex);
	f32 yOffsetCount = GetAveragedWaterAmountForIndex(yCrossfadeIndex);
	f32 diagOffsetCount = GetAveragedWaterAmountForIndex(diagonalCrossfadeIndex);

	f32 baseXOffsetCount = audCurveRepository::GetLinearInterpolatedValue(centreCount, xOffsetCount, 0.0f, 1.0f, xRatio);
	f32 yOffsetXOffsetCount = audCurveRepository::GetLinearInterpolatedValue(yOffsetCount, diagOffsetCount, 0.0f, 1.0f, xRatio);
	f32 finalCount = audCurveRepository::GetLinearInterpolatedValue(baseXOffsetCount, yOffsetXOffsetCount, 0.0f, 1.0f, yRatio);

	f32 waterFactor = m_WaterAmountToWaterFactor.CalculateValue(finalCount);
	f32 timeSmoothedFactor = m_WaterSmoother.CalculateValue(waterFactor, audNorthAudioEngine::GetCurrentTimeInMs());

	m_WaterFactor = timeSmoothedFactor;

	f32 directionalWaterAmount[4];

	directionalWaterAmount[0] = GetAveragedWaterAmountFromXYOffset(0.0f+ratioCtoR, g_NSEW_Offset+ratioCtoU);
	directionalWaterAmount[1] = GetAveragedWaterAmountFromXYOffset(g_NSEW_Offset+ratioCtoR, 0.0f+ratioCtoU);
	directionalWaterAmount[2] = GetAveragedWaterAmountFromXYOffset(0.0f+ratioCtoR, -g_NSEW_Offset+ratioCtoU);
	directionalWaterAmount[3] = GetAveragedWaterAmountFromXYOffset(-g_NSEW_Offset+ratioCtoR, 0.0f+ratioCtoU);

	for (u32 i=0; i<4; i++)
	{
		m_DirectionalWaterFactor[i] = m_WaterDirectionalSmoother[i].CalculateValue(m_WaterAmountToWaterFactor.CalculateValue(directionalWaterAmount[i]), audNorthAudioEngine::GetCurrentTimeInMs());
	}
}

void naEnvironment::UpdateHighwayMetrics(f32 ratioCtoR, f32 ratioCtoU, u32 xCrossfadeIndex, u32 yCrossfadeIndex, u32 diagonalCrossfadeIndex)
{
	m_HighwaySmoother.SetRate(g_BuiltUpSmootherRate);

	f32 xRatio = Abs(ratioCtoR);
	f32 yRatio = Abs(ratioCtoU);
	f32 centreCount = GetAveragedHighwayAmountForIndex(AUD_CENTER_SECTOR);
	f32 xOffsetCount = GetAveragedHighwayAmountForIndex(xCrossfadeIndex);
	f32 yOffsetCount = GetAveragedHighwayAmountForIndex(yCrossfadeIndex);
	f32 diagOffsetCount = GetAveragedHighwayAmountForIndex(diagonalCrossfadeIndex);

	f32 baseXOffsetCount = audCurveRepository::GetLinearInterpolatedValue(centreCount, xOffsetCount, 0.0f, 1.0f, xRatio);
	f32 yOffsetXOffsetCount = audCurveRepository::GetLinearInterpolatedValue(yOffsetCount, diagOffsetCount, 0.0f, 1.0f, xRatio);
	f32 finalCount = audCurveRepository::GetLinearInterpolatedValue(baseXOffsetCount, yOffsetXOffsetCount, 0.0f, 1.0f, yRatio);

	f32 highwayFactor = m_HighwayAmountToHighwayFactor.CalculateValue(finalCount);
	f32 timeSmoothedFactor = m_HighwaySmoother.CalculateValue(highwayFactor, audNorthAudioEngine::GetCurrentTimeInMs());

	m_HighwayFactor = timeSmoothedFactor;

	f32 directionalHighwayAmount[4];

	directionalHighwayAmount[0] = GetAveragedHighwayAmountFromXYOffset(0.0f+ratioCtoR, g_NSEW_Offset+ratioCtoU);
	directionalHighwayAmount[1] = GetAveragedHighwayAmountFromXYOffset(g_NSEW_Offset+ratioCtoR, 0.0f+ratioCtoU);
	directionalHighwayAmount[2] = GetAveragedHighwayAmountFromXYOffset(0.0f+ratioCtoR, -g_NSEW_Offset+ratioCtoU);
	directionalHighwayAmount[3] = GetAveragedHighwayAmountFromXYOffset(-g_NSEW_Offset+ratioCtoR, 0.0f+ratioCtoU);

	for (u32 i=0; i<4; i++)
	{
		m_DirectionalHighwayFactor[i] = m_HighwayDirectionalSmoother[i].CalculateValue(m_HighwayAmountToHighwayFactor.CalculateValue(directionalHighwayAmount[i]), audNorthAudioEngine::GetCurrentTimeInMs());
	}
}

f32 naEnvironment::GetBuildingHeightForSector(u32 sectorX, u32 sectorY)
{
	if(sectorX < g_audNumWorldSectorsX && sectorY < g_audNumWorldSectorsY)
	{
		const u32 sector = sectorX + (sectorY*g_audNumWorldSectorsX);
		return GetBuildingHeightForSector(sector);
//		rain.x = g_audWidthOfSector * (sectorX - (g_fAudNumWorldSectorsX/2.f));
//		rain.y = g_audDepthOfSector * (sectorY - (g_fAudNumWorldSectorsY/2.f));
	}
	else
	{
		// invalid sector - default to water (since the map is surrounded by water)
		return 0.0f;
	}
}

f32 naEnvironment::GetBuildingCountForSector(u32 sectorX, u32 sectorY)
{
	if(sectorX < g_audNumWorldSectorsX && sectorY < g_audNumWorldSectorsY)
	{
		const u32 sector = sectorX + (sectorY*g_audNumWorldSectorsX);
		return GetBuildingCountForSector(sector);
	}
	else
	{
		// invalid sector - default to water (since the map is surrounded by water)
		return 0.0f;
	}
}

f32 naEnvironment::GetTreeCountForSector(u32 sectorX, u32 sectorY)
{
	if(sectorX < g_audNumWorldSectorsX && sectorY < g_audNumWorldSectorsY)
	{
		const u32 sector = sectorX + (sectorY*g_audNumWorldSectorsX);
		return GetTreeCountForSector(sector);
	}
	else
	{
		// invalid sector - default to water (since the map is surrounded by water)
		return 0.0f;
	}
}

f32 naEnvironment::GetWaterAmountForSector(u32 sectorX, u32 sectorY)
{
	if(sectorX < g_audNumWorldSectorsX && sectorY < g_audNumWorldSectorsY)
	{
		const u32 sector = sectorX + (sectorY*g_audNumWorldSectorsX);
		return GetWaterAmountForSector(sector);
	}
	else
	{
		// invalid sector - default to water (since the map is surrounded by water)
		return 1.0f;
	}
}

f32 naEnvironment::GetHighwayAmountForSector(u32 sectorX, u32 sectorY)
{
	if(sectorX < g_audNumWorldSectorsX && sectorY < g_audNumWorldSectorsY)
	{
		const u32 sector = sectorX + (sectorY*g_audNumWorldSectorsX);
		return GetHighwayAmountForSector(sector);
	}
	else
	{
		// invalid sector
		return 0.0f;
	}
}

f32 naEnvironment::GetAveragedBuildingHeightForIndex(u32 index)
{
	// Straight average the nine heights around the requested one - we can maybe do better than this, and bias to the centre
	f32 totalHeight = 0.0f;
	for (s32 i=-AUD_NUM_SECTORS_ACROSS; i<=AUD_NUM_SECTORS_ACROSS; i+=AUD_NUM_SECTORS_ACROSS)
	{
		for (s32 j=-1; j<2; j++)
		{
			s32 finalIndex = index+j+i; 
			if (finalIndex >= 0 && finalIndex < AUD_NUM_SECTORS)
			{
				totalHeight += m_BuildingHeights[index+j+i];
			}
		}
	}
	f32 averageHeight = totalHeight/9.0f;
	return averageHeight;
}

f32 naEnvironment::GetAveragedBuildingCountForIndex(u32 index)
{
	// Straight average the nine counts around the requested one - we can maybe do better than this, and bias to the centre
	f32 totalCount = 0.0f;
	for (s32 i=-AUD_NUM_SECTORS_ACROSS; i<=AUD_NUM_SECTORS_ACROSS; i+=AUD_NUM_SECTORS_ACROSS)
	{
		for (s32 j=-1; j<2; j++)
		{
			s32 finalIndex = index+j+i; 
			if (finalIndex >= 0 && finalIndex < AUD_NUM_SECTORS)
			{
				totalCount += m_BuildingCount[index+j+i];
			}
		}
	}
	f32 averageCount = totalCount/9.0f;
	return averageCount;
}

f32 naEnvironment::GetAveragedTreeCountForIndex(u32 index)
{
	// Straight average the nine counts around the requested one - we can maybe do better than this, and bias to the centre
	f32 totalCount = 0.0f;
	for (s32 i=-AUD_NUM_SECTORS_ACROSS; i<=AUD_NUM_SECTORS_ACROSS; i+=AUD_NUM_SECTORS_ACROSS)
	{
		for (s32 j=-1; j<2; j++)
		{
			s32 finalIndex = index+j+i; 
			if (finalIndex >= 0 && finalIndex < AUD_NUM_SECTORS)
			{
				totalCount += m_TreeCount[index+j+i];
			}
		}
	}
	f32 averageCount = totalCount/9.0f;
	return averageCount;
}


f32 naEnvironment::GetAveragedWaterAmountForIndex(u32 index)
{
	// Straight average the nine counts around the requested one - we can maybe do better than this, and bias to the centre
	f32 totalCount = 0.0f;
	for (s32 i=-AUD_NUM_SECTORS_ACROSS; i<=AUD_NUM_SECTORS_ACROSS; i+=AUD_NUM_SECTORS_ACROSS)
	{
		for (s32 j=-1; j<2; j++)
		{
			s32 finalIndex = index+j+i; 
			if (finalIndex >= 0 && finalIndex < AUD_NUM_SECTORS)
			{
				totalCount += m_WaterAmount[index+j+i];
			}
		}
	}
	f32 averageCount = totalCount/9.0f;
	return averageCount;
}

f32 naEnvironment::GetAveragedHighwayAmountForIndex(u32 index)
{
	// Straight average the nine counts around the requested one - we can maybe do better than this, and bias to the centre
	f32 totalCount = 0.0f;
	for (s32 i=-AUD_NUM_SECTORS_ACROSS; i<=AUD_NUM_SECTORS_ACROSS; i+=AUD_NUM_SECTORS_ACROSS)
	{
		for (s32 j=-1; j<2; j++)
		{
			s32 finalIndex = index+j+i; 
			if (finalIndex >= 0 && finalIndex < AUD_NUM_SECTORS)
			{
				totalCount += m_HighwayAmount[index+j+i];
			}
		}
	}
	f32 averageCount = totalCount/9.0f;
	return averageCount;
}

f32 naEnvironment::GetAveragedBuildingHeightFromXYOffset(f32 xOffset, f32 yOffset)
{
	// Check we're not straying out of bounds
// valid situation now that the player can drive outside the world
// it returns 0.0 (the height of the water) so everything should be fine
//	Assert(Abs(xOffset)<(f32)(AUD_HALF_NUM_SECTORS_ACROSS));
//	Assert(Abs(yOffset)<(f32)(AUD_HALF_NUM_SECTORS_ACROSS));

	if (Abs(xOffset)>=(f32)(AUD_HALF_NUM_SECTORS_ACROSS) || Abs(yOffset)>=(f32)(AUD_HALF_NUM_SECTORS_ACROSS))
	{
		return 0.0f;
	}

	// Work out which main square we're in, then which ones to combine, and by how much
	u32 primaryXIndexOffset = (u32)Abs(xOffset);
	f32 primaryToSecondaryXRatio = 1.0f - (Abs(xOffset) - (f32)primaryXIndexOffset);

	u32 primaryYIndexOffset = (u32)Abs(yOffset);
	f32 primaryToSecondaryYRatio = 1.0f - (Abs(yOffset) - (f32)primaryYIndexOffset);

	u32 primarySquare	 = AUD_CENTER_SECTOR +  primaryXIndexOffset*(xOffset>0.0f?1:-1) + 
												(primaryYIndexOffset*(yOffset<0.0f?1:-1)*AUD_NUM_SECTORS_ACROSS);
	u32 secondaryXSquare = AUD_CENTER_SECTOR + (primaryXIndexOffset+1)*(xOffset>0.0f?1:-1) +
												(primaryYIndexOffset*(yOffset<0.0f?1:-1)*AUD_NUM_SECTORS_ACROSS);
	u32 secondaryYSquare = AUD_CENTER_SECTOR +  primaryXIndexOffset*(xOffset>0.0f?1:-1) +
												((primaryYIndexOffset+1)*(yOffset<0.0f?1:-1)*AUD_NUM_SECTORS_ACROSS);
	u32 diagSquare		 = AUD_CENTER_SECTOR + (primaryXIndexOffset+1)*(xOffset>0.0f?1:-1) +
												((primaryYIndexOffset+1)*(yOffset<0.0f?1:-1)*AUD_NUM_SECTORS_ACROSS);

	f32 primaryHeight = GetAveragedBuildingHeightForIndex(primarySquare);
	f32 xOffsetHeight = GetAveragedBuildingHeightForIndex(secondaryXSquare);
	f32 yOffsetHeight = GetAveragedBuildingHeightForIndex(secondaryYSquare);
	f32 diagOffsetHeight = GetAveragedBuildingHeightForIndex(diagSquare);

	f32 baseXOffsetHeight = audCurveRepository::GetLinearInterpolatedValue(xOffsetHeight, primaryHeight, 0.0f, 1.0f, primaryToSecondaryXRatio);
	f32 yOffsetXOffsetHeight = audCurveRepository::GetLinearInterpolatedValue(diagOffsetHeight, yOffsetHeight, 0.0f, 1.0f, primaryToSecondaryXRatio);
	f32 finalHeight = audCurveRepository::GetLinearInterpolatedValue(yOffsetXOffsetHeight, baseXOffsetHeight, 0.0f, 1.0f, primaryToSecondaryYRatio);

	return finalHeight;
}

f32 naEnvironment::GetAveragedBuildingCountFromXYOffset(f32 xOffset, f32 yOffset)
{
	// Check we're not straying out of bounds
	// valid situation now that the player can drive outside the world
	// it returns 0.0 (the height of the water) so everything should be fine
	//	Assert(Abs(xOffset)<(f32)(AUD_HALF_NUM_SECTORS_ACROSS));
	//	Assert(Abs(yOffset)<(f32)(AUD_HALF_NUM_SECTORS_ACROSS));

	if (Abs(xOffset)>=(f32)(AUD_HALF_NUM_SECTORS_ACROSS) || Abs(yOffset)>=(f32)(AUD_HALF_NUM_SECTORS_ACROSS))
	{
		return 0.0f;
	}

	// Work out which main square we're in, then which ones to combine, and by how much
	u32 primaryXIndexOffset = (u32)Abs(xOffset);
	f32 primaryToSecondaryXRatio = 1.0f - (Abs(xOffset) - (f32)primaryXIndexOffset);

	u32 primaryYIndexOffset = (u32)Abs(yOffset);
	f32 primaryToSecondaryYRatio = 1.0f - (Abs(yOffset) - (f32)primaryYIndexOffset);

	u32 primarySquare	 = AUD_CENTER_SECTOR +  primaryXIndexOffset*(xOffset>0.0f?1:-1) + 
		(primaryYIndexOffset*(yOffset<0.0f?1:-1)*AUD_NUM_SECTORS_ACROSS);
	u32 secondaryXSquare = AUD_CENTER_SECTOR + (primaryXIndexOffset+1)*(xOffset>0.0f?1:-1) +
		(primaryYIndexOffset*(yOffset<0.0f?1:-1)*AUD_NUM_SECTORS_ACROSS);
	u32 secondaryYSquare = AUD_CENTER_SECTOR +  primaryXIndexOffset*(xOffset>0.0f?1:-1) +
		((primaryYIndexOffset+1)*(yOffset<0.0f?1:-1)*AUD_NUM_SECTORS_ACROSS);
	u32 diagSquare		 = AUD_CENTER_SECTOR + (primaryXIndexOffset+1)*(xOffset>0.0f?1:-1) +
		((primaryYIndexOffset+1)*(yOffset<0.0f?1:-1)*AUD_NUM_SECTORS_ACROSS);

	f32 primaryCount = GetAveragedBuildingCountForIndex(primarySquare);
	f32 xOffsetCount = GetAveragedBuildingCountForIndex(secondaryXSquare);
	f32 yOffsetCount = GetAveragedBuildingCountForIndex(secondaryYSquare);
	f32 diagOffsetCount = GetAveragedBuildingCountForIndex(diagSquare);

	f32 baseXOffsetCount = audCurveRepository::GetLinearInterpolatedValue(xOffsetCount, primaryCount, 0.0f, 1.0f, primaryToSecondaryXRatio);
	f32 yOffsetXOffsetCount = audCurveRepository::GetLinearInterpolatedValue(diagOffsetCount, yOffsetCount, 0.0f, 1.0f, primaryToSecondaryXRatio);
	f32 finalCount = audCurveRepository::GetLinearInterpolatedValue(yOffsetXOffsetCount, baseXOffsetCount, 0.0f, 1.0f, primaryToSecondaryYRatio);

	return finalCount;
}

f32 naEnvironment::GetAveragedTreeCountFromXYOffset(f32 xOffset, f32 yOffset)
{
	// Check we're not straying out of bounds
	// valid situation now that the player can drive outside the world
	// it returns 0.0 (the height of the water) so everything should be fine
	//	Assert(Abs(xOffset)<(f32)(AUD_HALF_NUM_SECTORS_ACROSS));
	//	Assert(Abs(yOffset)<(f32)(AUD_HALF_NUM_SECTORS_ACROSS));

	if (Abs(xOffset)>=(f32)(AUD_HALF_NUM_SECTORS_ACROSS) || Abs(yOffset)>=(f32)(AUD_HALF_NUM_SECTORS_ACROSS))
	{
		return 0.0f;
	}

	// Work out which main square we're in, then which ones to combine, and by how much
	u32 primaryXIndexOffset = (u32)Abs(xOffset);
	f32 primaryToSecondaryXRatio = 1.0f - (Abs(xOffset) - (f32)primaryXIndexOffset);

	u32 primaryYIndexOffset = (u32)Abs(yOffset);
	f32 primaryToSecondaryYRatio = 1.0f - (Abs(yOffset) - (f32)primaryYIndexOffset);

	u32 primarySquare	 = AUD_CENTER_SECTOR +  primaryXIndexOffset*(xOffset>0.0f?1:-1) + 
		(primaryYIndexOffset*(yOffset<0.0f?1:-1)*AUD_NUM_SECTORS_ACROSS);
	u32 secondaryXSquare = AUD_CENTER_SECTOR + (primaryXIndexOffset+1)*(xOffset>0.0f?1:-1) +
		(primaryYIndexOffset*(yOffset<0.0f?1:-1)*AUD_NUM_SECTORS_ACROSS);
	u32 secondaryYSquare = AUD_CENTER_SECTOR +  primaryXIndexOffset*(xOffset>0.0f?1:-1) +
		((primaryYIndexOffset+1)*(yOffset<0.0f?1:-1)*AUD_NUM_SECTORS_ACROSS);
	u32 diagSquare		 = AUD_CENTER_SECTOR + (primaryXIndexOffset+1)*(xOffset>0.0f?1:-1) +
		((primaryYIndexOffset+1)*(yOffset<0.0f?1:-1)*AUD_NUM_SECTORS_ACROSS);

	f32 primaryCount = GetAveragedTreeCountForIndex(primarySquare);
	f32 xOffsetCount = GetAveragedTreeCountForIndex(secondaryXSquare);
	f32 yOffsetCount = GetAveragedTreeCountForIndex(secondaryYSquare);
	f32 diagOffsetCount = GetAveragedTreeCountForIndex(diagSquare);

	f32 baseXOffsetCount = audCurveRepository::GetLinearInterpolatedValue(xOffsetCount, primaryCount, 0.0f, 1.0f, primaryToSecondaryXRatio);
	f32 yOffsetXOffsetCount = audCurveRepository::GetLinearInterpolatedValue(diagOffsetCount, yOffsetCount, 0.0f, 1.0f, primaryToSecondaryXRatio);
	f32 finalCount = audCurveRepository::GetLinearInterpolatedValue(yOffsetXOffsetCount, baseXOffsetCount, 0.0f, 1.0f, primaryToSecondaryYRatio);

	return finalCount;
}

f32 naEnvironment::GetAveragedWaterAmountFromXYOffset(f32 xOffset, f32 yOffset)
{
	// Check we're not straying out of bounds
	// valid situation now that the player can drive outside the world
	// it returns 0.0 (the height of the water) so everything should be fine
	//	Assert(Abs(xOffset)<(f32)(AUD_HALF_NUM_SECTORS_ACROSS));
	//	Assert(Abs(yOffset)<(f32)(AUD_HALF_NUM_SECTORS_ACROSS));

	if (Abs(xOffset)>=(f32)(AUD_HALF_NUM_SECTORS_ACROSS) || Abs(yOffset)>=(f32)(AUD_HALF_NUM_SECTORS_ACROSS))
	{
		return 0.0f;
	}

	// Work out which main square we're in, then which ones to combine, and by how much
	u32 primaryXIndexOffset = (u32)Abs(xOffset);
	f32 primaryToSecondaryXRatio = 1.0f - (Abs(xOffset) - (f32)primaryXIndexOffset);

	u32 primaryYIndexOffset = (u32)Abs(yOffset);
	f32 primaryToSecondaryYRatio = 1.0f - (Abs(yOffset) - (f32)primaryYIndexOffset);

	u32 primarySquare	 = AUD_CENTER_SECTOR +  primaryXIndexOffset*(xOffset>0.0f?1:-1) + 
		(primaryYIndexOffset*(yOffset<0.0f?1:-1)*AUD_NUM_SECTORS_ACROSS);
	u32 secondaryXSquare = AUD_CENTER_SECTOR + (primaryXIndexOffset+1)*(xOffset>0.0f?1:-1) +
		(primaryYIndexOffset*(yOffset<0.0f?1:-1)*AUD_NUM_SECTORS_ACROSS);
	u32 secondaryYSquare = AUD_CENTER_SECTOR +  primaryXIndexOffset*(xOffset>0.0f?1:-1) +
		((primaryYIndexOffset+1)*(yOffset<0.0f?1:-1)*AUD_NUM_SECTORS_ACROSS);
	u32 diagSquare		 = AUD_CENTER_SECTOR + (primaryXIndexOffset+1)*(xOffset>0.0f?1:-1) +
		((primaryYIndexOffset+1)*(yOffset<0.0f?1:-1)*AUD_NUM_SECTORS_ACROSS);

	f32 primaryCount = GetAveragedWaterAmountForIndex(primarySquare);
	f32 xOffsetCount = GetAveragedWaterAmountForIndex(secondaryXSquare);
	f32 yOffsetCount = GetAveragedWaterAmountForIndex(secondaryYSquare);
	f32 diagOffsetCount = GetAveragedWaterAmountForIndex(diagSquare);

	f32 baseXOffsetCount = audCurveRepository::GetLinearInterpolatedValue(xOffsetCount, primaryCount, 0.0f, 1.0f, primaryToSecondaryXRatio);
	f32 yOffsetXOffsetCount = audCurveRepository::GetLinearInterpolatedValue(diagOffsetCount, yOffsetCount, 0.0f, 1.0f, primaryToSecondaryXRatio);
	f32 finalCount = audCurveRepository::GetLinearInterpolatedValue(yOffsetXOffsetCount, baseXOffsetCount, 0.0f, 1.0f, primaryToSecondaryYRatio);

	return finalCount;
}

f32 naEnvironment::GetAveragedHighwayAmountFromXYOffset(f32 xOffset, f32 yOffset)
{
	// Check we're not straying out of bounds
	// valid situation now that the player can drive outside the world
	// it returns 0.0 (the height of the water) so everything should be fine
	//	Assert(Abs(xOffset)<(f32)(AUD_HALF_NUM_SECTORS_ACROSS));
	//	Assert(Abs(yOffset)<(f32)(AUD_HALF_NUM_SECTORS_ACROSS));

	if (Abs(xOffset)>=(f32)(AUD_HALF_NUM_SECTORS_ACROSS) || Abs(yOffset)>=(f32)(AUD_HALF_NUM_SECTORS_ACROSS))
	{
		return 0.0f;
	}

	// Work out which main square we're in, then which ones to combine, and by how much
	u32 primaryXIndexOffset = (u32)Abs(xOffset);
	f32 primaryToSecondaryXRatio = 1.0f - (Abs(xOffset) - (f32)primaryXIndexOffset);

	u32 primaryYIndexOffset = (u32)Abs(yOffset);
	f32 primaryToSecondaryYRatio = 1.0f - (Abs(yOffset) - (f32)primaryYIndexOffset);

	u32 primarySquare	 = AUD_CENTER_SECTOR +  primaryXIndexOffset*(xOffset>0.0f?1:-1) + 
		(primaryYIndexOffset*(yOffset<0.0f?1:-1)*AUD_NUM_SECTORS_ACROSS);
	u32 secondaryXSquare = AUD_CENTER_SECTOR + (primaryXIndexOffset+1)*(xOffset>0.0f?1:-1) +
		(primaryYIndexOffset*(yOffset<0.0f?1:-1)*AUD_NUM_SECTORS_ACROSS);
	u32 secondaryYSquare = AUD_CENTER_SECTOR +  primaryXIndexOffset*(xOffset>0.0f?1:-1) +
		((primaryYIndexOffset+1)*(yOffset<0.0f?1:-1)*AUD_NUM_SECTORS_ACROSS);
	u32 diagSquare		 = AUD_CENTER_SECTOR + (primaryXIndexOffset+1)*(xOffset>0.0f?1:-1) +
		((primaryYIndexOffset+1)*(yOffset<0.0f?1:-1)*AUD_NUM_SECTORS_ACROSS);

	f32 primaryCount = GetAveragedHighwayAmountForIndex(primarySquare);
	f32 xOffsetCount = GetAveragedHighwayAmountForIndex(secondaryXSquare);
	f32 yOffsetCount = GetAveragedHighwayAmountForIndex(secondaryYSquare);
	f32 diagOffsetCount = GetAveragedHighwayAmountForIndex(diagSquare);

	f32 baseXOffsetCount = audCurveRepository::GetLinearInterpolatedValue(xOffsetCount, primaryCount, 0.0f, 1.0f, primaryToSecondaryXRatio);
	f32 yOffsetXOffsetCount = audCurveRepository::GetLinearInterpolatedValue(diagOffsetCount, yOffsetCount, 0.0f, 1.0f, primaryToSecondaryXRatio);
	f32 finalCount = audCurveRepository::GetLinearInterpolatedValue(yOffsetXOffsetCount, baseXOffsetCount, 0.0f, 1.0f, primaryToSecondaryYRatio);

	return finalCount;
}

void naEnvironment::MapDirectionalMetricsToSpeakers()
{
	Matrix34 listenerMatrix = MAT34V_TO_MATRIX34(g_AudioEngine.GetEnvironment().GetPanningListenerMatrix());
	static Vector3 directions[4];
	directions[0] = Vector3(1.0f, 0.0f, 0.0f); //N
	directions[1] = Vector3(0.0f, -1.0f, 0.0f); //E
	directions[2] = Vector3(-1.0f, 0.0f, 0.0f); //S
	directions[3] = Vector3(0.0f, 1.0f, 0.0f); //W

	Vector3 speakerPosition[4]; // these are in some different space, where 1,0,0 is forwards, and 0,-1,0 is right.
	speakerPosition[0] = Vector3(0.7071f, 0.7071f, 0.0f); // FL
	speakerPosition[1] = Vector3(0.7071f, -0.7071f, 0.0f); // FR
	speakerPosition[2] = Vector3(-0.7071f, 0.7071f, 0.0f); // RL
	speakerPosition[3] = Vector3(-0.7071f, -0.7071f, 0.0f); // RR 

	// we're always NESW from that, so ignore any positional aspect
	listenerMatrix.d.Zero();

	// This does the same clipped dot-product followed by elevation leakage that the panning algorithm and listener reverb do. 
	// quick elevation check - will be the same elevation for all directions
	f32 elevation = Abs(listenerMatrix.c.z); // 1 for not at all elevated, 0 for fully

	f32 speakerBuiltUpFactor[4];
	f32 speakerBlockedFactor[4];
	f32 speakerBlockedLinearVolume[4];
	f32 speakerOWOFactor[4];
	for (u32 i=0; i<4; i++) // 4 speakers
	{
		speakerBuiltUpFactor[i] = 0.0f;
		speakerBlockedFactor[i] = 0.0f;
		speakerBlockedLinearVolume[i] = 0.0f;
		speakerOWOFactor[i] = 0.0f;
		for (u32 j=0; j<4; j++) // 4 directional metrics
		{
			// Where is the poly relative to us
			Vector3 directionRelative = directions[j];
			listenerMatrix.UnTransform(directionRelative);

			Vector3 positionOnUnitCircle = directionRelative;
			positionOnUnitCircle.z = 0.0f;
			positionOnUnitCircle.NormalizeSafe();
			f32 dotProd = positionOnUnitCircle.Dot(speakerPosition[i]);
			// Don't care about ones more than 90degs away
			dotProd = Max(0.0f, dotProd);
			// Scale our contribution by the elevation
			f32 contribution = ((1.0f - elevation) * (1.0f/4.0f)) + (elevation * dotProd * dotProd);
			speakerBuiltUpFactor[i] += (contribution * m_DirectionalBuiltUpFactor[j]);
			f32 exteriorOcclusionInDirection = audNorthAudioEngine::GetOcclusionManager()->GetExteriorOcclusionForDirection((audAmbienceDirection)j);
			speakerBlockedFactor[i] += (contribution * audNorthAudioEngine::GetOcclusionManager()->GetBlockedFactorForDirection((audAmbienceDirection)j));
			speakerBlockedLinearVolume[i] += (contribution * audNorthAudioEngine::GetOcclusionManager()->GetBlockedLinearVolumeForDirection((audAmbienceDirection)j));
			speakerOWOFactor[i] += (contribution * exteriorOcclusionInDirection);
		}
		m_SpeakerBuiltUpFactors[i] = m_SpeakerBuiltUpSmoother[i].CalculateValue(speakerBuiltUpFactor[i], audNorthAudioEngine::GetCurrentTimeInMs());
		m_SpeakerBlockedFactor[i] = speakerBlockedFactor[i];
		m_SpeakerBlockedLinearVolume[i] = speakerBlockedLinearVolume[i];
		m_SpeakerOWOFactor[i] = speakerOWOFactor[i];
	}

#if __BANK
	if (g_DebugBuiltUpInfo)
	{
		char builtUpFactor[256] = "";
		sprintf(builtUpFactor, "el: %f; FL: %f; FR: %f; RL: %f; RR: %f", elevation, speakerBuiltUpFactor[0], speakerBuiltUpFactor[1], speakerBuiltUpFactor[2], speakerBuiltUpFactor[3]);
		grcDebugDraw::PrintToScreenCoors(builtUpFactor, 32,37);
	}
#endif

	g_AudioEngine.GetEnvironment().SetBuiltUpSpeakerFactors(m_SpeakerBuiltUpFactors);
	g_AudioEngine.GetEnvironment().SetBlockedSpeakerFactors(m_SpeakerBlockedFactor);
	g_AudioEngine.GetEnvironment().SetBlockedSpeakerLinearVolumes(m_SpeakerBlockedLinearVolume);
}

f32 naEnvironment::GetBuildingHeightForSector(const u32 sector) const
{
	return (f32)(g_AudioWorldSectors[sector].tallestBuilding);
}

f32 naEnvironment::GetBuildingCountForSector(const u32 sector) const
{
	return (f32)(g_AudioWorldSectors[sector].numBuildings);
}

f32 naEnvironment::GetTreeCountForSector(const u32 sector) const
{
	return (f32)(g_AudioWorldSectors[sector].numTrees);
}

f32 naEnvironment::GetWaterAmountForSector(const u32 sector) const
{
	if(g_AudioWorldSectors[sector].isWaterSector &&
	   g_AudioWorldSectors[sector].numTrees == 0 &&
	   g_AudioWorldSectors[sector].numBuildings == 0)
	{
		return 1.0f;
	}

	return 0.0f;
}

f32 naEnvironment::GetHighwayAmountForSector(const u32 sector) const
{
	return (f32)g_AudioWorldSectors[sector].numHighwayNodes;
}

f32 naEnvironment::GetRollOffFactor()
{
	return m_BuiltUpToRolloff.CalculateValue(g_AudioEngine.GetEnvironment().GetBuiltUpFactor());
}
bool naEnvironment::IsCameraShelteredFromRain() const
{
	// only return true when all probes are colliding
	for(u32 i = 0 ; i < VFXWEATHER_NUM_WEATHER_PROBES; i++)
	{
		if(!g_vfxWeather.GetWeatherProbeResult(i))
		{
			return false;
		}
	}
	return true;
}


f32 naEnvironment::GetSpeakerReverbWet(u32 speaker, u32 reverb)
{
	if(naVerifyf(speaker < 4, "Invalid speaker passed into GetSpeakerReverbWet; out of bounds")
		&& naVerifyf(reverb < 3, "Invalid reverb passed into GetSpeakerReverbWet; out of bounds"))
	{
		return m_SpeakerReverbWets[speaker][reverb];
	}
	return 0.f;
}

CEntity* naEnvironment::GetInterestingLocalObject(u32 index) 
{
	if(naVerifyf(index < g_MaxInterestingLocalObjects, "Invalid index passed into GetInterestingLocalObject; out of bounds"))
	{
		return m_InterestingLocalObjects[index].object;
	}
	return NULL;
}
const CEntity* naEnvironment::GetInterestingLocalObject(u32 index) const
{
	if(naVerifyf(index < g_MaxInterestingLocalObjects, "Invalid index passed into GetInterestingLocalObject; out of bounds"))
	{
		return m_InterestingLocalObjects[index].object;
	}
	return NULL;
}
void naEnvironment::GetInterestingLocalObjectInfo(u32 index,audInterestingObjectInfo &objectInfo) 
{
	if(naVerifyf(index < g_MaxInterestingLocalObjects, "Invalid index passed into GetInterestingLocalObject; out of bounds"))
	{
		objectInfo = m_InterestingLocalObjects[index];
	}
}
const audInterestingObjectInfo *naEnvironment::GetInterestingLocalObjectInfo(u32 index) const
{
	if(naVerifyf(index < g_MaxInterestingLocalObjects, "Invalid index passed into GetInterestingLocalObject; out of bounds"))
	{
		return &m_InterestingLocalObjects[index];
	}
	return NULL;
}
const ModelAudioCollisionSettings * naEnvironment::GetInterestingLocalObjectMaterial(u32 index) const
{
	const audInterestingObjectInfo *objectInfo = GetInterestingLocalObjectInfo(index);
	if(objectInfo && objectInfo->object)
	{
		if(objectInfo->macsComponent == -1)
		{
			return (objectInfo->object->GetBaseModelInfo()->GetAudioCollisionSettings());
		}
		else
		{
			return audCollisionAudioEntity::GetFragComponentMaterialSettings(objectInfo->object,(u32)objectInfo->macsComponent);
		}
	}
	return NULL;
}
CEntity *naEnvironment::GetInterestingLocalObjectCached(u32 index) 
{
	if(naVerifyf(index < m_NumInterestingLocalObjectsCached, "Invalid index passed into GetInterestingLocalObjectCached; out of bounds"))
	{
		return m_InterestingLocalObjectsCached[index].object;
	}
	return NULL;
}
naEnvironmentGroup *naEnvironment::GetInterestingLocalEnvGroup(u32 index) 
{
	if(naVerifyf(index < g_MaxInterestingLocalObjects, "Invalid index passed into GetInterestingLocalEnvGroupCached; out of bounds"))
	{
		return m_InterestingLocalObjects[index].envGroup;
	}
	return NULL;
}
naEnvironmentGroup *naEnvironment::GetInterestingLocalEnvGroupCached(u32 index) 
{
	if(naVerifyf(index < m_NumInterestingLocalObjectsCached, "Invalid index passed into GetInterestingLocalEnvGroupCached; out of bounds"))
	{
		return m_InterestingLocalObjectsCached[index].envGroup;
	}
	return NULL;
}
u32 naEnvironment::GetNumInterestingLocalObjectsCached() const
{
	return m_NumInterestingLocalObjectsCached;
}

bool naEnvironment::IsObjectUnderCover(u32 index)
{
	naAssertf(index < g_MaxInterestingLocalObjects, "Invalid index passed into GetInterestingLocalObject; out of bounds");
#if __BANK
	if(sm_UseCoverAproximation)
	{
		return m_InterestingLocalObjects[index].underCover;
	}
#endif
	if(m_InterestingLocalObjects[index].envGroup)
	{
		return m_InterestingLocalObjects[index].envGroup->IsUnderCover();
	}
	return true;
}

void naEnvironment::AddInterestingObject(CEntity *obj,const u32 shockwaveSoundHash, const u32 resoSoundHash,s32 component ,bool /*checkCovered*/)
{
	if(naVerifyf(obj, "Null CEntity ptr passed into AddInter estingObject"))
	{
		naAssertf(obj->GetOwnedBy()!=ENTITY_OWNEDBY_PROCEDURAL, "Found a procedural entity tagged with audio, please bug the audio team.");
		if(m_NumInterestingLocalObjectsCached < g_MaxInterestingLocalObjects)
		{
			naAssertf(!m_InterestingLocalObjectsCached[m_NumInterestingLocalObjectsCached].object, "Trying to add an interesting object but slot is already occupied");
			// Set the entity.
			m_InterestingLocalObjectsCached[m_NumInterestingLocalObjectsCached].object = obj;
			// Before setting anything up, check if we already had the info in the cached list.
			s32 objectIndex = -1;
			for(s32 i = g_MaxInterestingLocalObjects - 1; i >= 0; i--)
			{
				if(audNorthAudioEngine::GetGtaEnvironment()->GetInterestingLocalObject(i) == obj)
				{
					objectIndex = i;
					break;
				}
			}
			if( objectIndex != -1 )
			{
				audNorthAudioEngine::GetGtaEnvironment()->GetInterestingLocalObjectInfo(objectIndex,m_InterestingLocalObjectsCached[m_NumInterestingLocalObjectsCached]);
			}
			else
			{
				m_InterestingLocalObjectsCached[m_NumInterestingLocalObjectsCached].macsComponent = component;
				m_InterestingLocalObjectsCached[m_NumInterestingLocalObjectsCached].shockwaveSoundInfo.prevVol = 0.0f;
				if (shockwaveSoundHash != g_NullSoundHash)
				{
					bool hasCustomAttackReleaseTime = false;
					f32 customAttackTime = 0.f;
					f32 customReleaseTime = 0.f;

					Sound uncompressedMetadata;
					const VariableBlockSound *vbSound = SOUNDFACTORY.DecompressMetadata<VariableBlockSound>(shockwaveSoundHash, uncompressedMetadata);
					if (vbSound)
					{
						for (u32 i = 0; i < vbSound->numVariables; i++)
						{
							if (vbSound->Variable[i].VariableRef == ATSTRINGHASH("AttackTime", 0xF0104A91))
							{
								customAttackTime = vbSound->Variable[i].VariableValue;
								hasCustomAttackReleaseTime = true;
							}

							if (vbSound->Variable[i].VariableRef == ATSTRINGHASH("ReleaseTime", 0x19482A71))
							{								
								customReleaseTime = vbSound->Variable[i].VariableValue;
								hasCustomAttackReleaseTime = true;
							}
						}
					}
					
					m_InterestingLocalObjectsCached[m_NumInterestingLocalObjectsCached].shockwaveSoundInfo.attackTime = hasCustomAttackReleaseTime ? Max(0.00001f, customAttackTime) : -1.f;
					m_InterestingLocalObjectsCached[m_NumInterestingLocalObjectsCached].shockwaveSoundInfo.releaseTime = hasCustomAttackReleaseTime ? Max(0.00001f, customReleaseTime) : -1.f;
				}
				
				if(resoSoundHash != g_NullSoundHash)
				{
					Sound uncompressedMetadata;
					const EnvelopeSound *envSound = SOUNDFACTORY.DecompressMetadata<EnvelopeSound>(resoSoundHash, uncompressedMetadata);
					if(envSound) 
					{
						m_InterestingLocalObjectsCached[m_NumInterestingLocalObjectsCached].resoSoundInfo.attackTime = (f32)audSound::ApplyVariance(envSound->Envelope.Attack, envSound->Envelope.AttackVariance, 1<<16U);
						m_InterestingLocalObjectsCached[m_NumInterestingLocalObjectsCached].resoSoundInfo.attackTime *= 0.001f;
						m_InterestingLocalObjectsCached[m_NumInterestingLocalObjectsCached].resoSoundInfo.releaseTime = (f32)audSound::ApplyVariance(envSound->Envelope.Release, envSound->Envelope.ReleaseVariance, 1<<16U);
						m_InterestingLocalObjectsCached[m_NumInterestingLocalObjectsCached].resoSoundInfo.releaseTime *= 0.001f;
						m_InterestingLocalObjectsCached[m_NumInterestingLocalObjectsCached].resoSoundInfo.resoSoundRef = envSound->SoundRef;
					}
				}

			}
			m_NumInterestingLocalObjectsCached ++;
		}
	}
}
void naEnvironment::AddLocalTree(CEntity *tree)
{
	if (tree->GetTransform().GetPosition().GetZf() > m_WaterHeightAtListener &&  (tree->GetBoundRadius() > sm_BushesMinRadius) && (m_NumLocalTrees < (g_MaxInterestingLocalTrees - 1)))
	{
		//	Add it to the list
		m_LocalTrees[m_NumLocalTrees].tree = tree;
		// update our backGround tree info. 
		// First get the sector: FL,FR,RL,RR
		Vec3V treePos2D = tree->GetTransform().GetPosition();
		treePos2D.SetZ(0);
		Vec3V listenerPos2D = g_AudioEngine.GetEnvironment().GetVolumeListenerMatrix().GetCol3();
		listenerPos2D.SetZ(0);
		Vec3V dirToTree = Subtract(treePos2D, listenerPos2D);
		//f32 distance = fabs(((ScalarV)Mag(dirToTree)).Getf());
		dirToTree = Normalize(dirToTree);
		float frontDot = Dot( Vec3V(0.0f,1.f,0.0f), dirToTree).Getf();
		float rightDot = Dot( Vec3V(1.f,0.0f,0.0f), dirToTree).Getf();

		audFBSectors sector = AUD_SECTOR_FL;
		
		if((frontDot >= 0.f))
		{
			if(rightDot >= 0.f)
				sector = AUD_SECTOR_FR;
			else
				sector = AUD_SECTOR_FL; 
		}
		else
		{
			if(rightDot >= 0.f)
				sector = AUD_SECTOR_RR; 
			else
				sector = AUD_SECTOR_RL; 
		}
		m_LocalTrees[m_NumLocalTrees].sector = sector;

		if(tree->GetBoundRadius() >= sm_BigTreesMinRadius)
		{
			m_BackGroundTrees[sector].numBigTrees ++ ;
			m_LocalTrees[m_NumLocalTrees ++].type = AUD_BIG_TREES;
		}
		else if(tree->GetBoundRadius() >= sm_SmallTreesMinRadius) 
		{
			m_BackGroundTrees[sector].numTrees ++ ;
			m_LocalTrees[m_NumLocalTrees ++].type = AUD_SMALL_TREES;
		}
		else
		{
			m_BackGroundTrees[sector].numBushes ++ ;
			m_LocalTrees[m_NumLocalTrees ++].type = AUD_BUSHES;
		}
	}
}

#if __DEV
void naEnvironment::DebugDraw()
{
}
#endif
	
#if __BANK
void naEnvironment::CreateRAVEInteriorRoomObject(const char* instanceName, const char* roomName)
{
	if(roomName)
	{
		char xmlMsg[4096];

		sprintf(xmlMsg, "<RAVEMessage>\n");
		naDisplayf("%s", xmlMsg);
		char tmpBuf[256] = {0};
		sprintf(tmpBuf, "	<EditObjects metadataType=\"GAMEOBJECTS\" chunkNameHash=\"%u\">\n", (u32)audStringHash("BASE"));
		naDisplayf("%s", tmpBuf);
		strcat(xmlMsg, tmpBuf);
		sprintf(tmpBuf, "		<InteriorRoom name=\"%s_%s\">\n", instanceName, roomName);
		naDisplayf("%s", tmpBuf);
		strcat(xmlMsg, tmpBuf);
		sprintf(tmpBuf, "			<RoomName>%s</RoomName>\n", roomName);
		naDisplayf("%s", tmpBuf);
		strcat(xmlMsg, tmpBuf);
		sprintf(tmpBuf, "			<ReverbSmall>%f</ReverbSmall>\n", g_DebugRoomSettingsReverbSmall);
		naDisplayf("%s", tmpBuf);
		strcat(xmlMsg, tmpBuf);
		sprintf(tmpBuf, "			<ReverbMedium>%f</ReverbMedium>\n", g_DebugRoomSettingsReverbMedium);
		naDisplayf("%s", tmpBuf);
		strcat(xmlMsg, tmpBuf);
		sprintf(tmpBuf, "			<ReverbLarge>%f</ReverbLarge>\n", g_DebugRoomSettingsReverbLarge);
		naDisplayf("%s", tmpBuf);
		strcat(xmlMsg, tmpBuf);
		sprintf(tmpBuf, "		</InteriorRoom>\n");
		naDisplayf("%s", tmpBuf);
		strcat(xmlMsg, tmpBuf);
		sprintf(tmpBuf, "	</EditObjects>\n");
		naDisplayf("%s", tmpBuf);
		strcat(xmlMsg, tmpBuf);
		sprintf(tmpBuf, "</RAVEMessage>\n");
		naDisplayf("%s", tmpBuf);
		strcat(xmlMsg, tmpBuf);

		naDisplayf("XML message is %" SIZETFMT "d characters long \n", strlen(xmlMsg));

		audRemoteControl &rc = g_AudioEngine.GetRemoteControl();
		bool success = rc.SendXmlMessage(xmlMsg, istrlen(xmlMsg));
		naAssertf(success, "Failed to send xml message to rave");
		naDisplayf("%s", (success)? "Success":"Failed");
	}
}

void naEnvironment::CreateRAVEObjectsForCurrentInteriorAndRooms()
{
	CInteriorInst* pIntInst = CPortalVisTracker::GetPrimaryInteriorInst();

	if(pIntInst)
	{
		for(u32 instanceRoomLoop = 0; instanceRoomLoop < pIntInst->GetNumRooms(); instanceRoomLoop++)
		{
			const char* instanceName = pIntInst->GetModelName();
			const char* roomName = pIntInst->GetRoomName(instanceRoomLoop);
			CreateRAVEInteriorRoomObject(instanceName, roomName);
		}

		CreateRAVEObjectsForCurrentInterior();
	}
}

void naEnvironment::CreateRAVEObjectsForCurrentInterior()
{
	CInteriorInst* pIntInst = CPortalVisTracker::GetPrimaryInteriorInst();

	if(pIntInst)
	{
		char xmlMsg[4096];
		const char* instanceName = pIntInst->GetModelName();

		sprintf(xmlMsg, "<RAVEMessage>\n");
		naDisplayf("%s", xmlMsg);
		char tmpBuf[256] = {0};
		sprintf(tmpBuf, "	<EditObjects metadataType=\"GAMEOBJECTS\" chunkNameHash=\"%u\">\n", (u32)audStringHash("BASE"));
		naDisplayf("%s", tmpBuf);
		strcat(xmlMsg, tmpBuf);
		sprintf(tmpBuf, "		<InteriorSettings name=\"%s\">\n", instanceName);
		naDisplayf("%s", tmpBuf);
		strcat(xmlMsg, tmpBuf);

		for(u32 instanceRoomLoop = 0; instanceRoomLoop < pIntInst->GetNumRooms(); instanceRoomLoop++)
		{
			const char* roomName = pIntInst->GetRoomName(instanceRoomLoop);

			sprintf(tmpBuf, "			<Room>\n");
			naDisplayf("%s", tmpBuf);
			strcat(xmlMsg, tmpBuf);
			sprintf(tmpBuf, "				<Ref>%s_%s</Ref>\n", instanceName, roomName);
			naDisplayf("%s", tmpBuf);
			strcat(xmlMsg, tmpBuf);
			sprintf(tmpBuf, "			</Room>\n");
			naDisplayf("%s", tmpBuf);
			strcat(xmlMsg, tmpBuf);
		}

		sprintf(tmpBuf, "		</InteriorSettings>\n");
		naDisplayf("%s", tmpBuf);
		strcat(xmlMsg, tmpBuf);
		sprintf(tmpBuf, "	</EditObjects>\n");
		naDisplayf("%s", tmpBuf);
		strcat(xmlMsg, tmpBuf);
		sprintf(tmpBuf, "</RAVEMessage>\n");
		naDisplayf("%s", tmpBuf);
		strcat(xmlMsg, tmpBuf);

		naDisplayf("XML message is %" SIZETFMT "d characters long \n", strlen(xmlMsg));

		audRemoteControl &rc = g_AudioEngine.GetRemoteControl();
		bool success = rc.SendXmlMessage(xmlMsg, istrlen(xmlMsg));
		naAssertf(success, "Failed to send xml message to rave");
		naDisplayf("%s", (success)? "Success":"Failed");
	}
}

void naEnvironment::CreateRAVEObjectsForCurrentRoom()
{
	CInteriorInst* pIntInst = CPortalVisTracker::GetPrimaryInteriorInst();

	if(pIntInst)
	{
		s32 roomIndex = CPortalVisTracker::GetPrimaryRoomIdx();

		if(roomIndex < pIntInst->GetNumRooms())
		{
			const char* instanceName = pIntInst->GetModelName();
			const char* roomName = pIntInst->GetRoomName(roomIndex);
			CreateRAVEInteriorRoomObject(instanceName, roomName);
		}
	}
}

void DebugDrawSector(const u32 sectorX, const u32 sectorY)
{
	if(sectorX < g_audNumWorldSectorsX && sectorY < g_audNumWorldSectorsY)
	{
		const u32 sectorIndex = sectorX + (sectorY * g_audNumWorldSectorsX);

		Color32 colour(255,0,0);

		const f32 x = g_audWidthOfSector * (sectorX - (g_fAudNumWorldSectorsX/2.f));
		const f32 y = g_audDepthOfSector * (sectorY - (g_fAudNumWorldSectorsY/2.f));

		Vector3 v0,v1,v2,v3;

		v0.z = v1.z = v2.z = v3.z = 810.0f;

		v0.x = x;
		v0.y = y;

		v1.x = x + g_audWidthOfSector;
		v1.y = y;

		v2.x = x;
		v2.y = y + g_audDepthOfSector;

		v3.x = x + g_audWidthOfSector;
		v3.y = y + g_audDepthOfSector;

		Vector3 centre = (v0 + v3) * 0.5f;

		centre.z = v0.z = v1.z = v2.z = v3.z = 70.f;
		if(g_AudioWorldSectors[sectorIndex].isWaterSector)
		{
			colour = Color32(0,0,255);
		}

		grcDebugDraw::Line(v0,v1,colour);
		grcDebugDraw::Line(v1,v3,colour);
		grcDebugDraw::Line(v3,v2,colour);
		grcDebugDraw::Line(v2,v0,colour);

		char rainDebug[256] = "";

		formatf(rainDebug, 255, "%d: numHighwayNodes %d tallestBuilding: %d numBuildings: %d numTrees: %u", sectorIndex, g_AudioWorldSectors[sectorIndex].numHighwayNodes, g_AudioWorldSectors[sectorIndex].tallestBuilding, g_AudioWorldSectors[sectorIndex].numBuildings, g_AudioWorldSectors[sectorIndex].numTrees);

		grcDebugDraw::Text(centre, colour, rainDebug);
	}
}

void DebugDrawRoadInfo(const u32 sectorX, const u32 sectorY)
{
	if(sectorX < g_audNumWorldSectorsX && sectorY < g_audNumWorldSectorsY)
	{
		const u32 sectorIndex = sectorX + (sectorY * g_audNumWorldSectorsX);

		Color32 colour(255,0,0);

		const f32 x = g_audWidthOfSector * (sectorX - (g_fAudNumWorldSectorsX/2.f));
		const f32 y = g_audDepthOfSector * (sectorY - (g_fAudNumWorldSectorsY/2.f));

		Vector3 v0,v1;

		v0.x = x;
		v0.y = y;

		v1.x = x + g_audWidthOfSector;
		v1.y = y + g_audDepthOfSector;

		f32 height = CGameWorldHeightMap::GetMinHeightFromWorldHeightMap(v0.x, v0.y);
		v0.z = height - 20.0f;
		v1.z = height + 20.0f;

		if(g_AudioWorldSectors[sectorIndex].numHighwayNodes > 0)
		{
			colour.SetAlpha((u32)Clamp((s32)(255.0f * (g_AudioWorldSectors[sectorIndex].numHighwayNodes/50.0f)), 0, 255));
			grcDebugDraw::BoxAxisAligned(VECTOR3_TO_VEC3V(v0), VECTOR3_TO_VEC3V(v1), colour, true);
		}
	}
}

void naEnvironment::DrawDebug() const
{
	if(g_audDrawWorldSectorDebug)
	{
		const Vector3 camPos = VEC3V_TO_VECTOR3(g_AudioEngine.GetEnvironment().GetVolumeListenerPosition());
		const u32 sector = static_cast<u32>(ComputeWorldSectorIndex(camPos));

		const u32 sectorX = sector % g_audNumWorldSectorsX;
		const u32 sectorY = (sector - sectorX)/g_audNumWorldSectorsX;

		DebugDrawSector(sectorX-1, sectorY-1);
		DebugDrawSector(sectorX-1, sectorY);
		DebugDrawSector(sectorX-1, sectorY+1);
		DebugDrawSector(sectorX, sectorY-1);
		DebugDrawSector(sectorX, sectorY);
		DebugDrawSector(sectorX, sectorY+1);
		DebugDrawSector(sectorX+1, sectorY);
		DebugDrawSector(sectorX+1, sectorY+1);	
		DebugDrawSector(sectorX+1, sectorY-1);	

	}
	if(g_audResetWorldSectorRoadInfo)
	{
		for(u32 x = 0; x < g_audNumWorldSectorsX; x++)
		{
			for(u32 y = 0; y < g_audNumWorldSectorsY; y++)
			{
				g_AudioWorldSectors[x + (y * g_audNumWorldSectorsX)].numHighwayNodes = 0;
			}
		}

		g_audResetWorldSectorRoadInfo = false;
	}
	if(g_audDrawLocalObjects)
	{
		for(u32 i = 0; i < g_MaxInterestingLocalObjects; i++)
		{
			if(m_InterestingLocalObjects[i].object && GetInterestingLocalObjectMaterial(i))
			{
				const char *matName = audNorthAudioEngine::GetMetadataManager().GetObjectNameFromNameTableOffset(GetInterestingLocalObjectMaterial(i)->NameTableOffset);

				Color32 color = Color32(255,0,0);
				Vector3 pos = VEC3V_TO_VECTOR3(m_InterestingLocalObjects[i].object->GetTransform().GetPosition());
				if(m_InterestingLocalObjects[i].object->GetCurrentPhysicsInst())
				{
					color = Color32(0,255,0);
					pos = VEC3V_TO_VECTOR3(m_InterestingLocalObjects[i].object->GetCurrentPhysicsInst()->GetCenterOfMass());
				}
				CDebugScene::DrawEntityBoundingBox(m_InterestingLocalObjects[i].object,color);
				char buf[128];

				formatf(buf, sizeof(buf), "%s %s %s %s %s", matName, (AUD_GET_TRISTATE_VALUE(GetInterestingLocalObjectMaterial(i)->Flags, FLAG_ID_MODELAUDIOCOLLISIONSETTINGS_ISRESONANT)==AUD_TRISTATE_TRUE?"reso":""), (GetInterestingLocalObjectMaterial(i)->RandomAmbient != g_NullSoundHash?"rand ambient":""), (GetInterestingLocalObjectMaterial(i)->ShockwaveSound != g_NullSoundHash?"shockwave":""),(GetInterestingLocalObjectMaterial(i)->RainLoop != g_NullSoundHash?"rain":""));
				grcDebugDraw::Text(pos + Vector3(0.f,0.f,0.5f), color, buf);
			}
		}
	}
	if(g_audDrawShockwaves)
	{
		for(u32 i = 0 ; i < kMaxRegisteredShockwaves; i++)
		{
			if(m_RegisteredShockwavesAllocation.IsSet(i))
			{
				grcDebugDraw::Sphere(RCC_VEC3V(m_RegisteredShockwaves[i].centre), 1.f, Color32(0,0,255));
				char buf[128];
				formatf(buf, sizeof(buf), "intensity: %f updated %0.2fs ago", m_RegisteredShockwaves[i].intensity, (fwTimer::GetTimeInMilliseconds() - m_RegisteredShockwaves[i].lastUpdateTime) * 0.001f);
				grcDebugDraw::Text(RCC_VEC3V(m_RegisteredShockwaves[i].centre), Color32(0,0,255), buf);
				if(m_RegisteredShockwaves[i].radius > 0.f)
				{
					grcDebugDraw::Sphere(RCC_VEC3V(m_RegisteredShockwaves[i].centre), m_RegisteredShockwaves[i].radius, Color32(255,0,0), false);
				}
			}
		}
	}
}

void naEnvironment::DrawWeaponEchoes()
{
	if(g_DrawWeaponEchoes)
	{
		Vector3 vListenerPos = VEC3V_TO_VECTOR3(g_AudioEngine.GetEnvironment().GetPanningListenerPosition());
		Vector3 vBestEchoPlayPos, vOppositeEchoPlayPos;
		u32 bestEchoPredelay = 0;
		u32 oppositeEchoPredelay = 0;
		f32 bestEchoAtten = 0.0f;
		f32 oppositeEchoAtten = 0.0f;
		s32 oppositeEchoIdx;
		s32 bestEchoIdx = GetBestEchoForSourcePosition(vListenerPos, &oppositeEchoIdx);

		if(bestEchoIdx > -1)
		{
			CalculateEchoDelayAndAttenuation(bestEchoIdx, vListenerPos, &bestEchoPredelay, &bestEchoAtten, &vBestEchoPlayPos);
		}
		if(oppositeEchoIdx > -1)
		{
			CalculateEchoDelayAndAttenuation(oppositeEchoIdx, vListenerPos, &oppositeEchoPredelay, &oppositeEchoAtten, &vOppositeEchoPlayPos);
		}

		char buf[255];

		f32 echoSuitablility[8];
		GetEchoSuitablilityForSourcePosition(vListenerPos, &echoSuitablility[0]);
		for(s32 i = 0; i < 8; i++)
		{
			if(echoSuitablility[i] == 0)
				continue;
			f32 distance = (vListenerPos - m_EchoPosition[i]).Mag();
			if(i == bestEchoIdx)
			{
				grcDebugDraw::Sphere(m_EchoPosition[i], 1.0f, Color_green);
				formatf(buf, sizeof(buf), "Best Echo - Suitability: %f distance: %f predelay: %u volume: %f ", 
					echoSuitablility[i], distance, bestEchoPredelay, bestEchoAtten);
				grcDebugDraw::Text(m_EchoPosition[i] + Vector3(0.0f, 0.0f, 1.0f), Color_green, buf);
				grcDebugDraw::Sphere(vBestEchoPlayPos, 1.0f, Color_green);
				formatf(buf, sizeof(buf), "Best Echo Play Position");
				grcDebugDraw::Text(vBestEchoPlayPos + Vector3(0.0f, 0.0f, 1.0f), Color_green, buf);
				Vector3 vNormalArrow = m_EchoDirection[i];
				vNormalArrow.Scale(2.0f);
				grcDebugDraw::Arrow(VECTOR3_TO_VEC3V(m_EchoPosition[i]), VECTOR3_TO_VEC3V(m_EchoPosition[i] + vNormalArrow), 1.0f, Color_OrangeRed);
			}
			else if(i == oppositeEchoIdx)
			{
				grcDebugDraw::Sphere(m_EchoPosition[i], 1.0f, Color_OliveDrab);
				char buf[255];
				formatf(buf, sizeof(buf), "Opposite Echo - Suitability: %f distance: %f predelay: %u volume: %f", 
					echoSuitablility[i], distance, oppositeEchoPredelay, oppositeEchoAtten);
				grcDebugDraw::Text(m_EchoPosition[i] + Vector3(0.0f, 0.0f, 1.0f), Color_OliveDrab, buf);
				grcDebugDraw::Sphere(vOppositeEchoPlayPos, 1.0f, Color_OliveDrab);
				formatf(buf, sizeof(buf), "Opposite Echo Play Position");
				grcDebugDraw::Text(vOppositeEchoPlayPos + Vector3(0.0f, 0.0f, 1.0f), Color_OliveDrab, buf);
				Vector3 vNormalArrow = m_EchoDirection[i];
				vNormalArrow.Scale(2.0f);
				grcDebugDraw::Arrow(VECTOR3_TO_VEC3V(m_EchoPosition[i]), VECTOR3_TO_VEC3V(m_EchoPosition[i] + vNormalArrow), 1.0f, Color_OrangeRed);
			}
			else
			{
				grcDebugDraw::Sphere(m_EchoPosition[i], 0.5f, Color_grey);
				char buf[255];
				formatf(buf, sizeof(buf), "Suitability: %f distance: %f", echoSuitablility[i], distance);
				grcDebugDraw::Text(m_EchoPosition[i] + Vector3(0.0f, 0.0f, 0.5f), Color_grey, buf);
				Vector3 vNormalArrow = m_EchoDirection[i];
				vNormalArrow.Scale(2.0f);
				grcDebugDraw::Arrow(VECTOR3_TO_VEC3V(m_EchoPosition[i]), VECTOR3_TO_VEC3V(m_EchoPosition[i] + vNormalArrow), 1.0f, Color_OrangeRed);
			}
		}

		f32 normalizedReverbWet = ((f32)((m_SourceEnvironmentMetrics[0] & 12) >> 2))/3.0f;
		f32 normalizedReverbSize = ((f32)((m_SourceEnvironmentMetrics[0] & 3)))/3.0f;

		f32 volume;
		f32 hold;

		if(m_SourceEnvironmentMetrics[0] == 0)
		{
			volume = m_OutdoorInteriorWeaponVolCurve.CalculateValue(0.0f);
			hold = m_OutdoorInteriorWeaponHoldCurve.CalculateValue(0.0f);
		}
		else if(normalizedReverbWet == 0)
		{
			volume = m_OutdoorInteriorWeaponVolCurve.CalculateValue(0.15f);
			hold = m_OutdoorInteriorWeaponHoldCurve.CalculateValue(normalizedReverbSize);
		}
		else
		{
			volume = m_OutdoorInteriorWeaponVolCurve.CalculateValue(normalizedReverbWet);
			hold = m_OutdoorInteriorWeaponHoldCurve.CalculateValue(normalizedReverbSize);
		}

		//Draw tight area stats
		f32 yPos = 0.05f;
		f32 yInc = 0.025f;
		f32 xPos = 0.05f;
		formatf(buf, sizeof(buf), "Volume: %f", volume);
		grcDebugDraw::Text(Vector2(xPos, yPos), Color_blue1, buf);
		yPos += yInc;
		formatf(buf, sizeof(buf), "Hold: %f", hold);
		grcDebugDraw::Text(Vector2(xPos, yPos), Color_blue1, buf);
	}
}

void naEnvironment::DrawAudioWorldSectors()
{
	Vector3 origin,gridControl; 
	origin.SetX((f32)g_audWorldSectorsMinX);
	origin.SetY((f32)g_audWorldSectorsMinY);
	origin.SetZ(70.f);
	gridControl = origin;
	for(u32 i = 0 ; i <= g_audNumWorldSectorsX ; ++i)
	{
		grcDebugDraw::Line(gridControl,gridControl+ Vector3(g_fAudNumWorldSectorsX * g_audWidthOfSector,0.f,0.f), Color_red,1);
		gridControl.SetY(gridControl.GetY() + g_audWidthOfSector);
	}
	gridControl = origin;
	for(u32 i = 0 ; i <= g_audNumWorldSectorsY ; ++i)
	{
		grcDebugDraw::Line(gridControl,gridControl+ Vector3(0.f,g_fAudNumWorldSectorsY * g_audDepthOfSector,0.f), Color_red,1);
		gridControl.SetX(gridControl.GetX() + g_audDepthOfSector);
	}
}
void GenerateAudioSectorsCB()
{
	naEnvironment::DrawAudioWorldSectors(true); 
	naEnvironment::ProcessSectors(true); 
}
void StopCB()
{
	naEnvironment::ProcessSectors(false); 
	naEnvironment::DrawAudioWorldSectors(false); 
}
extern bool g_WarpToInterior;
void naEnvironment::AddWidgets(bkBank &bank)
{
	bank.PushGroup("GTA Environment",false);
	
		const char *specialEffectModes[kNumSpecialEffectModes] = { "Normal", "Underwater", "Stoned", "PauseMenu", "SlowMotion"};
	
		bank.AddCombo("SpecialEffectModeOverride", (s32*)&audNorthAudioEngine::GetGtaEnvironment()->m_SpecialEffectModeOverride, kNumSpecialEffectModes, (const char**)specialEffectModes);
		bank.AddToggle("override", &audNorthAudioEngine::GetGtaEnvironment()->m_OverrideSpecialEffectMode);

		bank.AddToggle("Update built up factor for each zone.", &g_UpdateBuiltUpFactor);
		// GTA-specific, game-thread only things
		bank.PushGroup("Audio world sectors info generator",false);
		bank.AddToggle("Draw audio world sectors ", &sm_DrawAudioWorldSectors);
		bank.AddToggle("Draw AudioWorldSectors info", &g_audDrawWorldSectorDebug);
		bank.AddSlider("sm_NumWaterProbeSets", &sm_NumWaterProbeSets, 0.0f, 997.0f, 1.f);
		bank.AddSlider("sm_CurrentSectorX", &sm_CurrentSectorX, 0, 100, 1);
		bank.AddSlider("sm_CurrentSectorY", &sm_CurrentSectorY, 0, 100, 1);
		bank.AddSlider("g_NumTreesForRain", &g_NumTreesForRain, 0.0f, 997.0f, 1.f);
		bank.AddSlider("g_NumTreesForRainBuiltUp", &g_NumTreesForRainBuiltUp, 0.0f, 997.0f, 1.f);
		bank.AddButton("Generate audio sectors info",datCallback(GenerateAudioSectorsCB));
		bank.AddButton("Stop",datCallback(StopCB));
		bank.PopGroup();
		bank.AddToggle("EnableLocalEnvironmentMetrics", &g_EnableLocalEnvironmentMetrics);

		bank.PushGroup("Interiors");
		
			bank.AddToggle("WarpToInterior", &g_WarpToInterior);
			bank.AddToggle("g_UseDebugRoomSettings", &g_UseDebugRoomSettings);
			bank.AddToggle("g_TryRoomsEveryTime", &g_TryRoomsEveryTime);
			bank.AddSlider("ReverbSmall", &g_DebugRoomSettingsReverbMedium, 0.0f, 1.0f, 0.01f);
			bank.AddSlider("ReverbMedium", &g_DebugRoomSettingsReverbSmall, 0.0f, 1.0f, 0.01f);
			bank.AddSlider("ReverbLarge", &g_DebugRoomSettingsReverbLarge, 0.0f, 1.0f, 0.01f);
			bank.AddToggle("g_UsePlayer", &g_UsePlayer);
			bank.AddText("Interior", &g_InteriorInfoDebug[0], sizeof(g_InteriorInfoDebug));
			bank.AddText("Room", &g_RoomInfoDebug[0], sizeof(g_RoomInfoDebug));
			bank.AddButton("Create RAVE objects for current Interior and Rooms", CFA(CreateRAVEObjectsForCurrentInteriorAndRooms));
			bank.AddButton("Create RAVE object for current Interior Only", CFA(CreateRAVEObjectsForCurrentInterior));
			bank.AddButton("Create RAVE object for current Room Only", CFA(CreateRAVEObjectsForCurrentRoom));
		bank.PopGroup();

		bank.AddToggle("g_DrawWeaponEchos", &g_DrawWeaponEchoes);

		bank.AddSlider("OpenSpaceReverbSize", &g_OpenSpaceReverbSize, 0.0f, 1.0f, 0.01f);
		bank.AddSlider("OpenSpaceReverbWet", &g_OpenSpaceReverbWet, 0.0f, 1.0f, 0.01f);
		bank.AddSlider("SpeakerReverbSmootherRate", &g_SpeakerReverbSmootherRate, 0.0f, 1.0f, 0.0001f);
		bank.AddSlider("SpeakerReverdDamping", &g_SpeakerReverbDamping, 0.0f, 1.0f, 0.01f);
		bank.AddSlider("SourceEnvironmentRadius", &g_SourceEnvironmentRadius, 0.0f, 20.0f, 0.1f);

		bank.AddSlider("TheManhattanFactor", &g_OverrideBuiltUpFactor, 0.0f, 1.0f, 0.01f);
		bank.AddToggle("OverrideManhattanFactor", &g_ShouldOverrideBuiltUpFactor);
		bank.AddSlider("Special Ability Slow-mo timeout", &g_SpecialAbilitySlowMoTimeout, 0, 10000, 10);

		bank.AddSlider("Deafening Up Time", &g_DeafeningRampUpTime, 0, 10000, 1);
		bank.AddSlider("Deafening Hold Time", &g_DeafeningHoldTime, 0, 10000, 1);
		bank.AddSlider("Deafening Down Time", &g_DeafeningRampDownTime, 0, 10000, 1);
		bank.AddSlider("Deafening Ear Leak", &g_DeafeningEarLeak, 0.0f, 1.0f, 0.1f);
		bank.AddSlider("g_DeafeningRolloff", &g_DeafeningRolloff, 0.0f, 10.0f, 0.1f);
		bank.PushGroup("Interesting local objects");
			bank.PushGroup("ShockWaves");
				bank.AddToggle("g_audDrawShockwaves", &g_audDrawShockwaves);
				bank.AddSlider("Heli shockwave radius", &sm_HeliShockwaveRadius, 0.0f, 1000.0f, 0.01f);
				bank.AddSlider("Train distance threshold", &sm_TrainDistanceThreshold, 0.0f, 1000.0f, 0.01f);
				bank.AddSlider("Train shockwave radius", &sm_TrainShockwaveRadius, 0.0f, 1000.0f, 0.01f);
			bank.PopGroup();
			bank.PushGroup("Resonance");
				bank.AddToggle("Force Resonance Tracks Listener", &g_ForceResonanceTracksListener);
				bank.AddToggle("Display ResoIntensity", &sm_DisplayResoIntensity);
				bank.AddSlider("Melee resonance impulse", &sm_MeleeResoImpulse, 0.0f, 1.f, 0.001f);
				bank.AddSlider("Pistol resonance impulse", &sm_PistolResoImpulse, 0.0f, 1.f, 0.001f);
				bank.AddSlider("Rifle resonance impulse", &sm_RifleResoImpulse, 0.0f, 1.f, 0.001f);
				bank.AddSlider("Weapons resonance distance", &sm_WeaponsResonanceDistance, 0.0f, 1000.0f, 0.01f);
				bank.AddSlider("Explosions resonance impulse", &sm_ExplosionsResonanceImpulse, 0.0f, 1.f, 0.001f);
				bank.AddSlider("Explosions resonance distance", &sm_ExplosionsResonanceDistance, 0.0f, 1000.f, 0.001f);
				bank.AddSlider("Vehicle collision resonance impulse", &sm_VehicleCollisionResonanceImpulse, 0.0f, 1.f, 0.001f);
				bank.AddSlider("Vehicle collision distance", &sm_VehicleCollisionResonanceDistance, 0.0f, 1000.f, 0.001f);
				bank.AddSlider("Plateau effect", &sm_DistancePlateau, 0.0f, 1000.0f, 0.01f);
				bank.PushGroup("Slow motion", false);
				bank.AddSlider("Frequency", &sm_SlowMoLFOFrequencyMin, 0.0f, 1000.0f, 0.01f);
				bank.AddSlider("Frequency", &sm_SlowMoLFOFrequencyMax, 0.0f, 1000.0f, 0.01f);
				bank.PopGroup();
			bank.PopGroup();
			bank.PushGroup("Rain");
				bank.AddToggle("Force Rain Tracks Listener", &g_ForceRainLoopTracksListener);
				bank.AddToggle("Force Rain Plays From Top Of Bounds", &g_ForceRainLoopPlayFromTopOfBounds);
				bank.AddSlider("sm_SqdDistanceToPlayRainSounds", &sm_SqdDistanceToPlayRainSounds, 0.0f, 10000.0f, 1.f);
				bank.AddSlider("sm_RainOnPropsVolumeOffset", &sm_RainOnPropsVolumeOffset, -100.0f, 24.f, 1.f);
				bank.AddSlider("sm_RainOnPopsPreDealy", &sm_RainOnPopsPreDealy, 0, 10000, 100);
				bank.AddSlider("sm_MaxNumRainProps", &sm_MaxNumRainProps, 0, 64, 1);
			bank.PopGroup();
			bank.AddToggle("g_audDrawLocalObjects",&g_audDrawLocalObjects);
			bank.AddToggle("Use aproximation to compute which objects are covered", &sm_UseCoverAproximation);
			bank.AddToggle("Debug covered objects", &sm_DebugCoveredObjects);
			bank.AddSlider("sm_RadiusScale", &sm_RadiusScale, 0.0f, 10.0f, 0.0001f);
			bank.AddSlider("sm_ObjOcclusionUpdateTime", &sm_ObjOcclusionUpdateTime, 0, 10000, 100);
		bank.PopGroup();
		bank.PushGroup("Wind");
			bank.PushGroup("Through trees");
				bank.PushGroup("Background",false);
					bank.AddToggle("sm_DrawBgTreesInfo", &sm_DrawBgTreesInfo);
					bank.AddSlider("sm_NumTreesForAvgDist", &sm_NumTreesForAvgDist, 0, 50, 1);
					bank.AddSlider("g_FitnessReduced", &g_FitnessReduced, 0.0f, 1.0f, 0.01f);
					bank.AddSlider("g_VolSmootherRate", &g_VolSmootherRate, 0.0f, 2.0f, 0.01f);
					bank.AddSlider("Bushes min radius", &sm_BushesMinRadius, 0.0f, 1000.0f, 0.01f);
					bank.AddSlider("Trees min radius", &sm_SmallTreesMinRadius, 0.0f, 1000.0f, 0.01f);
					bank.AddSlider("Big trees min radius", &sm_BigTreesMinRadius, 0.0f, 1000.0f, 0.01f);
					bank.AddSlider("sm_BigTreesFitness", &sm_BigTreesFitness, 0.0f, 1.0f, 0.01f);
					bank.AddSlider("sm_TreesFitness", &sm_TreesFitness, 0.0f, 1.0f, 0.01f);
					bank.AddSlider("sm_BushesFitness", &sm_BushesFitness, 0.0f, 1.0f, 0.01f);
					bank.AddSlider("Sounds distance from the listener", &sm_TreesSoundDist, 0.0f, 1000.0f, 0.01f);
					//bank.AddSlider("sm_GustVolSpikeForBgTrees", &sm_GustVolSpikeForBgTrees, 0.0f, 24.0f, 0.5f);
					bank.AddToggle("sm_MuteBgFL", &sm_MuteBgFL);
					bank.AddToggle("sm_MuteBgFR", &sm_MuteBgFR);
					bank.AddToggle("sm_MuteBgRL", &sm_MuteBgRL);
					bank.AddToggle("sm_MuteBgRR", &sm_MuteBgRR);
					bank.PushGroup("GustEnd sound",false);
					bank.AddSlider("sm_MinNumberOfTreesToTriggerGustEnd", &sm_MinNumberOfTreesToTriggerGustEnd, 0, 1000, 1);
					bank.PopGroup();
				bank.PopGroup();
			bank.PopGroup();
			bank.PushGroup("On props");
			bank.AddToggle("Show Info", &sm_DebugGustWhistle);
			bank.AddSlider("sm_MinTimeToRetrigger", &sm_MinTimeToRetrigger, 0,50000,100);
			bank.PopGroup();
		bank.PopGroup();
		bank.AddToggle("Reset AudioWorldSector Road Info", &g_audResetWorldSectorRoadInfo);
		bank.AddToggle("Debug Listener Reverb", &g_DebugListenerReverb);

		bank.AddSlider("g_BuiltUpSmootherRate", &g_BuiltUpSmootherRate, 0.0f, 1.0f, 0.0001f);
		bank.AddToggle("Debug BuiltUp Info", &g_DebugBuiltUpInfo);
		bank.AddToggle("g_DisplayBuiltUpFactors", &g_DisplayBuiltUpFactors);
		bank.AddToggle("g_UseProperBuiltUpFactor", &g_UseProperBuiltUpFactor);
		bank.AddSlider("g_NSEW_Offset", &g_NSEW_Offset, 0.0f, 2.49f, 0.01f);
		bank.AddSlider("g_BuiltUpDampingFactorSmoothUpRate", &g_BuiltUpDampingFactorSmoothUpRate, 0.0f, 10000.0f, 1.0f);
		bank.AddSlider("g_BuiltUpDampingFactorSmoothDownRate", &g_BuiltUpDampingFactorSmoothDownRate, 0.0f, 10000.0f, 1.0f);
		bank.AddSlider("g_BuiltUpDampingFactorOverride", &g_BuiltUpDampingFactorOverride, 0.0f, 1.0f, 0.1f);

		bank.AddSlider("g_InCarRatioSmoothRate", &g_InCarRatioSmoothRate, 0.0f, 100.0f, 0.1f);
		bank.AddSlider("g_InteriorRatioSmoothRate", &g_InteriorRatioSmoothRate, 0.0f, 100.0f, 0.1f);
		
	bank.PopGroup();
}
#endif
