//------------------------------------------------------------------------------
// Lamp : Open source game middleware
// Copyright (C) 2004  Junpei Ohtani ( Email : junpee@users.sourceforge.jp )
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//------------------------------------------------------------------------------

/** @file
 * 3Dサウンド実装
 * @author Junpee
 */

#include "LampBasic.h"
#include "Sound/3D/Sound3D.h"
#include "Sound/System/LampSound.h"
#include "Sound/3D/SoundListener.h"

namespace Lamp{

//------------------------------------------------------------------------------
// コンストラクタ
Sound3D::Sound3D(DirectSoundBuffer* soundBuffer) :
	SoundBuffer(soundBuffer), sound3DBuffer_(NULL){
	if(DirectXFailed(soundBuffer->QueryInterface(
		DirectSound3DBufferInterfaceID, (void**)&sound3DBuffer_))){
		ErrorOut("Sound3D::Sound3D() 3Dバッファの取得に失敗しました。");
	}
	// 初期データの取得
	DS3DBUFFER param;
	param.dwSize = sizeof(DS3DBUFFER);
	if(DirectXFailed(sound3DBuffer_->GetAllParameters(&param))){
		ErrorOut("Sound3D::Sound3D() 3Dパラメータの取得に失敗しました。");
	}
	position_.set(param.vPosition.x, param.vPosition.x, param.vPosition.z);
	velocity_.set(param.vVelocity.x, param.vVelocity.x, param.vVelocity.z);
	coneDirection_.set(param.vConeOrientation.x,
		param.vConeOrientation.x, param.vConeOrientation.z);
	minimumDistance_ = param.flMinDistance;
	maximumDistance_ = param.flMaxDistance;
	insideConeAngle_ = Math::toRadian((float)param.dwInsideConeAngle);
	outsideConeAngle_ = Math::toRadian((float)param.dwOutsideConeAngle);
	coneOutsideVolume_ = decibelToVolume(param.lConeOutsideVolume);
	Assert(param.dwMode == DS3DMODE_NORMAL);
	if(param.dwMode == DS3DMODE_NORMAL){
		is3DEnabled_ = true;
	}else if(param.dwMode == DS3DMODE_DISABLE){
		is3DEnabled_ = false;
	}else{ Assert(false); }
}
//------------------------------------------------------------------------------
// デストラクタ
Sound3D::~Sound3D(){
	SafeRelease(sound3DBuffer_);
}
//------------------------------------------------------------------------------
// 3Dサウンドデータのコピー
void Sound3D::copySound3DData(Sound3D* destination){
	copySoundBufferData(destination);
}
//------------------------------------------------------------------------------
// 3Dパラメータ
//------------------------------------------------------------------------------
// 位置の設定
void Sound3D::setPosition(const Vector3& position){
	if(position_ == position){ return; }
	if(DirectXFailed(sound3DBuffer_->SetPosition(
		-position.x, position.y, position.z, DS3D_DEFERRED))){
		ErrorOut("Sound3D::setPosition() 位置の設定に失敗しました。");
	}
	position_ = position;
}
//------------------------------------------------------------------------------
// 速度の設定
void Sound3D::setVelocity(const Vector3& velocity){
	if(velocity_ == velocity){ return; }
	if(DirectXFailed(sound3DBuffer_->SetVelocity(
		-velocity.x, velocity.y, velocity.z, DS3D_DEFERRED))){
		ErrorOut("Sound3D::setVelocity() 速度の設定に失敗しました。");
	}
	velocity_ = velocity;
}
//------------------------------------------------------------------------------
// 位置と速度の設定
void Sound3D::setPositionAndVelocity(
	const Vector3& position, float millisecond){
	Assert(millisecond >= 0);
	if(millisecond < Math::epsilon){
		setVelocity(Vector3::zero);
	}else{
		Vector3 velocity = position - position_;
		velocity *= (1000.f / millisecond);
		setVelocity(velocity);
	}
	setPosition(position);
}
//------------------------------------------------------------------------------
// 最小距離の設定
void Sound3D::setMinimumDistance(float minimumDistance){
	Assert(minimumDistance > 0.f);
	if(minimumDistance_ == minimumDistance){ return; }
	if(DirectXFailed(sound3DBuffer_->SetMinDistance(
		minimumDistance, DS3D_DEFERRED))){
		ErrorOut("Sound3D::setMinimumDistance() 最小距離の設定に失敗しました。");
	}
	minimumDistance_ = minimumDistance;
}
//------------------------------------------------------------------------------
// 最大距離の設定
void Sound3D::setMaximumDistance(float maximumDistance){
	Assert(maximumDistance > 0.f);
	if(maximumDistance_ == maximumDistance){ return; }
	if(DirectXFailed(sound3DBuffer_->SetMaxDistance(
		maximumDistance, DS3D_DEFERRED))){
		ErrorOut("Sound3D::setMaximumDistance() "
			"最大距離の設定に失敗しました。");
	}
	maximumDistance_ = maximumDistance;
}
//------------------------------------------------------------------------------
// コーンの向きの設定
void Sound3D::setConeDirection(const Vector3& coneDirection){
	if(coneDirection_ == coneDirection){ return; }
	if(DirectXFailed(sound3DBuffer_->SetConeOrientation(
		-coneDirection.x, coneDirection.y, coneDirection.z, DS3D_DEFERRED))){
		ErrorOut("Sound3D::setConeDirection() "
			"コーンの向きの設定に失敗しました。");
	}
	coneDirection_ = coneDirection;
}
//------------------------------------------------------------------------------
// コーン角度の設定
void Sound3D::setConeAngle(float insideConeAngle, float outsideConeAngle){
	if((insideConeAngle_ == insideConeAngle) &&
		(outsideConeAngle_ == outsideConeAngle)){ return; }
	Assert(insideConeAngle <= outsideConeAngle);
	Assert((insideConeAngle >= 0.f) && (insideConeAngle <= Math::doublePI));
	Assert((outsideConeAngle >= 0.f) && (outsideConeAngle <= Math::doublePI));
	if(DirectXFailed(sound3DBuffer_->SetConeAngles(
		(u_int)Math::toDegree(insideConeAngle),
		(u_int)Math::toDegree(outsideConeAngle), DS3D_DEFERRED))){
		ErrorOut("Sound3D::setConeAngle() コーン角度の設定に失敗しました。");
	}
	insideConeAngle_ = insideConeAngle;
	outsideConeAngle_ = outsideConeAngle;
}
//------------------------------------------------------------------------------
// コーン外側ボリュームの設定
void Sound3D::setConeOutsideVolume(float coneOutsideVolume){
	if(coneOutsideVolume_ == coneOutsideVolume){ return; }
	int db = volumeToDecibel(coneOutsideVolume);
	if(DirectXFailed(sound3DBuffer_->SetConeOutsideVolume(db, DS3D_DEFERRED))){
		ErrorOut("Sound3D::setConeOutsideVolume() "
			"コーン外側ボリュームの設定に失敗しました。");
	}
	coneOutsideVolume_ = coneOutsideVolume;
}
//------------------------------------------------------------------------------
// 3Dの有効、無効設定
void Sound3D::set3DEnabled(bool enabled){
	if(is3DEnabled_ == enabled){ return; }
	u_int mode;
	if(enabled){ mode = DS3DMODE_NORMAL; }
	else{ mode = DS3DMODE_DISABLE; }
	if(DirectXFailed(sound3DBuffer_->SetMode(mode, DS3D_DEFERRED))){
		ErrorOut("Sound3D::set3DEnabled() 3Dの有効、無効設定に失敗しました。");
	}
	is3DEnabled_ = enabled;
}
//------------------------------------------------------------------------------
// 3D設定の適用
void Sound3D::apply3DSettings(){
	LampSound::getSoundListener()->apply3DSettings();
}
//------------------------------------------------------------------------------
// その他
//------------------------------------------------------------------------------
// リセット
void Sound3D::reset(Reset flags){
	SoundBuffer::reset(flags);
	if((flags & resetPosition) != 0){ setPosition(Vector3::zero); }
	if((flags & resetVelocity) != 0){ setVelocity(Vector3::zero); }
	if((flags & resetDistance) != 0){
		setDistance(DS3D_DEFAULTMINDISTANCE, DS3D_DEFAULTMAXDISTANCE);
	}
	if((flags & resetConeDirection) != 0){ setConeDirection(Vector3::unitZ); }
	if((flags & resetConeAngle) != 0){
		setConeAngle(Math::doublePI, Math::doublePI);
	}
	if((flags & resetConeOutsideVolume) != 0){ setConeOutsideVolume(1.f); }
	if((flags & reset3DEnabled) != 0){ set3DEnabled(true); }
}
//------------------------------------------------------------------------------
// 文字列への変換
String  Sound3D::toString() const{
	String result, temp;
	result = SoundBuffer::toString();
	if(is3DEnabled_){
		result += "3D Enabled  ";
	}else{
		result += "3D Disabled  ";
	}
	temp.format("BufferSize(%7dbyte)\n", getBufferSize());
	result += temp;
	temp.format("Position ( %.1f, %.1f, %.1f )\n",
		position_.x, position_.y, position_.z);
	result += temp;
	temp.format("Velocity ( %.1f, %.1f, %.1f )\n",
		velocity_.x, velocity_.y, velocity_.z);
	result += temp;
	temp.format("Distance Min %.1f  Max %.3f\n",
		minimumDistance_, maximumDistance_);
	result += temp;
	temp.format("ConeDirection ( %.2f, %.2f, %.2f )\n",
		coneDirection_.x, coneDirection_.y, coneDirection_.z);
	result += temp;
	temp.format("ConeAngle In %5.1f° Out %5.1f° OutVolume %.2f\n",
		Math::toDegree(insideConeAngle_), Math::toDegree(outsideConeAngle_),
		coneOutsideVolume_);
	result += temp;

	return result;
}
//------------------------------------------------------------------------------
} // End of namespace Lamp
//------------------------------------------------------------------------------
