//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------

#include "game/audioEmitter.h"
#include "game/ambientAudioManager.h"
#include "console/consoleTypes.h"
#include "editor/editor.h"
#include "dgl/dgl.h"
#include "sceneGraph/sceneState.h"
#include "game/gameConnection.h"

//------------------------------------------------------------------------------
static MRandomLCG sgRandom(0xdeadbeef);

#define UPDATE_BUMP_MS  50
extern ALuint alxGetWaveLen(ALuint buffer);
extern ALuint alxFindSource(AUDIOHANDLE handle);

IMPLEMENT_CO_NETOBJECT_V1(AudioEmitter);

//------------------------------------------------------------------------------
class AudioEmitterLoopEvent : public SimEvent
{
   public:
      void process(SimObject * object) {
         ((AudioEmitter*)object)->processLoopEvent();
      }
};

//------------------------------------------------------------------------------
AudioEmitter::AudioEmitter(bool client)
{
   // ::packData depends on these values... make sure it is updated with changes
   mTypeMask |= MarkerObjectType;

   if(client)
      mNetFlags = 0;
   else
      mNetFlags.set(Ghostable|ScopeAlways);

   mAudioHandle = NULL_AUDIOHANDLE;

   mAudioProfileId = 0;
   mAudioDescriptionId = 0;

   mLoopCount = smDefaultDescription.mLoopCount;
   mEventID = 0;

   mOwnedByClient = false;

   // field defaults
   mAudioProfile = 0;
   mAudioDescription = 0;
   mFilename = 0;
   mUseProfileDescription = false;
   mOutsideAmbient = true;
   mDescription = smDefaultDescription;

   mEnableVisualFeedback = true;
   mAnimRotAngle=0.f;
}

Audio::Description AudioEmitter::smDefaultDescription;

//------------------------------------------------------------------------------
void AudioEmitter::processLoopEvent()
{
   if(mLoopCount == 0)
      return;
   else if(mLoopCount > 0)
      mLoopCount--;

   if(mUseProfileDescription && mAudioProfileId)
      return;

   if(!mDescription.mIsLooping || ((mDescription.mLoopCount <= 0) && !mDescription.mMaxLoopGap))
      return;

   // check if still playing...
   U32 source = alxFindSource(mAudioHandle);
   if(source != -1)
   {
      ALint state = AL_STOPPED;
      alGetSourcei(source, AL_SOURCE_STATE, &state);
      if(state == AL_PLAYING)
      {
         mEventID = Sim::postEvent(this, new AudioEmitterLoopEvent, Sim::getCurrentTime() + UPDATE_BUMP_MS);
         mLoopCount++;  // this one doesn't count because we are still playing
         return;
      }
   }

   mEventID = 0;
   mDirty.set(SourceMask);
   update();
}

