//=============================================================================
// FILE: spatialaudio_utility.cpp
//
//                  Copyright (c) 2017 QUALCOMM Technologies Inc.
//                              All Rights Reserved.
//
//=============================================================================

#include "spatialaudio_utility.h"

#include "glm/gtx/transform.hpp"
#include "svrGeometry.h"
#include "svrShader.h"
#include "svrUtil.h"

using namespace glm;
using namespace Svr;

q3d_audio_result Q3DAudioCreate(q3d_audio_client** client, JavaVM*const vm, jobject javaActivityObject)
{
    assert(client);
	assert(vm);

	JNIEnv* jniEnv;
	vm->AttachCurrentThread(&jniEnv, NULL);

	jclass activityClass = jniEnv->FindClass("android/app/NativeActivity");
	jmethodID getClassLoaderMethodId = jniEnv->GetMethodID(activityClass, "getClassLoader", "()Ljava/lang/ClassLoader;");
	jobject classLoaderObj = jniEnv->CallObjectMethod(javaActivityObject, getClassLoaderMethodId);

	jmethodID getApplicationContextMethodId = jniEnv->GetMethodID(activityClass, "getApplicationContext", "()Landroid/content/Context;");
	jobject androidContext = jniEnv->CallObjectMethod(javaActivityObject, getApplicationContextMethodId);

    const q3d_audio_result audioCreateResult = 
        q3dclient_audio_create(client, jniEnv, androidContext, classLoaderObj, Q3D_AUDIO_RENDERING_DIRECT);
    LOGI("q3d_audio_create(Q3D_AUDIO_RENDERING_DIRECT)=%i and returned pointer %x", audioCreateResult, (unsigned int)(*client));
    Q3D_AUDIO_ASSERT_SUCCESS(audioCreateResult);
	return audioCreateResult;
}
q3d_audio_result Q3DAudioDestroy(q3d_audio_client**const pSpatialAudio)
{
	assert(pSpatialAudio && *pSpatialAudio);

	const q3d_audio_client*const pSpatialAudioOriginal = *pSpatialAudio;
	const q3d_audio_result audioDestroyResult = q3dclient_audio_destroy(pSpatialAudio);
	
    LOGI("q3d_audio_destroy(pSpatialAudio=%x; *pSpatialAudio=%x)=%i", (unsigned int)pSpatialAudio, (unsigned int)pSpatialAudioOriginal, audioDestroyResult);
    Q3D_AUDIO_ASSERT_SUCCESS(audioDestroyResult);
    assert(*pSpatialAudio == 0);
	
    return audioDestroyResult;
}


q3d_audio_result Q3DAudioUpdate(q3d_audio_client*const pSpatialAudio, const bool log)
{
	assert(pSpatialAudio);

	const q3d_audio_result audioUpdateResult = q3dclient_audio_update(pSpatialAudio);
	if (log)
	{
		LOGI("q3d_audio_update()=%i", audioUpdateResult);
	}
	Q3D_AUDIO_ASSERT_SUCCESS(audioUpdateResult);
	return audioUpdateResult;
}
q3d_audio_result Q3DAudioSetHeadPosition(
	q3d_audio_client*const pSpatialAudio, 
	const vec3& headPositionWorldSpace, 
	const bool log)
{
	assert(pSpatialAudio);

	const q3d_audio_result setHeadPositionResult = q3dclient_audio_set_head_position(
		pSpatialAudio,
		headPositionWorldSpace.x,
		headPositionWorldSpace.y,
		headPositionWorldSpace.z);
	if (log)
	{
		LOGI("q3d_audio_set_head_position([%f,%f,%f])=%i", headPositionWorldSpace.x, headPositionWorldSpace.y, headPositionWorldSpace.z, setHeadPositionResult);
	}
	Q3D_AUDIO_ASSERT_SUCCESS(setHeadPositionResult);
	return setHeadPositionResult;
}
q3d_audio_result Q3DAudioSetHeadRotation(
	q3d_audio_client*const pSpatialAudio,
	const quat &playerHeadOrientationWorldSpace,
	const bool log)
{
	assert(pSpatialAudio);

	const q3d_audio_result setHeadRotationResult = q3dclient_audio_set_head_rotation(
		pSpatialAudio,
		playerHeadOrientationWorldSpace.x,
		playerHeadOrientationWorldSpace.y,
		playerHeadOrientationWorldSpace.z,
		playerHeadOrientationWorldSpace.w);
	if (log)
	{
		LOGI("q3d_audio_set_head_rotation(%f,%f,%f,%f)", playerHeadOrientationWorldSpace.x, playerHeadOrientationWorldSpace.y, playerHeadOrientationWorldSpace.z, playerHeadOrientationWorldSpace.w);
	}
	Q3D_AUDIO_ASSERT_SUCCESS(setHeadRotationResult);
	return setHeadRotationResult;
}

