Game Development Community

dev|Pro Game Development Curriculum

image rotation in GuiBitmapCtrl

by Orion Elenzil · 12/26/2007 (10:31 am) · 10 comments

This resource implements primitive rotating of the bitmaps in GuiBitmapCtrl.
GuiBitmapCtrl renders its bitmap as a textured quad,
and this resource simply rotates that quad.

Significant caveats:

* The clip-region of the control is unchanged,
which means portions of the bitmap can be clipped when rotated.

* The hit-region for mouse events etc is unchanged.

* Not implemented for repeating (tiled) bitmaps

* Rotation is not passed on to child GuiCtrls.

So essentially this is useful for things like say the hands of a clock
or a compass or a wheel of fortune or something,
where the bitmap can be more or less square,
and the bits which rotate fit inside a circle.

Three new script-exposed fields are added to GuiBitmapCtrl:
F32     rotRadians          is the radians by which the bitmaps will rotate.
                            default = 0.

Bool    rotCenterNormalized determines whether the point specific in the next field is "normalized" or not.
                            if normalized, then "0 0" = the top left and "1 1" = the bottom right.
                            if not, then the values are simple pixels.
                            default = true.

Point2F rotCenterLocal      is center of rotation, relative to the top-left of the control itself.
                            default = 0.5, 0.5.


The following is all the code changes required,
however a familiarity with TGE and C++ in general is assumed.

first the core:

dgl.h
change this declaration:
void dglDrawBitmapStretch(TextureObject* texObject,
                          const RectI&   in_rStretch,
                          const U32      in_flip = GFlip_None);
to this:
void dglDrawBitmapStretch(TextureObject* texObject,
                          const RectI&   in_rStretch,
                          const U32      in_flip = GFlip_None,
                          const F32      rotRadians = 0.0f,
                          const Point2F* rotCenter  = NULL);

and change this declaration:
void dglDrawBitmapStretchSR(TextureObject* texObject,
                            const RectI&   in_rStretch,
                            const RectI&   in_rSubRegion,
                            const U32      in_flip = GFlip_None);
to this:
void dglDrawBitmapStretchSR(TextureObject* texObject,
                            const RectI&   in_rStretch,
                            const RectI&   in_rSubRegion,
                            const U32      in_flip = GFlip_None,
                            const F32      rotRadians = 0.0f,
                            const Point2F* rotCenter  = NULL);


