Dynamic display compass
CompassCmd.cs
// Copyright 2010 ESRI
// 
// All rights reserved under the copyright laws of the United States
// and applicable international laws, treaties, and conventions.
// 
// You may freely redistribute and use this sample code, with or
// without modification, provided you include the original copyright
// notice and use restrictions.
// 
// See the use restrictions.
// 

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using ESRI.ArcGIS.ADF.BaseClasses;
using ESRI.ArcGIS.ADF.CATIDs;
using ESRI.ArcGIS.Controls;
using ESRI.ArcGIS.Geometry;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Display;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.SystemUI;
using OpenGL;

namespace DynamicDisplayCompass
{
  /// <summary>
  /// Command that works in ArcMap/Map/PageLayout
  /// </summary>
  [Guid("38360569-7dda-4892-a3a0-ea1f3531177b")]
  [ClassInterface(ClassInterfaceType.None)]
  [ProgId("DynamicDisplayCompass.CompassCmd")]
  public sealed class CompassCmd : BaseCommand
  {
    #region COM Registration Function(s)
    [ComRegisterFunction()]
    [ComVisible(false)]
    static void RegisterFunction(Type registerType)
    {
      // Required for ArcGIS Component Category Registrar support
      ArcGISCategoryRegistration(registerType);

      //
      // TODO: Add any COM registration code here
      //
    }

    [ComUnregisterFunction()]
    [ComVisible(false)]
    static void UnregisterFunction(Type registerType)
    {
      // Required for ArcGIS Component Category Registrar support
      ArcGISCategoryUnregistration(registerType);

      //
      // TODO: Add any COM unregistration code here
      //
    }

    #region ArcGIS Component Category Registrar generated code
    /// <summary>
    /// Required method for ArcGIS Component Category registration -
    /// Do not modify the contents of this method with the code editor.
    /// </summary>
    private static void ArcGISCategoryRegistration(Type registerType)
    {
      string regKey = string.Format("HKEY_CLASSES_ROOT\\CLSID\\{{{0}}}", registerType.GUID);
      MxCommands.Register(regKey);
      ControlsCommands.Register(regKey);
    }
    /// <summary>
    /// Required method for ArcGIS Component Category unregistration -
    /// Do not modify the contents of this method with the code editor.
    /// </summary>
    private static void ArcGISCategoryUnregistration(Type registerType)
    {
      string regKey = string.Format("HKEY_CLASSES_ROOT\\CLSID\\{{{0}}}", registerType.GUID);
      MxCommands.Unregister(regKey);
      ControlsCommands.Unregister(regKey);
    }

    #endregion
    #endregion

    #region class members
    private IHookHelper m_hookHelper = null;
    private IDynamicMap m_dynamicMap = null;
    private bool m_bIsDynamicMode = false;
    private bool m_bOnce = true;
    private uint m_compassList = 0;
    private tagRECT m_deviceFrame;
    #endregion

    #region class constructor
    /// <summary>
    /// constructor
    /// </summary>
    public CompassCmd()
    {
      base.m_category = ".NET Samples";
      base.m_caption = "Dynamic Display Compass";
      base.m_message = "Dynamic Display Compass";
      base.m_toolTip = "Dynamic Display Compass";
      base.m_name = "DynamicDisplay_Compass";

      try
      {
        string bitmapResourceName = GetType().Name + ".bmp";
        base.m_bitmap = new Bitmap(GetType(), bitmapResourceName);
      }
      catch (Exception ex)
      {
        System.Diagnostics.Trace.WriteLine(ex.Message, "Invalid Bitmap");
      }
    }
    #endregion

    #region Overriden Class Methods

    /// <summary>
    /// Occurs when this command is created
    /// </summary>
    /// <param name="hook">Instance of the application</param>
    public override void OnCreate(object hook)
    {
      if (hook == null)
        return;

      try
      {
        m_hookHelper = new HookHelperClass();
        m_hookHelper.Hook = hook;
        if (m_hookHelper.ActiveView == null)
          m_hookHelper = null;
      }
      catch
      {
        m_hookHelper = null;
      }

      if (m_hookHelper == null)
        base.m_enabled = false;
      else
        base.m_enabled = true;

      // TODO:  Add other initialization code
    }