bool Q3DAudioPreloadSoundFile(q3d_audio_client*const pSpatialAudio, const char*const pSoundFileName)
{
	assert(pSpatialAudio && pSoundFileName && pSoundFileName[0]);
	const bool preload = q3dclient_audio_preload_soundfile(pSpatialAudio, pSoundFileName);

	LOGI("q3d_audio_preload_soundfile(%s)=%i", pSoundFileName, preload);
	assert(preload);
	return preload;
}
q3d_audio_result Q3DAudioUnloadSoundFile(q3d_audio_client*const pSpatialAudio, const char*const pFilename)
{
	assert(pSpatialAudio && pFilename && pFilename[0]);

	const q3d_audio_result result = q3dclient_audio_unload_soundfile(pSpatialAudio, pFilename);
	LOGI("q3d_audio_unload_soundfile(%s)", pFilename);
	Q3D_AUDIO_ASSERT_SUCCESS(result);
	return result;
}

q3d_audio_sound_id Q3DAudioObjectCreate(q3d_audio_client* const pSpatialAudio, const char*const pFilename)
{
	assert(pSpatialAudio && pFilename);

	const q3d_audio_sound_id soundId = q3dclient_audio_create_sound_object(pSpatialAudio, pFilename);
	LOGI("q3d_audio_create_sound_object(%s)=%i", pFilename, soundId);
	Q3D_AUDIO_ASSERT_HANDLE_VALID(soundId);

	return soundId;
}
q3d_audio_sound_id Q3DAudioSoundfieldCreate(q3d_audio_client*const pSpatialAudio, const char*const pFilename)
{
	assert(pSpatialAudio);
	assert(pFilename && pFilename);

	const q3d_audio_sound_id ret = q3dclient_audio_create_soundfield(pSpatialAudio, pFilename);
	LOGI("q3d_audio_create_soundfield(%s)=%i", pFilename, ret);
	Q3D_AUDIO_ASSERT_HANDLE_VALID(ret);

	return ret;
}
q3d_audio_sound_id Q3DAudioSoundChannelCreate(q3d_audio_client*const pSpatialAudio, const char*const pFilename)
{
	assert(pSpatialAudio);
	assert(pFilename && pFilename[0]);

	const q3d_audio_sound_id soundChannelId = q3dclient_audio_create_sound_channel(pSpatialAudio, pFilename);
	LOGI("q3d_audio_create_sound_channel(%s)=%i", pFilename, soundChannelId);
	Q3D_AUDIO_ASSERT_HANDLE_VALID(soundChannelId);
	return soundChannelId;
}

q3d_audio_result Q3DAudioSoundPlay(
	q3d_audio_client*const pSpatialAudio,
	const q3d_audio_sound_id soundId,
	const bool looping)
{
	assert(pSpatialAudio);
	Q3D_AUDIO_ASSERT_HANDLE_VALID(soundId);

	const q3d_audio_result playSoundResult = q3dclient_audio_play_sound(pSpatialAudio, soundId, looping);
	LOGI("q3d_audio_play_sound(%i, looping=%i)=%i", soundId, looping, playSoundResult);
	Q3D_AUDIO_ASSERT_SUCCESS(playSoundResult);

	return playSoundResult;
}
q3d_audio_result Q3DAudioSoundStopAndInvalidateHandle(q3d_audio_client*const pSpatialAudio, const q3d_audio_sound_id soundId)
{
	assert(pSpatialAudio);
	Q3D_AUDIO_HANDLE_VALID(soundId);

	const q3d_audio_result stopSoundResult = q3dclient_audio_stop_sound(pSpatialAudio, soundId);
	LOGI("q3d_audio_stop_sound(%i)=%i", soundId, stopSoundResult);
	//Q3D_AUDIO_ASSERT_SUCCESS(stopSoundResult);//can't guarantee this will ever succeed with non-looping sounds, since non-looping sounds invalidate their own handles upon playing to completion in a separate thread

	return stopSoundResult;
}