//------------------------------------------------------------------------------
bool AudioEmitter::update()
{
   // object does not know it is really a client object if created through event
   AssertFatal(isClientObject() || mOwnedByClient, "AudioEmitter::update: only clients can update!");

   if(mDirty.test(LoopCount))
      mLoopCount = mDescription.mLoopCount;

   // updating source?
   if(mDirty.test(SourceMask|UseProfileDescription) || (!mUseProfileDescription &&
      ((mDescription.mIsLooping && mDirty.test(LoopingMask)) || mDirty.test(IsLooping|Is3D|AudioType))))
   {
      if(mAudioHandle != NULL_AUDIOHANDLE)
      {
         alxStop(mAudioHandle);
         mAudioHandle = NULL_AUDIOHANDLE;
      }

      // profile:
      if(mDirty.test(Profile))
      {
         if(!mAudioProfileId)
            mAudioProfile = 0;
         else
            mAudioProfile = dynamic_cast<AudioProfile*>(Sim::findObject(mAudioProfileId));
      }

      // description:
      if(mDirty.test(Description))
      {
         if(!mAudioDescriptionId)
            mAudioDescription = 0;
         else
            mAudioDescription = dynamic_cast<AudioDescription*>(Sim::findObject(mAudioDescriptionId));
      }

      MatrixF transform = getTransform();

      // use the profile?
      if(mUseProfileDescription && mAudioProfileId)
         mAudioHandle = alxCreateSource(mAudioProfile, &transform);
      else
      {
         // grab the filename
         const char * fileName = 0;
         if(mFilename && mFilename[0])
            fileName = mFilename;
         else if(mAudioProfile)
            fileName = mAudioProfile->mFilename;

         if(!fileName)
         {
            Con::errorf(ConsoleLogEntry::General, "AudioEmitter::update: invalid audio filename!");
            return(false);
         }

         // use a description?
         if(mAudioDescription)
            mAudioHandle = alxCreateSource(mAudioDescription, fileName, &transform);
         else
         {
            if(mDescription.mIsLooping)
            {
               S32 minGap = mClamp(mDescription.mMinLoopGap, 0, mDescription.mMaxLoopGap);
               S32 maxGap = mClamp(mDescription.mMaxLoopGap, mDescription.mMinLoopGap, mDescription.mMaxLoopGap);

               // controlling looping?
               if((mDescription.mLoopCount != -1) || maxGap)
               {
                  mDescription.mIsLooping = false;
                  mAudioHandle = alxCreateSource(&mDescription, fileName, &transform);
                  mDescription.mIsLooping = true;

                  // handle may come back as null.. (0 volume, no handles, ...), still
                  // want to try again (assume good buffer) since this is a looper
                  Resource<AudioBuffer> buffer = AudioBuffer::find(fileName);
                  S32 waveLen = 0;
                  if(bool(buffer))
                  {
                     ALuint alBuffer = buffer->getALBuffer();
                     waveLen = alxGetWaveLen(alBuffer);
                  }

                  if(waveLen)
                  {
                     S32 offset = waveLen + minGap + sgRandom.randI(minGap, maxGap);

                     if(mEventID)
                        Sim::cancelEvent(mEventID);
                     mEventID = Sim::postEvent(this, new AudioEmitterLoopEvent, Sim::getCurrentTime() + offset);

                     if(mAudioHandle == NULL_AUDIOHANDLE)
                        return(true);
                  }
               }
               else
                  mAudioHandle = alxCreateSource(&mDescription, fileName, &transform);
            }
            else
               mAudioHandle = alxCreateSource(&mDescription, fileName, &transform);
         }
      }

      if(mAudioHandle == NULL_AUDIOHANDLE)
      {
         Con::errorf(ConsoleLogEntry::General, "AudioEmitter::update: failed to create source!");
         return(false);
      }

      alxPlay(mAudioHandle);
   }
   else
   {
      // don't clear dirty flags until there is a source
      if(mAudioHandle == NULL_AUDIOHANDLE)
         return(true);

      if(mDirty.test(Transform))
      {
         // Update the position.
         Point3F pos;
         mObjToWorld.getColumn(3, &pos);
         alxSource3f(mAudioHandle, AL_POSITION, pos.x, pos.y, pos.z);

         //Make sure we update the direction also.
         mObjToWorld.getColumn(1, &pos);
         alSource3f(mAudioHandle, AL_DIRECTION, pos.x, pos.y, pos.z);
      }

      // grab the description
      const Audio::Description * desc = 0;

      if(mUseProfileDescription && mAudioProfile)
         desc = mAudioProfile->getDescription();
      else if(mAudioDescription)
         desc = mAudioDescription->getDescription();
      else
         desc = &mDescription;

      if(!desc)
         return(true);

      if(mDirty.test(Volume))
         alxSourcef(mAudioHandle, AL_GAIN_LINEAR, desc->mVolume);

      if(mDirty.test(ReferenceDistance|MaxDistance))
      {
         alxSourcef(mAudioHandle, AL_REFERENCE_DISTANCE, desc->mReferenceDistance);
         alxSourcef(mAudioHandle, AL_MAX_DISTANCE, desc->mMaxDistance);
      }

      if(mDirty.test(ConeInsideAngle))
         alxSourcei(mAudioHandle, AL_CONE_INNER_ANGLE, desc->mConeInsideAngle);

      if(mDirty.test(ConeOutsideAngle))
         alxSourcei(mAudioHandle, AL_CONE_OUTER_ANGLE, desc->mConeOutsideAngle);

      if(mDirty.test(ConeOutsideVolume))
         alxSourcef(mAudioHandle, AL_CONE_OUTER_GAIN, desc->mConeOutsideVolume);
   }

   mDirty.clear();
   return(true);
}

//------------------------------------------------------------------------------
bool AudioEmitter::onAdd()
{
   if(!Parent::onAdd())
      return(false);

   // object does not know it is really a client object if created through event
   if(isServerObject() && !mOwnedByClient)
   {
      // validate the object data
      mDescription.mVolume = mClampF(mDescription.mVolume, 0.0f, 1.0f);
      mDescription.mLoopCount = mClamp(mDescription.mLoopCount, -1, mDescription.mLoopCount);
      mDescription.mMaxLoopGap = mClamp(mDescription.mMaxLoopGap, mDescription.mMinLoopGap, mDescription.mMaxLoopGap);
      mDescription.mMinLoopGap = mClamp(mDescription.mMinLoopGap, 0, mDescription.mMaxLoopGap);

      if(mDescription.mIs3D)
      {
         mDescription.mReferenceDistance = mClampF(mDescription.mReferenceDistance, 0.f, mDescription.mReferenceDistance);
         mDescription.mMaxDistance = (mDescription.mMaxDistance > mDescription.mReferenceDistance) ? mDescription.mMaxDistance : (mDescription.mReferenceDistance+0.01f);
         mDescription.mConeInsideAngle = mClamp(mDescription.mConeInsideAngle, 0, 360);
         mDescription.mConeOutsideAngle = mClamp(mDescription.mConeOutsideAngle, mDescription.mConeInsideAngle, 360);
         mDescription.mConeOutsideVolume = mClampF(mDescription.mConeOutsideVolume, 0.0f, 1.0f);
         mDescription.mConeVector.normalize();
      }
   }
   else
   {
      if(!update())
      {
         Con::errorf(ConsoleLogEntry::General, "AudioEmitter::onAdd: client failed initial update!");
         if(mOwnedByClient)
            return(false);
      }
      gAmbientAudioManager.addEmitter(this);
   }

   //
   mObjBox.max = mObjScale;
   mObjBox.min = mObjScale;
   mObjBox.min.neg();
   resetWorldBox();
   addToScene();

   return(true);
}