    /// <summary>
    /// Occurs when this command is clicked
    /// </summary>
    public override void OnClick()
    {
      m_dynamicMap = m_hookHelper.FocusMap as IDynamicMap;

      if (!m_bIsDynamicMode)
      {
        //switch into dynamic mode
        if (!m_dynamicMap.DynamicMapEnabled)
          m_dynamicMap.DynamicMapEnabled = true;

        //start listening to DynamicMap's 'After Draw' events
        ((IDynamicMapEvents_Event)m_dynamicMap).AfterDynamicDraw += new IDynamicMapEvents_AfterDynamicDrawEventHandler(OnAfterDynamicDraw);
      }
      else
      {
        //stop listening to DynamicMap's 'After Draw' events
        ((IDynamicMapEvents_Event)m_dynamicMap).AfterDynamicDraw -= new IDynamicMapEvents_AfterDynamicDrawEventHandler(OnAfterDynamicDraw);

        //leave dynamic mode
        if (m_dynamicMap.DynamicMapEnabled)
          m_dynamicMap.DynamicMapEnabled = false;


      }
      m_bIsDynamicMode = !m_bIsDynamicMode;
      m_hookHelper.ActiveView.Refresh ();
    }

    public override bool Checked
    {
      get { return m_bIsDynamicMode; }
    }

    #endregion

    #region  private methods
    /// <summary>
    /// nDeviceFrameUpdated event handler
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="sizeChanged"></param>
    private void OnDeviceFrameUpdated(IDisplayTransformation sender, bool sizeChanged)
    {
      //update the device frame rectangle
      m_deviceFrame = sender.get_DeviceFrame();
    }

    /// <summary>
    /// DynamicMap AfterDynamicDraw event handler method
    /// </summary>
    /// <param name="DynamicMapDrawPhase"></param>
    /// <param name="Display"></param>
    /// <param name="dynamicDisplay"></param>
    void OnAfterDynamicDraw(esriDynamicMapDrawPhase DynamicMapDrawPhase, IDisplay Display, IDynamicDisplay dynamicDisplay)
    {
      try
      {
        if (DynamicMapDrawPhase != esriDynamicMapDrawPhase.esriDMDPDynamicLayers)
          return;

        //make sure that the display is valid as well as that the layer is visible
        if (null == dynamicDisplay || null == Display)
          return;

        if (m_bOnce)
        {
          //get the device frame size
          m_deviceFrame = Display.DisplayTransformation.get_DeviceFrame();

          //start listening to display events
          ((ITransformEvents_Event)Display.DisplayTransformation).DeviceFrameUpdated += new ITransformEvents_DeviceFrameUpdatedEventHandler(OnDeviceFrameUpdated);

          CreateDisplayLists();

          m_bOnce = false;
        }

        GL.glPushMatrix();
        GL.glLoadIdentity();

        //draw the compass list
        GL.glPushMatrix();
        GL.glTranslatef((float)m_deviceFrame.left + 70.0f, (float)m_deviceFrame.top + 70.0f, 0.0f);
        GL.glScalef(90.0f, 90.0f, 0.0f);
        GL.glRotatef((float)Display.DisplayTransformation.Rotation, 0.0f, 0.0f, 1.0f);
        GL.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
        GL.glCallList(m_compassList);
        GL.glPopMatrix();

        GL.glPopMatrix();
      }
      catch (Exception ex)
      {
        System.Windows.Forms.MessageBox.Show(ex.Message, "ERROR", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error);
      }
    }

    /// <summary>
    /// create the display list for the compass symbol
    /// </summary>
    private void CreateDisplayLists()
    {
      //create the display list for the animated icons
      //the quad size is set to 1 unit. Therefore you will have to scale it 
      //each time before drawing.

      //open the compass bitmap
      Bitmap compassBitmap = new Bitmap(GetType(), "compass.gif");

      //create the texture for the bitmap
      uint textureId = CreateTexture(compassBitmap);

      m_compassList = GL.glGenLists(1);
      GL.glNewList(m_compassList, GL.GL_COMPILE);
      GL.glPushMatrix();
        //shift the item 1/2 unit to the middle so that it'll get drawn around the center
        GL.glTranslatef(-0.5f, -0.5f, 0.0f);
        //enable texture in order to allow for texture binding
        GL.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, (int)GL.GL_MODULATE);

        //bind the texture