q3d_audio_result Q3DAudioSetObjectPosition(
	q3d_audio_client*const pSpatialAudio,
	const q3d_audio_sound_id soundId,
	const vec3& position,
	const bool log)
{
	assert(pSpatialAudio);
	Q3D_AUDIO_ASSERT_HANDLE_VALID(soundId);

	const q3d_audio_result result =
		q3dclient_audio_set_sound_object_position(pSpatialAudio, soundId, position.x, position.y, position.z);
	if (log)
	{
		LOGI("q3d_audio_set_sound_object_position(%i, [%f,%f,%f])=%i", soundId, position.x, position.y, position.z, result);
	}
	//Q3D_AUDIO_ASSERT_SUCCESS(result);//can't guarantee this will ever succeed with non-looping sounds, since non-looping sounds invalidate their own handles upon playing to completion in a separate thread

	return result;
}
q3d_audio_result Q3DAudioGetObjectPosition(
	q3d_audio_client* const pSpatialAudio,
	q3d_audio_vec3f* pPosition,
	const q3d_audio_sound_id soundId,
	const bool log)
{
	assert(pSpatialAudio && pPosition);
	Q3D_AUDIO_HANDLE_VALID(soundId);

	const q3d_audio_result getPositionResult = q3dclient_audio_get_sound_object_position_in_vec3f(pSpatialAudio, soundId, *pPosition);
	if (log)
	{
		LOGI("q3d_audio_get_sound_object_position_in_vec3f(%i)=%i reported [%f,%f,%f]", soundId, getPositionResult, pPosition->x, pPosition->y, pPosition->z);
	}
	//Q3D_AUDIO_ASSERT_SUCCESS(getPositionResult);//can't guarantee this will ever succeed with non-looping sounds, since non-looping sounds invalidate their own handles upon playing to completion in a separate thread

	return getPositionResult;
}

void Q3DAudioSetSoundVolume(
	q3d_audio_client*const pSpatialAudio, 
	const q3d_audio_sound_id soundId, 
	const float volume, 
	const bool log)
{
	assert(pSpatialAudio);
	Q3D_AUDIO_HANDLE_VALID(soundId);
	assert(volume >= 0.f && volume <= 1.f);

	const q3d_audio_result result = q3dclient_audio_set_sound_volume(pSpatialAudio, soundId, volume);
	if (log)
	{
		LOGI("q3d_audio_set_sound_volume(%i,%f)=%i", soundId, volume, result);
	}
	//Q3D_AUDIO_ASSERT_SUCCESS(result);//can't guarantee this will ever succeed with non-looping sounds, since non-looping sounds invalidate their own handles upon playing to completion in a separate thread
}

q3d_audio_result Q3DAudioGetSoundVolume(
	q3d_audio_client*const pSpatialAudio,
	float& volume,
	const q3d_audio_sound_id soundId,
	const bool log)
{
	assert(pSpatialAudio);
	Q3D_AUDIO_HANDLE_VALID(soundId);

	const q3d_audio_result result = q3dclient_audio_get_sound_volume(pSpatialAudio, soundId, volume);
	if (log)
	{
		LOGI("q3d_audio_get_sound_volume(%i)=%i and reports %f", soundId, result, volume);
	}

	//Q3D_AUDIO_ASSERT_SUCCESS(result);//can't guarantee this will ever succeed with non-looping sounds, since non-looping sounds invalidate their own handles upon playing to completion in a separate thread
	return result;
}

q3d_audio_result Q3DAudioObjectSetRolloff(
	q3d_audio_client* const pSpatialAudio,
	const q3d_audio_sound_id soundId,
	const q3d_audio_distance_rolloff_type rolloffType,
	const float minDistance,
	const float maxDistance)
{
	assert(pSpatialAudio);
	assert(minDistance < maxDistance);
	assert(rolloffType >= Q3D_AUDIO_ROLLOFF_LOGARITHMIC && rolloffType <= Q3D_AUDIO_ROLLOFF_NONE);

	const q3d_audio_result distanceRolloffResult =
		q3dclient_audio_set_sound_object_distance_rolloff_model(pSpatialAudio, soundId, rolloffType, minDistance, maxDistance);
	LOGI("q3d_audio_set_sound_object_distance_rolloff_model(%i, %i, %f, %f)=%i", soundId, rolloffType, minDistance, maxDistance, distanceRolloffResult);
	//Q3D_AUDIO_ASSERT_SUCCESS(distanceRolloffResult);//can't guarantee this will ever succeed with non-looping sounds, since non-looping sounds invalidate their own handles upon playing to completion in a separate thread

	return distanceRolloffResult;
}