void AudioEmitter::onRemove()
{
   if(isClientObject())
   {
      gAmbientAudioManager.removeEmitter(this);
      if(mAudioHandle != NULL_AUDIOHANDLE)
         alxStop(mAudioHandle);
   }
   removeFromScene();
   Parent::onRemove();
}

void AudioEmitter::setTransform(const MatrixF & mat)
{
    // Set the transform directly from the matrix created by inspector
    // Also, be sure to strip out the pointing vector and place it in the cone vector
    Point3F pointing;
    mat.getColumn(1, &pointing);
    mDescription.mConeVector = pointing;
    Parent::setTransform(mat);
    setMaskBits(TransformUpdateMask);
}

void AudioEmitter::setScale(const VectorF &)
{
}

//------------------------------------------------------------------------------
namespace {
   static Point3F cubePoints[8] = {
      Point3F(-1, -1, -1), Point3F(-1, -1,  1), Point3F(-1,  1, -1), Point3F(-1,  1,  1),
      Point3F( 1, -1, -1), Point3F( 1, -1,  1), Point3F( 1,  1, -1), Point3F( 1,  1,  1)
   };

   static U32 cubeFaces[6][4] = {
      { 0, 2, 6, 4 }, { 0, 2, 3, 1 }, { 0, 1, 5, 4 },
      { 3, 2, 6, 7 }, { 7, 6, 4, 5 }, { 3, 7, 5, 1 }
   };

   static Point2F textureCoords[4] = {
      Point2F(0, 0), Point2F(1, 0), Point2F(1, 1), Point2F(0, 1)
   };
}

bool AudioEmitter::prepRenderImage(SceneState * state, const U32 stateKey, const U32, const bool)
{
   if(!gEditingMission || isLastState(state, stateKey))
      return(false);

   setLastState(state, stateKey);
   if(gEditingMission && state->isObjectRendered(this))
   {
      SceneRenderImage * image = new SceneRenderImage;
      image->obj = this;
      state->insertRenderImage(image);
   }
   return(false);
}