dgl.cc
change the entire function dglDrawBitmapStretchSR()
to this:
void dglDrawBitmapStretchSR(TextureObject* texture,
                            const RectI&   dstRect,
                            const RectI&   srcRect,
                            const U32      in_flip,
                            const F32      rotRadians,
                            const Point2F* rotCenter)
{
   AssertFatal(texture != NULL, "GSurface::drawBitmapStretchSR: NULL Handle");
   if(!dstRect.isValidRect())
      return;
   AssertFatal(srcRect.isValidRect() == true,
               "GSurface::drawBitmapStretchSR: routines assume normal rects");

   glDisable(GL_LIGHTING);

   glEnable(GL_TEXTURE_2D);
   glBindTexture(GL_TEXTURE_2D, texture->texGLName);
   glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

   glEnable(GL_BLEND);
   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

   F32 texLeft      = (F32)(srcRect.point.x)                    / (F32)(texture->texWidth );
   F32 texRight     = (F32)(srcRect.point.x + srcRect.extent.x) / (F32)(texture->texWidth );
   F32 texTop       = (F32)(srcRect.point.y)                    / (F32)(texture->texHeight);
   F32 texBottom    = (F32)(srcRect.point.y + srcRect.extent.y) / (F32)(texture->texHeight);
   F32 screenLeft   = (F32) dstRect.point.x;
   F32 screenRight  = (F32) dstRect.point.x + dstRect.extent.x;
   F32 screenTop    = (F32) dstRect.point.y;
   F32 screenBottom = (F32) dstRect.point.y + dstRect.extent.y;

   if(in_flip & GFlip_X)
   {
      F32 temp = texLeft;
      texLeft = texRight;
      texRight = temp;
   }
   if(in_flip & GFlip_Y)
   {
      F32 temp  = texTop;
      texTop    = texBottom;
      texBottom = temp;
   }
   
   glColor4ub(sg_bitmapModulation.red,
              sg_bitmapModulation.green,
              sg_bitmapModulation.blue,
              sg_bitmapModulation.alpha);


   if (rotCenter != NULL)
   {
      // rotation fun
      glMatrixMode  (GL_MODELVIEW);
      glPushMatrix  ();

      // transposed Z-rotation matrix around point.
      F32 cosTheta = mCos(rotRadians);
      F32 sinTheta = mSin(rotRadians);
      MatrixF mat;
      mat.setColumn(0, Point4F( cosTheta, sinTheta, 0.0f, rotCenter->x * (1.0f - cosTheta) - rotCenter->y * sinTheta));
      mat.setColumn(1, Point4F(-sinTheta, cosTheta, 0.0f, rotCenter->y * (1.0f - cosTheta) + rotCenter->x * sinTheta));
      mat.setColumn(2, Point4F(0.0f    ,  0.0f    , 1.0f, 0.0f                                                      ));
      mat.setColumn(3, Point4F(0.0f    ,  0.0f    , 0.0f, 1.0f                                                      ));
      glLoadMatrixf(mat);
   }

// sglTranslatef  ( BaseDriftX * m_SurfaceParallax, BaseDriftY * m_SurfaceParallax, 0.0f );

   glBegin(GL_TRIANGLE_FAN);
      glTexCoord2f(texLeft, texBottom);
      glVertex2f(screenLeft, screenBottom);
   
      glTexCoord2f(texRight, texBottom);
      glVertex2f(screenRight, screenBottom);
   
      glTexCoord2f(texRight, texTop);
      glVertex2f(screenRight, screenTop);

      glTexCoord2f(texLeft, texTop);
      glVertex2f(screenLeft, screenTop);
   glEnd();

   if (rotCenter != NULL)
   {
      // rotation fun
      glPopMatrix();
   }

   glDisable(GL_BLEND);
   glDisable(GL_TEXTURE_2D);
}

change the entire function dglDrawBitmapStretch()
to this:
void dglDrawBitmapStretch(TextureObject* texture, const RectI& dstRect, const U32 in_flip, const F32 rotRadians, const Point2F* rotCenter)
{
   AssertFatal(texture != NULL, "GSurface::drawBitmapStretch: NULL Handle");
   AssertFatal(dstRect.isValidRect() == true,
               "GSurface::drawBitmapStretch: routines assume normal rects");

   RectI subRegion(0, 0,
                   texture->bitmapWidth,
                   texture->bitmapHeight);
   dglDrawBitmapStretchSR(texture   ,
                          dstRect   ,
                          subRegion ,
                          in_flip   ,
                          rotRadians,
                          rotCenter);
}



now to utilize this stuff in GuiBitmapCtrl:

guiBitmapCtrl.h
right after this line: ColorI mModulationColor;
add these lines:
F32     mRotRadians;
   Point2F mRotCenterLocal;      // center of rotation, relative to the top-left of this control
   bool    mRotCenterNormalized; // whether or not the previous is in normalized coords. (ie, 0 to 1)

guiBitmapCtrl.cc
make the constructor for guiBitmapCtrl look like this:
GuiBitmapCtrl::GuiBitmapCtrl(void)
{
   mBitmapName          = StringTable->insert("");
   startPoint           = Point2I(0, 0);
   mWrap                = false;
   mModulationColor     = ColorI(255, 255, 255, 255);
   mRotRadians          = 0.0f;
   mRotCenterLocal      = Point2F(0.5f, 0.5f);
   mRotCenterNormalized = true;
}

make GuiBitmapCtrl::initPersistFields() look like this:
void GuiBitmapCtrl::initPersistFields()
{
   Parent::initPersistFields();
   addGroup("Misc");		// MM: Added Group Header.
   addField("bitmap"             , TypeFilename, Offset(mBitmapName         , GuiBitmapCtrl));
   addField("wrap"               , TypeBool    , Offset(mWrap               , GuiBitmapCtrl));
   addField("modulationColor"    , TypeColorI  , Offset(mModulationColor    , GuiBitmapCtrl));
   addField("rotRadians"         , TypeF32     , Offset(mRotRadians         , GuiBitmapCtrl));
   addField("rotCenter"          , TypePoint2F , Offset(mRotCenterLocal     , GuiBitmapCtrl));
   addField("rotCenterNormalized", TypeBool    , Offset(mRotCenterNormalized, GuiBitmapCtrl));
   endGroup("Misc");		// MM: Added Group Footer.
}