q3d_audio_result Q3DAudioSetSoundfieldRotation(
	q3d_audio_client*const pSpatialAudio,
	const q3d_audio_sound_id soundFieldId,
	const q3d_audio_quatf& orientation,
	const bool log)
{
	assert(pSpatialAudio);
	Q3D_AUDIO_ASSERT_HANDLE_VALID(soundFieldId);

	const q3d_audio_result result = q3dclient_audio_set_soundfield_rotation(pSpatialAudio, soundFieldId, orientation);
	if (log)
	{
		LOGI("q3d_audio_set_soundfield_rotation(%f,%f,%f,%f)=%i",
			orientation.qx,
			orientation.qy,
			orientation.qz,
			orientation.qw,
			result);
	}
	//Q3D_AUDIO_ASSERT_SUCCESS(result);//can't guarantee this will ever succeed with non-looping sounds, since non-looping sounds invalidate their own handles upon playing to completion in a separate thread

	return result;
}

q3d_audio_result Q3DAudioGetSoundfieldRotation(
	q3d_audio_client*const pSpatialAudio,
	q3d_audio_quatf*const pOrientation,
	const q3d_audio_sound_id soundFieldId,
	const bool log)
{
	assert(pSpatialAudio && pOrientation);

	const q3d_audio_result getSoundFieldRotationResult =
		q3dclient_audio_get_soundfield_rotation(
			pSpatialAudio,
			soundFieldId,
			*pOrientation);
	if (log)
	{
		LOGI("q3d_audio_get_soundfield_rotation()=[%f,%f,%f,%f]=%i", pOrientation->qx, pOrientation->qy, pOrientation->qz, pOrientation->qw, getSoundFieldRotationResult);
	}
	//Q3D_AUDIO_ASSERT_SUCCESS(getSoundFieldRotationResult);//can't guarantee this will ever succeed with non-looping sounds, since non-looping sounds invalidate their own handles upon playing to completion in a separate thread
	return getSoundFieldRotationResult;
}

bool Q3DAudioSetReverb(
	q3d_audio_client* mpSpatialAudio,
	const bool enabled,
	const float gain,
	const float timeAdjust,
	const float brightnessAdjust,
	const vec3& roomDimensions,
	const q3d_audio_material_type wallMaterial,
	const q3d_audio_material_type ceilingMaterial,
	const q3d_audio_material_type floorMaterial)
{
	const q3d_audio_result enableRoomResult = q3dclient_audio_enable_room(mpSpatialAudio, enabled);
	LOGI("q3d_audio_enable_room()=%i", enableRoomResult);
	//Q3D_AUDIO_ASSERT_SUCCESS(enableRoomResult);///@todo: not yet returning correct values

	const q3d_audio_result setRoomReverbAdjustmentsResult =
		q3dclient_audio_set_room_reverb_adjustments(mpSpatialAudio, gain, timeAdjust, brightnessAdjust);
	LOGI("q3d_audio_set_room_reverb_adjustments(%f,%f,%f)=%i", gain, timeAdjust, brightnessAdjust, setRoomReverbAdjustmentsResult);
	//Q3D_AUDIO_ASSERT_SUCCESS(setRoomReverbAdjustmentsResult);///@todo: not yet returning correct values

	Q3D_AUDIO_ASSERT_MATERIAL_VALID(wallMaterial);
	Q3D_AUDIO_ASSERT_MATERIAL_VALID(ceilingMaterial);
	Q3D_AUDIO_ASSERT_MATERIAL_VALID(floorMaterial);

	const q3d_audio_result setRoomPropertiesResult = q3dclient_audio_set_room_properties(
		mpSpatialAudio,
		roomDimensions.x,
		roomDimensions.y,
		roomDimensions.z,
		wallMaterial,
		ceilingMaterial,
		floorMaterial);
	LOGI("q3d_audio_set_room_properties(dimensions=[%f,%f,%f], wall=%i,ceiling=%i,floor=%i)=%i",
		roomDimensions.x,
		roomDimensions.y,
		roomDimensions.z,
		wallMaterial,
		ceilingMaterial,
		floorMaterial,
		setRoomPropertiesResult);
	//Q3D_AUDIO_ASSERT_SUCCESS(setRoomPropertiesResult);///@todo: not yet returning correct values


	return true;///@todo: for now, until reverb return values are fixed
				//return	Q3D_AUDIO_SUCCESS(enableRoomResult) && 
				//		Q3D_AUDIO_SUCCESS(setRoomReverbAdjustmentsResult) && 
				//		Q3D_AUDIO_SUCCESS(setRoomPropertiesResult);
}