void AudioEmitter::render3DVisualFeedBack(void)
{
    const float RADINC  = 30.f;
    const float SWPINC  = 10.f;
    const float FULLPNT = 6.f;

    float OldPointSize  = 1.f;
    float OldLineWidth  = 1.f;
    float OldColor[4]   = {1.f,1.f,1.f,1.f};

    float fPointIncrement = (mDescription.mMaxDistance/10.f);

    //
    // Startup Ops
    //
    glGetFloatv(GL_POINT_SIZE,      &OldPointSize);
    glGetFloatv(GL_LINE_WIDTH,      &OldLineWidth);
    glGetFloatv(GL_CURRENT_COLOR,   &OldColor[0]);
    glPushMatrix();
    glRotatef(mAnimRotAngle+=1.f,0.f,1.f,0.f);
    if(mAnimRotAngle >= 360.f) mAnimRotAngle = 0.f;
    //
    // RenderOps
    //
    // 0. INNER CONE
    {
        glPointSize(FULLPNT);

        // Radial Sweep - Sweeps about radius of current angular sweep angle
        for(float fRadAngle=0.f; fRadAngle < 360.f; fRadAngle += RADINC) {

            bool  bDone         = false;
            float fCurAngle     = mDescription.mConeInsideAngle/2;
            float fStopAngle    = 0;

            // Angular Sweep - Sweeps from outer angle of INNER CONE to 0 DEG
            while(!bDone)
            {
                glPushMatrix();
                glRotatef(fRadAngle, 0.f, 1.f, 0.f);
                glRotatef(fCurAngle, 1.f, 0.f, 0.f);

                glBegin(GL_POINTS);
                glColor4f(1.f,0.0f,0.0f,1.f);

                for(float fY=0.f;
                fY <= mDescription.mMaxDistance;
                fY += fPointIncrement) {
                    if(fY > mDescription.mReferenceDistance) {
                        glColor4f(1.f - (1.f - (mDescription.mReferenceDistance/fY)), 0.f,0.f,1.f);
                    }
                    glVertex3f(0.f, fY, 0.f);
                }

                glEnd();

                glPopMatrix();

                if(fStopAngle == fCurAngle) bDone = true;

                fCurAngle -= SWPINC;
                if(fStopAngle > fCurAngle) fCurAngle = fStopAngle;
            }
        }
    }
    // Outer Cone and Outside volume only get rendered if mConeOutsideVolume > 0
    if(mDescription.mConeOutsideVolume) {
        // 1. OUTER CONE
        {
            glPointSize(FULLPNT-1);

            // Radial Sweep - Sweeps about radius of current angular sweep angle
            for(float fRadAngle=0.f; fRadAngle < 360.f; fRadAngle += RADINC) {

                bool  bDone         = false;
                float fCurAngle     = mDescription.mConeOutsideAngle/2;
                float fStopAngle    = mDescription.mConeInsideAngle/2;

                // Angular Sweep - Sweeps from outer angle of INNER CONE to 0 DEG
                while(!bDone)
                {

                    glPushMatrix();
                    glRotatef(fRadAngle, 0.f, 1.f, 0.f);
                    glRotatef(fCurAngle, 1.f, 0.f, 0.f);

                    glBegin(GL_POINTS);
                    glColor4f(1.f,
                        0.0f,
                        1.0f,
                        1.f);

                    for(float fY=0.f;
                    fY <= mDescription.mMaxDistance;
                    fY += fPointIncrement) {
                        glVertex3f(0.f, fY, 0.f);
                    }

                    glEnd();

                    glPopMatrix();

                    if(fStopAngle == fCurAngle) bDone = true;

                    fCurAngle -= SWPINC*2.f;
                    if(fStopAngle > fCurAngle) fCurAngle = fStopAngle;
                }
            }

        }
        // 1. OUTSIDE VOLUME
        {
            glPointSize(FULLPNT-2);

            // Radial Sweep - Sweeps about radius of current angular sweep angle
            for(float fRadAngle=0.f; fRadAngle < 360.f; fRadAngle += RADINC) {

                bool  bDone         = false;
                float fCurAngle     = 180;
                float fStopAngle    = mDescription.mConeOutsideAngle/2;

                // Angular Sweep - Sweeps from outer angle of INNER CONE to 0 DEG
                while(!bDone)
                {

                    glPushMatrix();
                    glRotatef(fRadAngle, 0.f, 1.f, 0.f);
                    glRotatef(fCurAngle, 1.f, 0.f, 0.f);

                    glBegin(GL_POINTS);
                    glColor4f(0.f, 0.0f, 1.0f,1.f);

                    for(float fY=0.f;
                    fY <= mDescription.mMaxDistance;
                    fY += fPointIncrement) {
                        glVertex3f(0.f, fY, 0.f);
                    }

                    glEnd();

                    glPopMatrix();

                    if(fStopAngle == fCurAngle) bDone = true;

                    fCurAngle -= SWPINC*2.f;
                    if(fStopAngle > fCurAngle) fCurAngle = fStopAngle;
                }
            }

        }
    }
    //
    // Cleanup Ops
    //
    glPopMatrix();
    glColor4f(OldColor[0],OldColor[1],OldColor[2],OldColor[3]);
    glLineWidth(OldLineWidth);
    glPointSize(OldPointSize);
}

void AudioEmitter::renderObject(SceneState*, SceneRenderImage*)
{
   glPushMatrix();
   dglMultMatrix(&mObjToWorld);
   glScalef(mObjScale.x, mObjScale.y, mObjScale.z);

   glDisable(GL_CULL_FACE);
   Point3F size(1.f, 1.f, 1.f);


   // draw the cube
   // Check to see if it is playing (green == playing; black == off)
   glColor4f(0.f,0.f,0.f,1.f);
   U32 source = alxFindSource(mAudioHandle);
   if(source != -1)
   {

       ALint state = AL_STOPPED;
       alGetSourcei(source, AL_SOURCE_STATE, &state);
       if(state == AL_PLAYING)
       {
           Point3F listener;
           alGetListener3f(AL_POSITION, &listener.x, &listener.y, &listener.z);

           Point3F pos;
           alGetSourcefv(source, AL_POSITION, (F32*)pos);

           F32 dist = 0.f;
           alGetSourcef(source, AL_MAX_DISTANCE, &dist);

           pos -= listener;
           dist -= pos.len();

           if (dist > 0.f)
           {
               glColor4f(0.2f,1.f,0.2f,1.f);
           }
       }
   }

   for(int i = 0; i < 6; i++)
   {
       glBegin(GL_QUADS);
       for(int vert = 0; vert < 4; vert++)
       {
           int idx = cubeFaces[i][vert];
           glVertex3f(cubePoints[idx].x * size.x, cubePoints[idx].y * size.y, cubePoints[idx].z * size.z);
       }
       glEnd();
   }

   // Only render 3D visuals if in 3D Audio Mode
   if(mDescription.mIs3D && ( mEnableVisualFeedback || isSelected() ) ) render3DVisualFeedBack();

   glPopMatrix();
}