in GuiBitmapCtrl::onRender(),
replace this line: dglDrawBitmapStretch(mTextureHandle, rect);
with these lines:
if (mRotRadians > -0.001f && mRotRadians < 0.001f)
         {
            dglDrawBitmapStretch(mTextureHandle, rect, GFlip_None, 0.0f, NULL);
         }
         else
         {
            Point2F center = mRotCenterLocal;
            if (mRotCenterNormalized)
            {
               center.x *= (F32)rect.extent.x;
               center.y *= (F32)rect.extent.y;
            }
            center.x    += (F32)rect.point.x;
            center.y    += (F32)rect.point.y;

            dglDrawBitmapStretch(mTextureHandle, rect, GFlip_None, mRotRadians, &center);
         }


.. that's it !
please forward mistakes, etc, to me.

#1
12/26/2007 (3:41 pm)
you should rotate the whole quad and NOT just the texture coordinates
#2
12/26/2007 (4:51 pm)
hi anthony, thanks for looking at this.
didn't we already discuss that in this thread ?

as you can see from the source code and the write-up:
Quote:this resource simply rotates that quad.

ie, it does rotate the quad and not the texture coords.
the reason it gets clipped is, as is also mentioned in the write-up:
Quote:The clip-region of the control is unchanged, which means portions of the bitmap can be clipped when rotated.
#3
12/26/2007 (5:00 pm)
folks interested in this resource may also be interested in this much earlier one by Phil Carlisle which does something similar.

personally i prefer my implementation, as it lets you specify the center of rotation and is implemented in less code. I think Anthony thinks Phil's implementation gets around the clipping issue, but looking at the source code, i don't see how. I guess i should just try it. As mentioned in the write-up above, this here resource is perfectly good for bitmaps which are round in nature, such as a compass. I suspect the same is true w/ Phil's, since his actual use for it is a compass.
#4
12/27/2007 (3:33 pm)
Good work as always Orion!
#5
12/30/2007 (8:02 am)
phil's does rotate the whole quad, I 've used it for several years.
#6
12/31/2007 (2:01 pm)
Hi Anthony,
the issue here isn't rotating the whole quad or not; both resources do that.
the issue, as i've mentioned, is updating the current clipping rectangle to be large enough to accommodate the rotated quad.

i just tried integrating Phil's resource into my tge 1.3-based codebase,
but i think it's drifted too far to Just Work.

perhaps you're using this with like tge 1.1, which phil's seems to be based on,
and the clipping rectangle was introduced later ?

in 1.3, the clip rectangle is set by the Parent guiControl in GuiControl::renderChildControls():
...
         if (childClip.intersect(clipRect))
         {
            dglSetClipRect(childClip);
            glDisable(GL_CULL_FACE);
            ctrl->onRender(childPosition, childClip);
         }
...

i've always thought it was a bit backwards to have the parent setting the child's clip rect,
instead of letting the child do it itself.

if anyone's interested in solving this for the resource at hand,
the fix would i think be to add something in OnRender(), in the "if (rotCenter != NULL)" section,
which set the clipping rectangle to the parent control's clip rectangle.
#7
12/22/2009 (5:03 am)
I don't suppose anyone has done this for TGEA or T3D?
#8
10/29/2010 (1:28 am)
I'll be attempting to implement this into TGEA over the next couple days and will post back with my questions and/or success story. :)
#9
10/29/2010 (1:44 am)
sweet potato pie !
#10
11/04/2010 (7:40 pm)
Well, I got stumped on the very first step... TGEA doesn't have a dgl.cpp OR dgl.h.

I tried searching "Entire Solution" for: dglDrawBitmapStretch

The only file I have that uses that is in afxSpellButton.cpp (I have AFX 2).

So, I'm at a complete loss right from the very beginning... What's really sad is, all I want is to be able to dynamically rotate a little triangle through Torque Script (mini map marker...)