void * GetFileBuffer(int*const pBufferSize, const char*const pAppExternalPath, const char*const pFileName)
{
	assert(pBufferSize && pAppExternalPath && pFileName);

	const int kFilePathMaxLen = 512;
	char filePath[kFilePathMaxLen];

	snprintf(filePath, kFilePathMaxLen, "%s/%s", pAppExternalPath, pFileName);

	LOGI("Opening File: %s", filePath);
	FILE* fp = fopen(filePath, "rb");
	if (!fp)
	{
		LOGE("Unable to open file: %s", filePath);
		return NULL;
	}

	// Need the file size
	fseek(fp, 0, SEEK_END);
	*pBufferSize = ftell(fp);
	fseek(fp, 0, SEEK_SET);

	// Now read the file into the new buffer
	char* pRetBuff = new char[*pBufferSize + 1];
	fread(pRetBuff, 1, *pBufferSize, fp);
	pRetBuff[*pBufferSize] = 0;

	fclose(fp);

	return pRetBuff;
}

static void InitializeShader(
	Svr::SvrShader &whichShader,
	const char*const pVertexPath,
	const char*const pFragmentPath,
	const char*const pVertexName,
	const char*const pFragmentName,
	const char*const pAppExternalPath)
{
	assert(pVertexPath && pFragmentPath && pVertexName && pFragmentName && pAppExternalPath);

	int fileBuffSize;

	char *pVsBuffer = (char *)GetFileBuffer(&fileBuffSize, pAppExternalPath, pVertexPath);
	if (pVsBuffer == NULL)
	{
		return;
	}

	char *pFsBuffer = (char *)GetFileBuffer(&fileBuffSize, pAppExternalPath, pFragmentPath);
	if (pFsBuffer == NULL)
	{
		free(pVsBuffer);
		return;
	}

	int numVertStrings = 0;
	int numFragStrings = 0;
	const char *vertShaderStrings[16];
	const char *fragShaderStrings[16];

	vertShaderStrings[numVertStrings++] = pVsBuffer;
	fragShaderStrings[numFragStrings++] = pFsBuffer;
	whichShader.Initialize(numVertStrings, vertShaderStrings, numFragStrings, fragShaderStrings, pVertexName, pFragmentName);

	// No longer need buffers
	free(pVsBuffer);
	free(pFsBuffer);
}

void InitializeModel(
	SvrGeometry**const pModelGeometry,
	SvrShader*const pShader,
	const char*const pAppExternalPath,
	const char*const pObjFileLocalPath,
	const char*const pVertexShaderFileRelativePath,
	const char*const pFragmentShaderFileRelativePath,
	const char*const pVertexShaderDebugName,
	const char*const pFragmentShaderDebugName)
{
	assert(pModelGeometry && pShader && pAppExternalPath && pObjFileLocalPath && pVertexShaderFileRelativePath &&
		pVertexShaderDebugName && pFragmentShaderFileRelativePath && pFragmentShaderDebugName);

	const int kFilePathMaxLen = 512;
	char filePath[kFilePathMaxLen];

	SvrGeometry*    pObjGeom;
	int             nObjGeom;
	snprintf(filePath, kFilePathMaxLen, "%s/%s", pAppExternalPath, pObjFileLocalPath);
	LOGI("Opening File: %s", filePath);
	SvrGeometry::CreateFromObjFile(filePath, &pObjGeom, nObjGeom);
	(*pModelGeometry) = &pObjGeom[0];

	InitializeShader(
		*pShader,
		pVertexShaderFileRelativePath,
		pFragmentShaderFileRelativePath,
		pVertexShaderDebugName,
		pFragmentShaderDebugName,
		pAppExternalPath);
}