        GL.glEnable(GL.GL_TEXTURE_2D);
        GL.glBindTexture(GL.GL_TEXTURE_2D, (uint)textureId);

        //create the geometry (quad) and specify the texture coordinates
        GL.glBegin (GL.GL_QUADS);
          GL.glTexCoord2f (0.0f, 0.0f); GL.glVertex2f (0.0f, 0.0f);
          GL.glTexCoord2f (1.0f, 0.0f); GL.glVertex2f (1.0f, 0.0f);
          GL.glTexCoord2f (1.0f, 1.0f); GL.glVertex2f (1.0f, 1.0f);
          GL.glTexCoord2f (0.0f, 1.0f); GL.glVertex2f (0.0f, 1.0f);
        GL.glEnd ();

      GL.glPopMatrix();
      GL.glEndList();
    }

    /// <summary>
    /// Given a bitmap (GDI+), create for it an OpenGL texture and return its ID
    /// </summary>
    /// <param name="bitmap"></param>
    /// <returns>the OGL texture id</returns>
    /// <remarks>in order to allow hardware acceleration, texture size must be power of two.</remarks>
    private uint CreateTexture(Bitmap bitmap)
    {
      try
      {
        //get the bitmap's dimensions
        int h = bitmap.Height;
        int w = bitmap.Width;
        int s = Math.Max(h, w);

        //calculate the closest power of two to match the bitmap's size
        //(thank god for high-school math...)
        double x = Math.Log(Convert.ToDouble(s)) / Math.Log(2.0);
        s = Convert.ToInt32(Math.Pow(2.0, Convert.ToDouble(Math.Ceiling(x))));

        int bufferSizeInPixels = s * s;

        //get the bitmap's raw data 
        Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
        bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);
        System.Drawing.Imaging.BitmapData bitmapData;
        bitmapData = bitmap.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);

        

        byte[] bgrBuffer = new byte[bufferSizeInPixels * 3];

        //scale the bitmap to be a power of two
        unsafe
        {
          fixed (byte* pBgrBuffer = bgrBuffer)
          {
            GLU.gluScaleImage(GL.GL_BGR_EXT, bitmap.Size.Width, bitmap.Size.Height, GL.GL_UNSIGNED_BYTE, bitmapData.Scan0.ToPointer(), s, s, GL.GL_UNSIGNED_BYTE, pBgrBuffer);
          }
        }

        //create a new buffer to store the raw data and set the transparency color (alpha = 0)
        byte[] bgraBuffer = new byte[bufferSizeInPixels * 4];

        int posBgr = 0;
        int posBgra = 0;
        for (int i = 0; i < bufferSizeInPixels; i++)
        {
          bgraBuffer[posBgra] = bgrBuffer[posBgr];          //B
          bgraBuffer[posBgra + 1] = bgrBuffer[posBgr + 1];  //G
          bgraBuffer[posBgra + 2] = bgrBuffer[posBgr + 2];  //R

          //take care of the alpha
          if (255 == bgrBuffer[posBgr] && 255 == bgrBuffer[posBgr + 1] && 255 == bgrBuffer[posBgr + 2])
          {
            bgraBuffer[posBgra + 3] = 0;
          }
          else
          {
            bgraBuffer[posBgra + 3] = 255;
          }
          posBgr += 3;
          posBgra += 4;
        }

        //create the texture
        uint[] texture = new uint[1];
        GL.glEnable(GL.GL_TEXTURE_2D);
        GL.glGenTextures(1, texture);
        GL.glBindTexture(GL.GL_TEXTURE_2D, texture[0]);

        //set the texture parameters
        GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, (int)GL.GL_LINEAR);
        GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, (int)GL.GL_LINEAR);
        GL.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, (int)GL.GL_MODULATE);

        unsafe
        {
          fixed (byte* pBgraBuffer = bgraBuffer)
          {
            GL.glTexImage2D(GL.GL_TEXTURE_2D, 0, (int)GL.GL_RGBA, s, s, 0, GL.GL_BGRA_EXT, GL.GL_UNSIGNED_BYTE, pBgraBuffer);
          }
        }

        //unlock the bitmap from memory
        bitmap.UnlockBits(bitmapData);

        //return the newly created texture id
        return texture[0];

      }
      catch (Exception ex)
      {
        System.Diagnostics.Trace.WriteLine(ex.Message);
      }
      return (uint)0;
    }
    #endregion
  }
}