//------------------------------------------------------------------------------
namespace {
   static AudioProfile * saveAudioProfile;
   static AudioDescription * saveAudioDescription;
   static StringTableEntry saveFilename;
   static bool saveUseProfileDescription;
   static Audio::Description saveDescription;
   static Point3F savePos;
   static bool saveOutsideAmbient;
}

void AudioEmitter::inspectPreApply()
{
   if(isClientObject())
      return;

   Parent::inspectPreApply();

   mObjToWorld.getColumn(3, &savePos);
   saveAudioProfile = mAudioProfile;
   saveAudioDescription = mAudioDescription;
   saveFilename = mFilename;
   saveUseProfileDescription = mUseProfileDescription;
   saveDescription = mDescription;
   saveOutsideAmbient = mOutsideAmbient;
}

void AudioEmitter::inspectPostApply()
{
   if(isClientObject())
      return;

   Parent::inspectPostApply();

   Point3F pos;
   mObjToWorld.getColumn(3, &pos);

   // set some dirty flags
   mDirty.clear();
   mDirty.set((savePos != pos) ? Transform : 0);
   mDirty.set((saveAudioProfile != mAudioProfile) ? Profile : 0);
   mDirty.set((saveAudioDescription != mAudioDescription) ? Description : 0);
   mDirty.set((saveFilename != mFilename) ? Filename : 0);
   mDirty.set((saveUseProfileDescription != mUseProfileDescription) ? UseProfileDescription : 0);
   mDirty.set((saveDescription.mVolume != mDescription.mVolume) ? Volume : 0);
   mDirty.set((saveDescription.mIsLooping != mDescription.mIsLooping) ? IsLooping : 0);
   mDirty.set((saveDescription.mIs3D != mDescription.mIs3D) ? Is3D : 0);
   mDirty.set((saveDescription.mReferenceDistance != mDescription.mReferenceDistance) ? ReferenceDistance : 0);
   mDirty.set((saveDescription.mMaxDistance != mDescription.mMaxDistance) ? MaxDistance : 0);
   mDirty.set((saveDescription.mConeInsideAngle != mDescription.mConeInsideAngle) ? ConeInsideAngle : 0);
   mDirty.set((saveDescription.mConeOutsideAngle != mDescription.mConeOutsideAngle) ? ConeOutsideAngle : 0);
   mDirty.set((saveDescription.mConeOutsideVolume != mDescription.mConeOutsideVolume) ? ConeOutsideVolume : 0);
   mDirty.set((saveDescription.mConeVector != mDescription.mConeVector) ? ConeVector : 0);
   mDirty.set((saveDescription.mLoopCount != mDescription.mLoopCount) ? LoopCount : 0);
   mDirty.set((saveDescription.mMinLoopGap != mDescription.mMinLoopGap) ? MinLoopGap : 0);
   mDirty.set((saveDescription.mMaxLoopGap != mDescription.mMaxLoopGap) ? MaxLoopGap : 0);
   mDirty.set((saveDescription.mType != mDescription.mType) ? AudioType : 0);
   mDirty.set((saveOutsideAmbient != mOutsideAmbient) ? OutsideAmbient : 0);

   if(mDirty)
      setMaskBits(DirtyUpdateMask);
}