void DestroyGeometry(SvrGeometry**const model)
{
	assert(model);
	(*model)->Destroy();
	*model = 0;
}

void PositionsOnSphereCalculate(vec3*const positions, const float radius)
{
	assert(positions);

	//generate sound object positions across the surface of a sphere centered at the world origin
	const vec4 forwardOffset(0.f, 0.f, -radius, 1.f);
	const vec3 worldUp(0.f, 1.f, 0.f);
	const vec3 worldRight(1.f, 0.f, 0.f);
	int soundObjectIndex = 0;
	{
		const int azimuthNum = 5;
		const float azimuthIncrement = radians(360.f) / azimuthNum;
		for (int i = 0; i < azimuthNum; ++i)
		{
			positions[soundObjectIndex++] = vec3(rotate(azimuthIncrement*i, worldUp)*forwardOffset);
		}
	}
	{
		const int azimuthNum = 5;
		const float azimuthIncrement = radians(360.f) / azimuthNum;
		const mat4 elevationRotation = rotate(radians(45.f), worldRight);
		for (int i = 0; i < azimuthNum; ++i)
		{
			positions[soundObjectIndex++] = vec3((rotate(azimuthIncrement*i, worldUp)*elevationRotation)*forwardOffset);
		}
	}
	{
		const int azimuthNum = 4;
		const float azimuthIncrement = radians(360.f) / azimuthNum;
		const mat4 elevationRotation = rotate(radians(-45.f), worldRight);
		for (int i = 0; i < azimuthNum; ++i)
		{
			positions[soundObjectIndex++] = vec3((rotate(azimuthIncrement*i, worldUp)*elevationRotation)*forwardOffset);
		}
	}
	//top and bottom of sphere
	positions[soundObjectIndex++] = vec3(0.f, radius, 0.f);
	positions[soundObjectIndex++] = vec3(0.f, -radius, 0.f);
}

static vec3 ColorCode(const float val, const vec3 &colorPositive, const vec3 &colorNegative)
{
	const glm::vec3 &color = val > 0.f ? colorPositive : colorNegative;
	return color * (float)fabs(val);
}
vec3 ColorCode(const vec3& positionUnitLength)
{
	const glm::vec3 xPositive(1.0f, 0.0f, 0.0f);
	const glm::vec3 yPositive(0.0f, 1.0f, 0.0f);
	const glm::vec3 zPositive(0.0f, 0.0f, 1.0f);
	const glm::vec3 xNegative(0.0f, 1.0f, 1.0f);
	const glm::vec3 yNegative(1.0f, 0.0f, 1.0f);
	const glm::vec3 zNegative(1.0f, 1.0f, 0.0f);
    
    const float pi = 3.141592653589793f;
    const float piDiv2 = pi / 2.f;

    const float xzTheta = atan2f(positionUnitLength.x, positionUnitLength.z);
    const float elevationRadians = asinf(positionUnitLength.y);
    vec3 color;
    if (xzTheta <= pi && xzTheta > piDiv2)
    {
        color = mix(zNegative, xPositive, (pi - xzTheta) / piDiv2);
    }
    else if (xzTheta <= piDiv2 && xzTheta > 0.0)
    {
        color = mix(xPositive, zPositive, (piDiv2 - xzTheta) / piDiv2);
    }
    else if (xzTheta <= 0.0 && xzTheta > -piDiv2)
    {
        color = mix(zPositive, xNegative, (-xzTheta) / piDiv2);
    }
    else//(xzTheta <= -piDiv2 && xzTheta >= -pi)
    {
        color = mix(xNegative, zNegative, (-xzTheta - piDiv2) / piDiv2);
    }

    //blend in the "vertical color"
    if (positionUnitLength.y > 0.0)
    {
        color = mix(color, yPositive, elevationRadians / piDiv2);
    }
    else//(positionUnitLength.y <= 0.0)
    {
        color = mix(color, yNegative, -elevationRadians / piDiv2);
    }

	return color;
}

typedef tquat<float> tquatf;
q3d_audio_quatf GlmMat4ToSpatialAudioQuat(const mat4& m)
{
	q3d_audio_quatf ret;
	tquatf q = tquatf(m);

	ret.qx = q.x;
	ret.qy = q.y;
	ret.qz = q.z;
	ret.qw = q.w;

	return ret;
}