void AudioEmitter::packData(NetConnection *, U32 mask, BitStream * stream)
{
   // initial update
   if(stream->writeFlag(mask & InitialUpdateMask))
   {
      mask |= TransformUpdateMask;
      mDirty = AllDirtyMask;

      // see if can remove some items from initial update: description checked below
      if(!mAudioProfile)
         mDirty.clear(Profile);
      if(!mAudioDescription)
         mDirty.clear(Description);
      if(!mFilename || !mFilename[0])
         mDirty.clear(Filename);
      if(!mUseProfileDescription)
         mDirty.clear(UseProfileDescription);
      if(mOutsideAmbient)
         mDirty.clear(OutsideAmbient);
   }

   // transform
   if(stream->writeFlag(mask & TransformUpdateMask))
      stream->writeAffineTransform(mObjToWorld);

   // profile
   if(stream->writeFlag(mDirty.test(Profile)))
      if(stream->writeFlag(mAudioProfile))
         stream->writeRangedU32(mAudioProfile->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast);

   // description
   if(stream->writeFlag(mDirty.test(Description)))
      if(stream->writeFlag(mAudioDescription))
         stream->writeRangedU32(mAudioDescription->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast);

   // filename
   if(stream->writeFlag(mDirty.test(Filename)))
      stream->writeString(mFilename);

   // useprofiledescription
   if(stream->writeFlag(mDirty.test(UseProfileDescription)))
   {
      if(mUseProfileDescription)
      {
         if(!mAudioProfile)
            mUseProfileDescription = false;
         else
            mDirty.clear(UseProfileDescriptionMask);
      }

      if(!mUseProfileDescription)
         mDirty.set(UseProfileDescriptionMask);

      stream->writeFlag(mUseProfileDescription);
   }

   if(mAudioDescription && !mUseProfileDescription)
      mDirty.clear(UseProfileDescriptionMask);

   // check initial update against the default description
   if((mask & InitialUpdateMask) && !mUseProfileDescription)
   {
      if(mDescription.mVolume == smDefaultDescription.mVolume) mDirty.clear(Volume);
      AssertFatal(smDefaultDescription.mIsLooping, "Doh!");
      if(mDescription.mIsLooping != smDefaultDescription.mIsLooping)
         mDirty.clear(LoopingMask);
      else
      {
         mDirty.clear(IsLooping);
         if(mDescription.mLoopCount == smDefaultDescription.mLoopCount) mDirty.clear(LoopCount);
         if(mDescription.mMinLoopGap == smDefaultDescription.mMinLoopGap) mDirty.clear(MinLoopGap);
         if(mDescription.mMaxLoopGap == smDefaultDescription.mMaxLoopGap) mDirty.clear(MaxLoopGap);
      }
      AssertFatal(smDefaultDescription.mIs3D, "Doh!");
      if(mDescription.mIs3D != smDefaultDescription.mIs3D)
         mDirty.clear(Is3DMask);
      else
      {
         mDirty.clear(Is3D);
         if(mDescription.mReferenceDistance == smDefaultDescription.mReferenceDistance) mDirty.clear(ReferenceDistance);
         if(mDescription.mMaxDistance == smDefaultDescription.mMaxDistance) mDirty.clear(MaxDistance);
         if(mDescription.mConeInsideAngle == smDefaultDescription.mConeInsideAngle) mDirty.clear(ConeInsideAngle);
         if(mDescription.mConeOutsideAngle == smDefaultDescription.mConeOutsideAngle) mDirty.clear(ConeOutsideAngle);
         if(mDescription.mConeOutsideVolume == smDefaultDescription.mConeOutsideVolume) mDirty.clear(ConeOutsideVolume);
         if(mDescription.mConeVector == smDefaultDescription.mConeVector)  mDirty.clear(ConeVector);
      }
      if(mDescription.mType == smDefaultDescription.mType) mDirty.clear(AudioType);
   }

   // volume
   if(stream->writeFlag(mDirty.test(Volume)))
      stream->write(mDescription.mVolume);

   // islooping
   if(stream->writeFlag(mDirty.test(IsLooping)))
   {
      mDescription.mIsLooping ? mDirty.set(LoopingMask) : mDirty.clear(LoopingMask);
      stream->writeFlag(mDescription.mIsLooping);
   }

   // is3d
   if(stream->writeFlag(mDirty.test(Is3D)))
   {
      mDescription.mIs3D ? mDirty.set(Is3DMask) : mDirty.clear(Is3DMask);
      stream->writeFlag(mDescription.mIs3D);
   }

   // ReferenceDistance
   if(stream->writeFlag(mDirty.test(ReferenceDistance)))
      stream->write(mDescription.mReferenceDistance);

   // maxdistance
   if(stream->writeFlag(mDirty.test(MaxDistance)))
      stream->write(mDescription.mMaxDistance);

   // coneinsideangle
   if(stream->writeFlag(mDirty.test(ConeInsideAngle)))
      stream->write(mDescription.mConeInsideAngle);

   // coneoutsideangle
   if(stream->writeFlag(mDirty.test(ConeOutsideAngle)))
      stream->write(mDescription.mConeOutsideAngle);

   // coneoutsidevolume
   if(stream->writeFlag(mDirty.test(ConeOutsideVolume)))
      stream->write(mDescription.mConeOutsideVolume);

   // conevector
   if(stream->writeFlag(mDirty.test(ConeVector)))
   {
      stream->write(mDescription.mConeVector.x);
      stream->write(mDescription.mConeVector.y);
      stream->write(mDescription.mConeVector.z);
   }

   // loopcount
   if(stream->writeFlag(mDirty.test(LoopCount)))
      stream->write(mDescription.mLoopCount);

   // minloopgap
   if(stream->writeFlag(mDirty.test(MinLoopGap)))
      stream->write(mDescription.mMinLoopGap);

   // maxloopgap
   if(stream->writeFlag(mDirty.test(MaxLoopGap)))
      stream->write(mDescription.mMaxLoopGap);

   // audiotype
   if(stream->writeFlag(mDirty.test(AudioType)))
      stream->write(mDescription.mType);

   // outside ambient
   if(stream->writeFlag(mDirty.test(OutsideAmbient)))
      stream->writeFlag(mOutsideAmbient);

   stream->write(mEnableVisualFeedback);

   mDirty.clear();
}

//------------------------------------------------------------------------------
U32 AudioEmitter::packUpdate(NetConnection * con, U32 mask, BitStream * stream)
{
   U32 retMask = Parent::packUpdate(con, mask, stream);
   packData(con, mask, stream);
   return(retMask);
}

bool AudioEmitter::readDirtyFlag(BitStream * stream, U32 mask)
{
   bool flag = stream->readFlag();
   if(flag)
      mDirty.set(mask);
   return(flag);
}

void AudioEmitter::unpackData(NetConnection *, BitStream * stream)
{
   // initial update?
   bool initialUpdate = stream->readFlag();

   // transform
   if(readDirtyFlag(stream, Transform))
   {
      MatrixF mat;
      stream->readAffineTransform(&mat);
      Parent::setTransform(mat);
   }

   // profile
   if(readDirtyFlag(stream, Profile))
      if(stream->readFlag())
         mAudioProfileId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
      else
         mAudioProfileId = 0;

   // description
   if(readDirtyFlag(stream, Description))
      if(stream->readFlag())
         mAudioDescriptionId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
      else
         mAudioDescriptionId = 0;

   // filename
   if(readDirtyFlag(stream, Filename))
      mFilename = stream->readSTString();

   // useprofiledescription
   if(readDirtyFlag(stream, UseProfileDescription))
      mUseProfileDescription = stream->readFlag();

   // volume
   if(readDirtyFlag(stream, Volume))
      stream->read(&mDescription.mVolume);

   // islooping
   if(readDirtyFlag(stream, IsLooping))
      mDescription.mIsLooping = stream->readFlag();

   // is3d
   if(readDirtyFlag(stream, Is3D))
      mDescription.mIs3D = stream->readFlag();

   // ReferenceDistance
   if(readDirtyFlag(stream, ReferenceDistance))
      stream->read(&mDescription.mReferenceDistance);

   // maxdistance
   if(readDirtyFlag(stream, MaxDistance))
      stream->read(&mDescription.mMaxDistance);

   // coneinsideangle
   if(readDirtyFlag(stream, ConeInsideAngle))
      stream->read(&mDescription.mConeInsideAngle);

   // coneoutsideangle
   if(readDirtyFlag(stream, ConeOutsideAngle))
      stream->read(&mDescription.mConeOutsideAngle);

   // coneoutsidevolume
   if(readDirtyFlag(stream, ConeOutsideVolume))
      stream->read(&mDescription.mConeOutsideVolume);

   // conevector
   if(readDirtyFlag(stream, ConeVector))
   {
      stream->read(&mDescription.mConeVector.x);
      stream->read(&mDescription.mConeVector.y);
      stream->read(&mDescription.mConeVector.z);
   }

   // loopcount
   if(readDirtyFlag(stream, LoopCount))
      stream->read(&mDescription.mLoopCount);

   // minloopgap
   if(readDirtyFlag(stream, MinLoopGap))
   {
      stream->read(&mDescription.mMinLoopGap);
      if(mDescription.mMinLoopGap < 0)
         mDescription.mMinLoopGap = 0;
   }

   // maxloopgap
   if(readDirtyFlag(stream, MaxLoopGap))
   {
      stream->read(&mDescription.mMaxLoopGap);
      if(mDescription.mMaxLoopGap < 0)
         mDescription.mMaxLoopGap = 0;
   }

   // audiotype
   if(readDirtyFlag(stream, AudioType))
   {
      stream->read(&mDescription.mType);
      if(mDescription.mType >= Audio::NumAudioTypes)
         mDescription.mType = 0;
   }

   // outside ambient
   if(readDirtyFlag(stream, OutsideAmbient))
      mOutsideAmbient = stream->readFlag();

   stream->read(&mEnableVisualFeedback);

   // update the emitter now?
   if(!initialUpdate)
      update();
}

void AudioEmitter::unpackUpdate(NetConnection * con, BitStream * stream)
{
   Parent::unpackUpdate(con, stream);
   unpackData(con, stream);
}

void AudioEmitter::initPersistFields()
{
   Parent::initPersistFields();

   addGroup("Profile");
   addField("profile",              TypeAudioProfilePtr, Offset(mAudioProfile, AudioEmitter));
   addField("useProfileDescription",  TypeBool,          Offset(mUseProfileDescription, AudioEmitter));
   endGroup("Profile");

   addGroup("Media");
   addField("description",          TypeAudioDescriptionPtr,   Offset(mAudioDescription, AudioEmitter));
   addField("fileName",             TypeFilename,              Offset(mFilename, AudioEmitter));
   addField("type",                 TypeS32,                   Offset(mDescription.mType, AudioEmitter));
   endGroup("Media");

   addGroup("Sound");
   addField("volume",               TypeF32,    Offset(mDescription.mVolume, AudioEmitter));
   addField("outsideAmbient",       TypeBool,   Offset(mOutsideAmbient, AudioEmitter));
   addField("ReferenceDistance",    TypeF32,    Offset(mDescription.mReferenceDistance, AudioEmitter));
   addField("maxDistance",          TypeF32,    Offset(mDescription.mMaxDistance, AudioEmitter));
   endGroup("Sound");

   addGroup("Looping");
   addField("isLooping",            TypeBool,   Offset(mDescription.mIsLooping, AudioEmitter));
   addField("loopCount",            TypeS32,    Offset(mDescription.mLoopCount, AudioEmitter));
   addField("minLoopGap",           TypeS32,    Offset(mDescription.mMinLoopGap, AudioEmitter));
   addField("maxLoopGap",           TypeS32,    Offset(mDescription.mMaxLoopGap, AudioEmitter));
   endGroup("Looping");

   addGroup("Advanced");
   addField("enableVisualFeedback", TypeBool,   Offset(mEnableVisualFeedback, AudioEmitter));
   addField("is3D",                 TypeBool,   Offset(mDescription.mIs3D, AudioEmitter));
   addField("coneInsideAngle",      TypeS32,    Offset(mDescription.mConeInsideAngle, AudioEmitter));
   addField("coneOutsideAngle",     TypeS32,    Offset(mDescription.mConeOutsideAngle, AudioEmitter));
   addField("coneOutsideVolume",    TypeF32,    Offset(mDescription.mConeOutsideVolume, AudioEmitter));
   addField("coneVector",           TypePoint3F, Offset(mDescription.mConeVector, AudioEmitter));
   endGroup("Advanced");

   // create the static description
   smDefaultDescription.mVolume = 1.0f;
   smDefaultDescription.mIsLooping = true;
   smDefaultDescription.mIs3D = true;
   smDefaultDescription.mReferenceDistance = 1.0f;
   smDefaultDescription.mMaxDistance = 100.0f;
   smDefaultDescription.mConeInsideAngle = 360;
   smDefaultDescription.mConeOutsideAngle = 360;
   smDefaultDescription.mConeOutsideVolume = 1.0f;
   smDefaultDescription.mConeVector.set(0, 0, 1);
   smDefaultDescription.mLoopCount = -1;
   smDefaultDescription.mMinLoopGap = 0;
   smDefaultDescription.mMaxLoopGap = 0;
   smDefaultDescription.mType = 0;
}

////--------------------------------------------------------------------------
//IMPLEMENT_CO_NETOBJECT_V1(ClientAudioEmitter);
//
//ClientAudioEmitter::ClientAudioEmitter()
//{
//   mNetFlags = 0;
//   mEventCount = 0;
//}
//
//bool ClientAudioEmitter::onAdd()
//{
//   if(!SceneObject::onAdd())
//      return(false);
//
//   // create and send events to all clients
//   bool postedEvent = false;
//   for(NetConnection * conn = NetConnection::getConnectionList(); conn; conn = conn->getNext())
//   {
//      if(!conn->isConnectionToServer())
//      {
//         postedEvent = true;
//         conn->postNetEvent(new AudioEmitterToEvent(this));
//      }
//   }
//
//   return(postedEvent);
//}
//
//void ClientAudioEmitter::initPersistFields()
//{
//   Parent::initPersistFields();
//}
//
////--------------------------------------------------------------------------
//IMPLEMENT_CO_NETEVENT_V1(AudioEmitterToEvent);
//
//AudioEmitterToEvent::AudioEmitterToEvent(ClientAudioEmitter * emitter)
//{
//   mEmitter = emitter;
//   if(bool(mEmitter))
//      mEmitter->mEventCount++;
//}
//
//void AudioEmitterToEvent::notifyDelivered(NetConnection *, bool)
//{
//   if(bool(mEmitter) && mEmitter->mEventCount && (--mEmitter->mEventCount == 0))
//   {
//      mEmitter->deleteObject();
//      mEmitter = 0;
//   }
//}
//
//void AudioEmitterToEvent::write(NetConnection * conn, BitStream * bstream)
//{
//   if(bool(mEmitter))
//      mEmitter->packData(conn, AudioEmitter::InitialUpdateMask, bstream);
//}
//
//void AudioEmitterToEvent::pack(NetConnection * conn, BitStream * bstream)
//{
//   if(bool(mEmitter))
//      mEmitter->packData(conn, AudioEmitter::InitialUpdateMask, bstream);
//}
//
//void AudioEmitterToEvent::unpack(NetConnection * conn, BitStream * bstream)
//{
//   // clients are responsible for deleting these audioemitters!
//   AudioEmitter * emitter = new AudioEmitter(true);
//   emitter->mOwnedByClient = true;
//   emitter->unpackData(conn, bstream);
//   emitter->registerOb