Part B - Direct3D

Heads Up Display

Introduce a HUD that superimposes text on the screen
Keep track of and report the average frame rate and the current camera stats


Sample | Framework | Design | Coordinator | HUD | Text | Utilities | Exercise



Nearly every digital game reports some aspects of its own state.  Reports may include images or text strings or combinations of both.  A common way to report game data is through a heads up display (HUD).  The term follows from aircraft technology where pilots need to view data while focusing on their look at points.  In digital games, a HUD is a sprite-like image superimposed on the current view of the scene.  This image, which is sufficiently translucent to keep the scene behind itself visible, may contain textual data.  This data typically includes frame rate and viewpoint position and heading as well as other state information. 

This chapter introduces a HUD into the framework.  The user determines the HUD's visibility.  Text strings created throughout the framework appear only when their parent HUD is visible. 


Heads Up Display Sample

The Heads Up Display Sample adds a HUD with text to the Texturing Sample.  The user can toggle the HUD's visibility by pressing the 'Toggle Heads Up Display' key and can move the HUD around using of the four positioning keys. 

heads up display sample

The positining keys default to 'VBNM' for left, right, up, and down translation.  The text in this HUD reports:

  • the current frame rate
  • the position and heading of the current camera
  • the orientation of the x and y local axes of the left box
  • the orientation of the z local axis of the right box
  • the on/off state of the point light
  • the on/off state of the spot light
  • the on/off state of the directional light

The HUD display on a translucent backgorund that identifies the area covered by the HUD. 


Framework

Upgrades to two components accommodate the HUD with its text strings:

  • Design - the Design object creates the HUD and the reporting text strings.
  • Coordinator - calculates the frame rate, reports it, and manages all HUD and Text objects created throughout the framework

Two further components are introduced with this sample:

  • HUD - HUD objects serve as containers for sets of Text objects.
  • Text - Text objects and APIText objects hold information for the text strings displayed within each HUD.

HUD map

Topics

The topics raised in this sample are:

  • frame rate calculations
  • positioning of text
  • drawing text

Frame Rate Calculations

The intervals at which frames are redrawn vary from frame to frame.  To account for this variation, we calculate the frame rate over a sample period (for example, one second).  The number of frames drawn over the sample period gives the average frame rate, which we report in frames per second. 

Frame rate calculations assume that there has been no interruptions to the execution of the application.  Upon restoration of focus or reconfiguration, frame counting restarts from zero and ignores any frames accumulated immediately preceding the interruption. 

Positioning of Text

The text strings translate with their parent HUD and scale with changes in resolution.  To accommodate such variations, we enclose each text string in its own bounding rectangle and describe that rectangle in relative coordinates with respect to the position and size of the parent HUD.  The reference coordinate system for the HUD has its top-left corner as the origin and its bottom-right corner at location [1, 1].  We define the bounding rectangle for each text string with respect to this coordinate system. 

The Text Rectangle

Drawing Text

Each character in a text string is stored in its Unicode representation, using the wchar_t type, which limits the framework to Unicode representations of no more than 16-bits.  Drawing a character involves the conversion of the Unicode code point for the character into a glyph. 

A glyph is the pictorial representation of a Unicode code point.  There are many ways of converting a character into a glyph.  We use fonts and font-families to describe this conversion. 

Direct3D Implementation

Direct3D provides support for the drawing glyphs through the D3DXCreateFont() function.  This function returns an interface to the D3DXFont COM object.  The D3DXCreateFont() function takes the following arguments:

  • IDirect3DDevice9* - the interface to the display device
  • INT - the height of the characters in logical units
  • UINT - the width of the characters in logical units
  • UNIT - typeface weight (e.g. bold, normal)
  • UNIT - number of mipmap levels
  • BOOL - italic font (true, false)
  • DWORD - the character set of the font
  • DWORD - matching criteria to actual fonts
  • DWORD - raster font matching
  • DWORD - pitch and family index
  • LPCTSTR - type face name for creating the glyphs
  • LPD3DXFONT* - address of the pointer that will point to the interface

The DrawText() method on the ID3DXFont interface draws formatted Unicode strings.  This method takes the following arguments:

  • LPD3DXSPRITE - the address of the ID3DXSPRITE interface to the sprite object that manages the drawing of sprites
  • LPCSTR - the address of the string of characters to be drawn
  • INT - the number of characters to draw (-1 if string is null-terminated)
  • LPRECT - the address of the rectangle within which the string is to be drawn
  • DWORD - alignment
  • D3DCOLOR - the color of the text in DWORD ARGB format

Creating sprites to draw text is computationally intensive.  For improved efficiency, the first argument to the DrawText() method should be the address of the D3DXSPRITE object that manages the drawing of all text items within a HUD. 

Direct3D brackets the drawing of text with the Begin(), End() pair of methods on the D3DXSPRITE sprite object that manages that drawing.  The Begin() method accepts flags that specify drawing parameters.  For alpha-blending with the background, we pass the D3DXSPRITE_ALPHABLEND flag.

Direct3D does not require recreation of an interface to the D3DXFont COM object everytime an application regains focus.  The OnLostDevice() method on the ID3DXFont interface releases references to video memory before the display device is lost, while the OnResetDevice() method re-acquires the resources when the application regains focus. 

Settings

Translation Layer

The Translation Layer settings define

  • the enumeration constants for
    • HUD display
    • HUD translation
  • the macros for
    • HUD display description
    • HUD translation descriptions
    • default action-key mappings
    • text alignment
    • text colour
 // Translation.h
 // ...
 typedef enum Action {
     // ...
     HUD_SELECT,
     HUD_LEFT,
     HUD_RIGHT,
     HUD_UP,
     HUD_DOWN,
 } Action;

 #define ACTION_DESCRIPTIONS {\
     // ...
     L"Toggle Heads Up Display",      \
     L"Heads Up Display - Move Left", \
     L"Heads Up Display - Move Right",\
     L"Heads Up Display - Move Up",   \
     L"Heads Up Display - Move Down",  \
 }

 #define ACTION_MAPPINGS {\
     // ...
     KEY_F, KEY_V, \
     KEY_B, KEY_N, KEY_M, \
 }
 // ...
 // Font description flags
 #define TEXT_LEFT          1
 #define TEXT_RIGHT         2
 #define TEXT_CENTER        4
 #define TEXT_TOP           8
 #define TEXT_BOTTOM       16
 #define TEXT_MIDDLE       32
 #define TEXT_NORMAL       64
 #define TEXT_MEDIUM      128
 #define TEXT_SEMIBOLD    256
 #define TEXT_BOLD        512
 #define TEXT_TRUETYPE   1024
 #define TEXT_RASTER     2048
 #define TEXT_ANTIALIAS  4096
 #define TEXT_PROOF      8192
 #define TEXT_CLEARTYPE 16384
 #define TEXT_EXTRA     32768
 #define TEXT_DEFAULT TEXT_LEFT | TEXT_TOP | TEXT_NORMAL
 #define TEXT_HEIGHT       15
 #define TEXT_TYPEFACE L"ARIAL"
 // font colour [0,255]
 #define TEXT_R 0
 #define TEXT_G 0
 #define TEXT_B 0
 #define TEXT_A 255
 #define TEXT_COLOUR  (TEXT_A << 24 | TEXT_R << 16 | TEXT_G << 8 | TEXT_B)
 // ...

Model Layer

The Model Layer settings define

  • the name of the file that holds the HUD background image
  • the margins of the client area in relative screen coordinates within which the HUD can translate
  • the translation speed of the HUD
  • the initial position and size of the HUD in relative screen coordinates
  • the translucency of the HUD texture
  • the characters that symbolize on and off states
 // Model.h
 // ...
 // hud and text parameters
 //
 // hud background image
 #define HUD_IMAGE L"hudBackground.bmp"
 // relative limits on the size of the hud [0,1]
 #define HUD_MAX    0.99f
 #define HUD_MIN    0.01f
 #define HUD_SPEED  (0.5f /* screen units per sec */ / UNITS_PER_SEC)
 // hud location within window [0,1]
 #define HUD_W     0.43f
 #define HUD_H     0.43f
 // text limits within the hud [0,1]
 #define TEXT_MAX   0.99f
 #define TEXT_MIN   0.01f

 // foreground texture transparency [\x00,\xff]
 #define TEXTURE_ALPHA '\x20'

 // Text status
 #define TEXT_ON  L" + " // symbol for on
 #define TEXT_OFF L" o " // symbol for off

Design

The Design component creates the HUD and its text items.  The component uses the CreateHUD() and CreateText() functions of the HUD and Text components for this purpose. 

The initialize() method on the Design object creates

  • one HUD with a background texture named HUD_IMAGE
  • eight Text objects for reporting
    • the frame rate
    • the current camera position and heading
    • the orientations of the local axes of the roller and spinner boxes
    • the on/off states of the three lights
 void Design::initialize() {
     // general display design
     //
     setAmbientLight(0.2f, 0.2f, 0.2f);
     iHUD* hud = CreateHUD(CreateGraphic(), 0.1f, 0.1f, 0.43f, 0.43f,
      CreateTexture(HUD_IMAGE));
     setTimerText(CreateText(Rectf(0.0f, 0.05f, 0.2f, 0.15f), hud, L"",
      TEXT_HEIGHT, TEXT_TYPEFACE, TEXT_LEFT));
     // ...
     // cameras --------------------------------------------------------------

     // camera at a distance - in lhs coordinates
     iCamera* camera = CreateCamera();
     camera->translate(-5, 0, -80);
     // second camera attached to the box
     iCamera* objectCamera = CreateCamera();
     CreateText(Rectf(0, 0.05f, 0.65f, 0.15f), hud, L" Camera: at ",
      position, Camera::getCurrent(), ' ', 1, 16, L"ARIAL", TEXT_CENTER);
     CreateText(Rectf(0.50f, 0.05f, 0.90f, 0.15f), hud, L" heading ",
      orient, Camera::getCurrent(), 'z', 100,  16, L"ARIAL", TEXT_CENTER);
     // ...

     // Heads Up Display Text ------------------------------------------------

     // object data
     CreateText(Rectf(0, 0.30f, 0.7f, 0.42f), hud, L" Roller x orientation ",
      orient, rollLeft, 'x', 100);
     CreateText(Rectf(0, 0.42f, 0.7f, 0.54f), hud, L" Roller y orientation ",
      orient, rollLeft, 'y', 100);
     CreateText(Rectf(0, 0.54f, 0.7f, 0.66f), hud, L" Spinner z orientation ",
      orient, spinRight, 'z', 100);

     // lighting data
     CreateText(Rectf(0, 0.66f, 0.5f, 0.74f), hud, L"Point light ", onOff,
      pointLight);
     CreateText(Rectf(0, 0.74f, 0.5f, 0.82f), hud, L"Spot light ", onOff,
      spotLight);
     CreateText(Rectf(0, 0.82f, 0.5f, 0.90f), hud, L"Directional light ",
      onOff, distantLight);
 }

The CreateHUD() function accepts the supporting graphic, the bounding rectangle, and the background texture.  The various CreateText() functions accept the bounding rectangle for the text string, the address of the parent HUD, the label to precede the text, the address of the function that calculates the reported value, the font family, and the text alignment.

The position(), orient(), and onOff() functions construct the text strings to be displayed (see the Utilities section below).


Coordinator

The Coordinator component calculates the frame rate, manages HUD visibility and positioning, and manages the housekeeping of HUD and Text objects across focus changes and reconfiguration. 

The iCoordinator interface exposes four virtual methods for adding and removing HUD and Text objects:

 // iCoordinator.h

 class iCoordinator {
   public:
     // ...
     virtual void add(const iText* t)    = 0;
     virtual void add(iHUD* h)           = 0;
     // ...
     virtual void remove(const iText* t) = 0;
     virtual void remove(iHUD* h)        = 0;
     // ...
 };
 iCoordinator* CoordinatorAddress();

The Coordinator class includes eight new instance variables:

  • text - a vector of pointers to Text objects
  • lastHUDToggle - the time of the last HUD visibility toggle
  • on - the visibility state of the HUD
  • hudPosX - x screen position in normalized screen coordinates of the top left corner of the HUD
  • hudPosY - y screen position in normalized screen coordinates of the top left corner of the HUD
  • lastReset - the time of the last reset of the frame rate counter
  • framecount - the number of frames since the last reset of the frame rate counter
  • fps - the average frame rate at the last reset of the frame rate counter
 // Coordinator.h

 class Coordinator : public iCoordinator {
     // ...
     std::vector<iText*> text;        // points to text items
     std::vector<iHUD*>  hud;         // points to huds
     // ...
     unsigned framecount;       // no of frames since 'lastReset'
     unsigned fps;              // frame rate per sec
     unsigned lastReset;        // last time framecount reset to 0
     unsigned currentHUD;       // index - current HUD
     unsigned lastHUDToggle;    // time of most recent toggle
     iText*   timerText;        // points to timer's text object
     // ...
   protected:
     void setTimerText(void* text) { timerText = (iText*)text; }
     // ...
   public:
     // ...
     void add(iText* t)    { ::add(text, t); }
     void add(iHUD* h)     { ::add(hud, h); }
     // ...
     void remove(iText* t) { ::remove(text, t); }
     void remove(iHUD* h)  { ::remove(hud, h); }
 };

Implementation

Construct

The constructor initializes the instance timers, the current HUD index, and the address of the timer text:

 Coordinator::Coordinator(void* hinst, int show) { 
     // ...
     // timers
     now              = 0;
     lastReset        = 0;
     lastUpdate       = 0;
     lastCameraToggle = 0;
     lastWFrameToggle = 0;
     lastHUDToggle    = 0;
     framecount       = 0;
     fps              = 0;

     // current state
     currentCam = 0;
     currentHUD = 0;
     wireFrame  = false;
     wBuffering = false;
     zBuffering = true;

     // pointers
     timerText  = nullptr;
     // ...
 }

Update

The update() method updates the frame count or calculates the average frame rate, and responds to the user's updating of the HUD's visibility or position:

 void Coordinator::update() {
     // ...
     // adjust framecount and fps
     if (now - lastReset <= UNITS_PER_SEC)
         framecount++;
     else {
         // recalculate the frame rate
         fps        = framecount * UNITS_PER_SEC / (now - lastReset);
         framecount = 0;
         lastReset  = now;
         if (timerText) {
             wchar_t str[MAX_DESC + 1];
             sprintf(str, fps, L" fps");
             timerText->set(str);
         }
     }
     // toggle and update the current hud
     if (hud.size() && userInput->pressed(HUD_SELECT) &&
         now - lastHUDToggle > KEY_LATENCY) {
         lastHUDToggle = now;
         currentHUD++;
         if (currentHUD == hud.size())
             currentHUD = 0;
     }
     if (hud.size() && hud[currentHUD] && userInput->pressed(HUD_SELECT))
         hud[currentHUD]->toggle();
     if (hud.size() && hud[currentHUD])
         hud[currentHUD]->update();
     // ...
 }

Render

The no-argument render() method renders objects that belong to the HUD_TEXT category after drawing the rest of the frame:

 void Coordinator::render() { 
     // ...
     // render the hud and text objects
     render(HUD_TEXT);
     // finished the graphics part
     display->endDrawFrame();
 }

The one-argument render() method draws the visible HUDs with their text items:

 void Coordinator::render(Category category) {
     ViewFrustum viewFrustum(view * projection);

     switch (category) {
         case HUD_TEXT:
             // draw all huds
             for (unsigned i = 0; i < hud.size(); i++)
                 if (hud[i] && hud[i]->isOn()) {
                     hud[i]->beginDraw();
                     hud[i]->render();
                     for (unsigned j = 0; j < text.size(); j++)
                         if (text[j] && text[j]->getHUD() == hud[i])
                             text[j]->render();
                     hud[i]->endDraw();
                 }
             break;
         default:
             // draw objects that only belong to category
             for (unsigned i = 0; i < object.size(); i++)
                 if (object[i] && object[i]->belongsTo(category) &&
                  (category == SPRITE ||
                  viewFrustum.contains(object[i]->position(),
                  object[i]->radius())))
                     object[i]->render();
     }
 }

Suspend, Restore, and Release

The suspend() method suspends the connection of each Text object to the display device:

 void Coordinator::suspend() {
     // ...
     for (unsigned i = 0; i < text.size(); i++) 
         if (text[i])
             text[i]->suspend();
 }

The restore() method restores the connection of each HUD and Text object to the display device, resets the reference timers to the current time, and initializes the frame counter: 

 void Coordinator::restore() {
     // ...
     for (unsigned i = 0; i < hud.size(); i++)
         if (hud[i])
             hud[i]->restore();
     for (unsigned i = 0; i < text.size(); i++) 
         if (text[i])
             text[i]->restore();

     framecount       = 0;
     lastReset        = now;
     lastHUDToggle    = now;
     lastUpdate       = now;
     lastCameraToggle = now;
 }

The release() method releases the connection of each HUD and Text object to the Graphics API: 

 void Coordinator::release() {
     // ...
     for (unsigned i = 0; i < text.size(); i++) 
         if (text[i])
             text[i]->release();
     // ...
 }

Destroy

The destructor deletes all of the HUD and Text objects:

 Coordinator::~Coordinator() {
     // ...
     for (unsigned i = 0; i < text.size(); i++) 
         if (text[i])
             text[i]->Delete();
     for (unsigned i = 0; i < hud.size(); i++)
         if (hud[i])
             hud[i]->Delete();
     // ...
 }

HUD

The HUD component manages the HUD objects within the framework, including their positioning, and their notifications to their sprite managers of the start and end of each drawing cycle.  The HUD class handles all of these functions. 

The iHUD interface exposes eight virtual methods:

 // iHUD.h

 class iHUD : public iSwitch, public Base {
     virtual bool toggle()                = 0;
     virtual void update()                = 0;
     virtual const Rectf& getRect() const = 0;
     virtual void beginDraw()             = 0;
     virtual void render()                = 0;
     virtual void endDraw()               = 0;
     virtual void restore()               = 0;
     virtual void Delete() const          = 0;
     friend class Coordinator;
     friend class Design;
     friend class Text;
 };
 iHUD* CreateHUD(iGraphic*, float, float, float, float, iTexture* = 0); 

The HUD class defines five instance variables:

 class HUD : public iHUD {
     bool      on;         // is the HUD being displayed?
     Rectf*    rect;       // bounds [HUD_MIN, HUD_MIN, HUD_MAX, HUD_MAX]
     iTexture* texture;    // points to the HUD texture
     iGraphic* graphic;    // points to the sprite manager
     unsigned  lastToggle; // time of the last toggle
     void      validate(); // validates HUD size & position
     virtual ~HUD();

 public:
     HUD(iGraphic*, float, float, float, float, iTexture*);
     HUD(const HUD&);
     HUD& operator=(const HUD&);
     void* clone() const          { return new HUD(*this); }
     bool isOn() const            { return on; }
     const Rectf& getRect() const { return *rect; }
     bool toggle();
     void update();
     void beginDraw();
     void render();
     void endDraw();
     void restore();
     void Delete() const { delete this; }
 };

Implementation

Construct

The constructor initializes the position, width, and height of the HUD within the bounds imposed by the framework:

 HUD::HUD(iGraphic* g, float x, float y, float w, float h, iTexture* t) :
  graphic(g), texture(t), on(false) {
     coordinator->add(this);

     w = w > HUD_MAX - HUD_MIN ? HUD_MAX - HUD_MIN :
      w < HUD_MIN ? HUD_MIN : w;
     h = h > HUD_MAX - HUD_MIN ? HUD_MAX - HUD_MIN :
      h < HUD_MIN ? HUD_MIN : h;
     rect = new Rectf(x, y, x + w, y + h);
     validate();
     lastToggle = 0;
 }

Validate

The validate() method checks the coordinates of the HUD and adjusts them to keep the HUD wholly within the screen:

 void HUD::validate() {
     float width  = rect->bottomRightX - rect->topLeftX;
     float height = rect->bottomRightY - rect->topLeftY;

     if (rect->topLeftX < HUD_MIN) {
         rect->topLeftX = HUD_MIN;
         rect->bottomRightX = HUD_MIN + width;
     }
     else if (rect->bottomRightX > HUD_MAX) {
         rect->bottomRightX = HUD_MAX;
         rect->topLeftX = HUD_MAX - width;
     }
     if (rect->topLeftY < HUD_MIN) {
         rect->topLeftY = HUD_MIN;
         rect->bottomRightY = HUD_MIN + height;
     }
     else if (rect->bottomRightY > HUD_MAX) {
         rect->bottomRightY = HUD_MAX;
         rect->topLeftY = HUD_MAX - height;
     }
 }

Update

The update() method responds to user's actions to change the HUD's position, updating its position and validating the update:

 void HUD::update() {
     // update the HUD only if it is being displayed
     if (on) {
         int delta = now - lastUpdate;
         int dx = 0, dy = 0;
         if (coordinator->pressed(HUD_RIGHT)) dx += delta;
         if (coordinator->pressed(HUD_LEFT))  dx -= delta;
         if (coordinator->pressed(HUD_UP))    dy -= delta;
         if (coordinator->pressed(HUD_DOWN))  dy += delta;
         rect->topLeftX     += dx * HUD_SPEED;
         rect->topLeftY     += dy * HUD_SPEED;
         rect->bottomRightX += dx * HUD_SPEED;
         rect->bottomRightY += dy * HUD_SPEED;
         validate();
     }
 }

Toggle

The toggle() method toggles between on and off states if sufficient time has elapsed since the last toggle:

 bool HUD::toggle() {
     if (now - lastToggle > KEY_LATENCY) {
         lastToggle = now;
         on         = !on;
     }
     return on;
 }

Begin Draw

The beginDraw() method notifies the sprite manager that drawing is about to begin:

 void HUD::beginDraw() { graphic->beginDraw(); }

Render

The render() method attaches the background texture for the HUD, renders it, and detaches it:

 void HUD::render() {
     if (texture) {
         float w = (rect->bottomRightX - rect->topLeftX) * width;
         float h = (rect->bottomRightY - rect->topLeftY) * height;
         texture->attach((int)w, (int)h);
         graphic->render((int)(rect->topLeftX * width),
          (int)(rect->topLeftY * height), TEXTURE_ALPHA);
         texture->detach();
     }
 }

End Draw

The endDraw() method notifies the sprite manager that the drawing has finished:

 void HUD::endDraw() { graphic->endDraw(); }

Restore

The restore() method resets the time of the last toggle:

void HUD::restore() { lastToggle = now; }

Destroy

The destructor deletes the dynamic memory allocated for the bounding rectangle and removes the HUD from the coordinator's list:

 HUD::~HUD() {
     if (rect) delete [] rect;
     coordinator->remove(this);
 }

Text

The Text component handles all of the text reported by the framework.  The component consists of the Text class, which interfaces with the Design object and the APIText class, which communicates with the Graphics API. 

Text Component

The APIText objects access the graphics hardware through two interfaces:

  • ID3DXFont - to the Direct3D Font COM object
  • ID3DXSprite - to the Direct3D sprite COM object

A Text object receives its data in the form of a C-style null-terminated wide-character string, along with the address of its parent HUD, a description of its bounding rectangle within the HUD, and a description of its alignment within that bounding rectangle.  The bounding rectangle identifies the text string's position relative to the top-left corner of its parent HUD. 

Text Class

The iText interface exposes nine virtual methods:

 // iText.h

 class iText : public Base {
     virtual void set(const wchar_t* text)  = 0;
     virtual const wchar_t* text() const    = 0;
     virtual iHUD* getHUD() const           = 0;
     virtual void render()                  = 0;
     virtual void suspend()                 = 0;
     virtual void restore()                 = 0;
     virtual void release()                 = 0;
     virtual void Delete() const            = 0;
     friend class Coordinator;
     friend class Design;
   public:
     virtual iText* clone() const           = 0;
 };

 iText* CreateText(Rectf, void* = 0, const wchar_t* = 0, int = 0,
  const wchar_t* = 0, unsigned = 0, unsigned = 0);
 iText* CreateText(Rectf, void*, const wchar_t*,
  const wchar_t* (*)(wchar_t*, const Frame*, char, unsigned),
  Frame*, char, unsigned = 1, int = 0, const wchar_t* = 0,
  unsigned = 0, unsigned = 0);
 iText* CreateText(Rectf, void*, const wchar_t*,
  const wchar_t* (*)(wchar_t*, const Frame*, char, unsigned),
  Frame**, char, unsigned = 1, int = 0, const wchar_t* = 0,
  unsigned = 0, unsigned = 0);
 iText* CreateText(Rectf, void*, const wchar_t*,
  const wchar_t* (*)(wchar_t*, const iSwitch*), iSwitch*, int = 0,
  const wchar_t* = 0, unsigned = 0, unsigned = 0);

 iText* Clone(const iText*);

 const wchar_t* orient(wchar_t*, const Frame*, char, unsigned = 1u);
 const wchar_t* position(wchar_t*, const Frame*, char = ' ', unsigned = 1u);
 const wchar_t* onOff(wchar_t*, const iSwitch*);

The Text class instance variables for the include

  • apiText - the address of the APIText instance that interface with the Graphics API
  • str - the address of the C-style null-terminated wide-character string that holds the text for the object
  • rect - the address of the RelRect object that holds the bounding rectangle for the text string
  • dFlags - the flags that describe the formatting for displaying the text string
 // Text.h

 class Text : public iText {
     iAPIText* apiText;             // points to the Translation Layer
     wchar_t   label[MAX_DESC + 1]; // the label before the text string
     iHUD*     hud;                 // points to the parent HUD
     Rectf*    rect;                // relative rectangle within the HUD
     Frame*    frame;
     Frame**   pFrame;
     iSwitch*  swtch;
     char      axis;
     unsigned  factor;
     const wchar_t* (*intToWCStr)(wchar_t*, const Frame*, char, unsigned);
     const wchar_t* (*boolToWCStr)(wchar_t*, const iSwitch*);
     Text(const Text& src);
     virtual ~Text();
     void init(Rectf&, const wchar_t*, const wchar_t*, int, unsigned,
      unsigned);

   public:
     Text(Rectf, void*, const wchar_t*, int = 0, const wchar_t* = 0,
      unsigned = 0, unsigned = 0);
     Text(Rectf, void*, const wchar_t*, const wchar_t* (*)(wchar_t*,
      const Frame*, char, unsigned), Frame*, char, unsigned = 1, int = 0,
      const wchar_t* = 0, unsigned = 0, unsigned = 0);
     Text(Rectf, void*, const wchar_t*, const wchar_t* (*)(wchar_t*,
      const Frame*, char, unsigned), Frame**, char, unsigned = 1, int = 0,
      const wchar_t* = 0, unsigned = 0, unsigned = 0);
     Text(Rectf, void*, const wchar_t*, const wchar_t* (*)(wchar_t*,
      const iSwitch*), iSwitch*, int = 0, const wchar_t* = 0, unsigned = 0,
      unsigned = 0);
     Text&   operator=(const Text&);
     iText*  clone() const         { return new Text(*this); }
     void set(const wchar_t*);
     iHUD* getHUD() const        { return hud; }
     const wchar_t* text() const { return label; }
     void render();
     void suspend();
     void restore();
     void release();
     void Delete() const { delete this; }
 };

The CreateText() functions create instances of a Text class on dynamic memory:

 iText* CreateText(Rectf r, void* h, const wchar_t* s, int j,
  const wchar_t* u, unsigned f, unsigned c) {
     return new Text(r, h, s, j, u, f, c);
 }

 iText* CreateText(Rectf r, void* h, const wchar_t* s,
  const wchar_t* (*v)(wchar_t*, const Frame*, char, unsigned),
  Frame* q, char a, unsigned x, int j, const wchar_t* u, unsigned f,
  unsigned c) {
     return new Text(r, h, s, v, q, a, x, j, u, f, c);
 }

 iText* CreateText(Rectf r, void* h, const wchar_t* s,
  const wchar_t* (*v)(wchar_t*, const Frame*, char, unsigned),
  Frame** q, char a, unsigned x, int j, const wchar_t* u, unsigned f,
  unsigned c) {
     return new Text(r, h, s, v, q, a, x, j, u, f, c);
 }

 iText* CreateText(Rectf r, void* h, const wchar_t* t,
  const wchar_t* (*v)(wchar_t*, const iSwitch*), iSwitch* s, int j,
  const wchar_t* u, unsigned f, unsigned c) {

      return new Text(r, h, t, v, s, j, u, f, c);
 }

  iText* CreateText(RelRect r, const wchar_t* str, unsigned f) {
      return new Text(r, str, f);
 }

Construct

The constructors initialize the instance variables to the received values and call the init() method to complete the construction:

 Text::Text(Rectf r, void* h, const wchar_t* text, int j,
  const wchar_t* type, unsigned flags, unsigned colour) :
  intToWCStr(nullptr), boolToWCStr(nullptr), swtch(nullptr),
  pFrame(nullptr), frame(nullptr), hud((iHUD*)h) {
     init(r, text, type, j, flags, colour);
 }

 Text::Text(Rectf r, void* h, const wchar_t* text,
  const wchar_t* (*fn)(wchar_t*, const Frame*, char, unsigned), Frame* q,
  char c, unsigned x, int j, const wchar_t* type, unsigned flags,
  unsigned colour) : frame(q), axis(c), factor(x), intToWCStr(fn),
  boolToWCStr(nullptr), swtch(nullptr), pFrame(nullptr), hud((iHUD*)h) {
     init(r, text, type, j, flags, colour);
 }

 Text::Text(Rectf r, void* h, const wchar_t* text,
  const wchar_t* (*fn)(wchar_t*, const Frame*, char, unsigned), Frame** q,
  char c, unsigned x, int j, const wchar_t* type, unsigned flags,
  unsigned colour) : frame(nullptr), axis(c), factor(x), intToWCStr(fn),
  boolToWCStr(nullptr), swtch(nullptr), pFrame(q), hud((iHUD*)h) {
     init(r, text, type, j, flags, colour);
 }

 Text::Text(Rectf r, void* h, const wchar_t* text,
  const wchar_t* (*fn)(wchar_t*, const iSwitch*), iSwitch* s, int j,
  const wchar_t* type, unsigned flags, unsigned colour) : swtch(s),
  axis(' '), factor(0), boolToWCStr(fn), frame(nullptr),
  intToWCStr(nullptr), hud((iHUD*)h) {
     init(r, text, type, j, flags, colour);
 }

Init

The init method adds the address of the Text object to the coordinator's list, creates the APIText object that communicates with the Graphics API, validates and stores the bounding rectangle in relative coordinates, and initializes the text string to that at the received address:

 void Text::init(Rectf& r, const wchar_t* text, const wchar_t* face,
  int height, unsigned flags, unsigned colour) {
     coordinator->add(this);
     apiText = CreateAPIText(face, height, flags, colour);

     if (r.topLeftX < TEXT_MIN) r.topLeftX = TEXT_MIN;
     else if (r.topLeftX > TEXT_MAX) r.topLeftX = TEXT_MAX;
     if (r.topLeftY < TEXT_MIN) r.topLeftY = TEXT_MIN;
     else if (r.topLeftY > TEXT_MAX) r.topLeftY = TEXT_MAX;
     if (r.bottomRightX < TEXT_MIN) r.bottomRightX = TEXT_MIN;
     else if (r.bottomRightX > TEXT_MAX) r.bottomRightX = TEXT_MAX;
     if (r.bottomRightY < TEXT_MIN) r.bottomRightY = TEXT_MIN;
     else if (r.bottomRightY > TEXT_MAX) r.bottomRightY = TEXT_MAX;
     rect  = new Rectf;
     *rect = r;
     if (text) set(text);
 }

Note that the limits on the values of the bounding rectangle are hard-coded.

Set

The set() method stores a copy of the received string in the label instance variable:

 void Text::set(const wchar_t* text) {
     strcpy(label, text, MAX_DESC);
 }

Render

The render() method completes the text strings by concatenating the value returned from the function pointed to and passes the text string to the Translation Layer for drawing:

 void Text::render() {
     wchar_t text[MAX_DESC + 1];

     // value follows label
     if (intToWCStr && frame) {
         wchar_t value[MAX_DESC + 1];
         intToWCStr(value, frame, axis, factor);
         strcpy(text, label, MAX_DESC);
         strcat(text, value, MAX_DESC);
     }
     else if (intToWCStr && pFrame) {
         wchar_t value[MAX_DESC + 1];
         intToWCStr(value, *pFrame, axis, factor);
         strcpy(text, label, MAX_DESC);
         strcat(text, value, MAX_DESC);
     }
     // value precedes label
     else if (boolToWCStr && swtch) {
         boolToWCStr(text, swtch);
         strcat(text, label, MAX_DESC);
     }
     else
         strcpy(text, label, MAX_DESC);

     // retrieves the current hud coordinates, if any
     Rectf hudRect = Rectf(0, 0, 1, 1);
     if (hud)
         hudRect = hud->getRect();
     float width  = hudRect.bottomRightX - hudRect.topLeftX;
     float height = hudRect.bottomRightY - hudRect.topLeftY;
     Rectf textRect(
      hudRect.topLeftX + width * rect->topLeftX,
      hudRect.topLeftY + height * rect->topLeftY,
      hudRect.topLeftX + width * rect->bottomRightX,
      hudRect.topLeftY + height * rect->bottomRightY);

     if (apiText)
         apiText->draw(textRect, text);
 }

Suspend, Restore, and Release

The suspend() method suspends the connection to the display device:

 void Text::suspend() { if (apiText) apiText->suspend(); }

The restore() method restores the connection to the display device:

 void Text::restore() { if (apiText) apiText->restore(); }

The release() method releases the interface to the Graphics API:

 void Text::release() { if (apiText) apiText->release(); }

Destroy

The destructor deletes the associated APIText object, deallocates the dynamic memory for the bounding rectangle, and removes the current object's address from the coordinator's list:

 Text::~Text() {
     if (apiText) apiText->Delete();
     if (rect) delete rect;
     coordinator->remove(this);
 }

Position

The position() function creates a C_style null-terminated wide-character string that holds the position of *frame in world space:

 const wchar_t* position(wchar_t* str, const Frame* frame, char c, unsigned f) { 
     if (frame) {
         Vector v = frame->position();
         sprintf(str, (int)(f * v.x), (int)(f * v.y), (int)(f * v.z));
     }
     return str;
 }

Orient

The orient() function creates a C-style null-terminated wide-character string that holds the orientation of the local axis c of the reference frame *frame in world space.  This method scales the components of the unit orientation vector by a factor f:

 const wchar_t* orient(wchar_t* str, const Frame* frame, char c, int f) { 
     if (frame) {
         Vector v = frame->orientation(c);
         sprintf(str, (int)(f * v.x), (int)(f * v.y), (int)(f * v.z));
     }
     return str;
 }

OnOff

The onOff() function creates a C-style null-terminated wide-character string that holds the on or off state of *item.  The macros define the symbols used to identify on and off states:

 const wchar_t* onOff(wchar_t* str, const iSwitch* item) {
     if (item)
         strcpy(str, item->isOn() ? TEXT_ON : TEXT_OFF, MAX_DESC);
     return str;
 }
 const wchar_t* orient(wchar_t* str, const Frame* frame, char c, int f) { 
     if (frame) {
         Vector v = frame->orientation(c);
         sprintf(str, (int)(f*v.x), (int)(f*v.y), (int)(f*v.z));
     }
     return str;
 }

APIText Class

The APIText class translates the received text into a sequence of glyphs on the display device.  The APIText class provides the necessary information to the D3DXFont COM object. 

The iAPIText interface exposes six virtual methods to the Text class:

  • clone() - clones the current object
  • draw() - draws the text received on the display device
  • suspend() - suspends the connection to the display device
  • restore() - restores the connection to the display device
  • release() - releases the interface to the Graphics API
  • Delete() - deletes the current object
 // iAPIText.h

 class iAPIText {
     virtual iAPIText* clone() const                 = 0;
     virtual void draw(const Rectf&, const wchar_t*) = 0;
     virtual void suspend()                          = 0;
     virtual bool restore()                          = 0;
     virtual void release()                          = 0;
     virtual void Delete() const                     = 0;
     friend class Text;
 };
 iAPIText* CreateAPIText(const wchar_t*, int, unsigned, unsigned);
 

The APIText class defines eight instance variables:

 // APIText.h

 class APIText : public iAPIText, public APIBase {
     ID3DXFont*     d3dfont;   // points to the ID3DXFont Interface
     unsigned       align;     // alignment flags
     unsigned       colour;    // font colour
     unsigned       weight;    // normal, medium, bold
     unsigned       precision; // true type, raster
     unsigned       quality;   // antialias, cleartype
     const wchar_t* typeFace;  // typeFace name of the font
     int            fontHght;  // height of the font
     virtual ~APIText();
     void    setup();

   public:
     APIText(const wchar_t*, int, unsigned, unsigned);
     APIText(const APIText& v);
     APIText& operator=(const APIText& v);
     iAPIText* clone() const { return new APIText(*this); }
     void draw(const Rectf& r, const wchar_t* text);
     void suspend();
     bool restore();
     void release();
     void Delete() const { delete this; }
 };

Construct

The constructor initializes the text drawing flags and the address of the interface to the D3DXFont COM object:

 APIText::APIText(const wchar_t* t, int h, unsigned f, unsigned c) :
  typeFace(t), fontHght(h) {
     d3dfont = nullptr;

     // define the text alignment flags
     if (!f) f = TEXT_DEFAULT;
     align = 0;
     if (f & TEXT_LEFT)           align |= DT_LEFT;
     else if (f & TEXT_RIGHT)     align |= DT_RIGHT;
     else if (f & TEXT_CENTER)    align |= DT_CENTER;
     if (f & TEXT_TOP)            align |= DT_TOP;
     else if (f & TEXT_BOTTOM)    align |= DT_BOTTOM;
     else if (f & TEXT_MIDDLE)    align |= DT_VCENTER;
     // define the font weight
     if (f & TEXT_NORMAL)         weight = FW_NORMAL;
     else if (f & TEXT_MEDIUM)    weight = FW_MEDIUM;
     else if (f & TEXT_SEMIBOLD)  weight = FW_SEMIBOLD;
     else if (f & TEXT_BOLD)      weight = FW_BOLD;
     else                         weight = FW_DONTCARE;
     // define the output precision
     if (f & TEXT_TRUETYPE)       precision = OUT_TT_PRECIS;
     else if (f & TEXT_RASTER)    precision = OUT_RASTER_PRECIS;
     else                         precision = OUT_DEFAULT_PRECIS;
     // define the output quality
     if (f & TEXT_ANTIALIAS)      quality = ANTIALIASED_QUALITY;
     else if (f & TEXT_PROOF)     quality = PROOF_QUALITY;
     else if (f & TEXT_CLEARTYPE) quality = CLEARTYPE_QUALITY;
     else                         quality = DEFAULT_QUALITY;
     // use defaults if height, typeface and colour undefined (0)
     if (!fontHght) fontHght = TEXT_HEIGHT;
     if (!typeFace) typeFace = TEXT_TYPEFACE;
     if (!c)             c   = TEXT_COLOUR;
     unsigned char alpha = (c & 0xFF000000) >> 24;
     unsigned char red   = (c & 0xFF0000) >> 16;
     unsigned char green = (c & 0xFF00) >> 8;
     unsigned char blue  = (c & 0xFF);
     colour = D3DCOLOR_ARGB(alpha, red, green, blue);
 }

Setup

The setup() method retrieves an interface to the D3DXFont COM object for the specified drawing properties:

 void APIText::setup() {
     // retrieve an API interface to the font object
     if (FAILED(D3DXCreateFont(d3dd, 15, 0, FW_NORMAL, 0, 0,
       DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY,
       FF_DONTCARE | DEFAULT_PITCH, L"ARIAL", &d3dfont)))
         error(L"APIText::10 Failed to retrieve the font interface");
 }

Draw

The draw() method sets up the APIText object for drawing if necessary, determines the drawing rectangle in screen coordinates, and draws the text string within that rectangle using the specified colour: 

 void APIText::draw(const Rectf& r, const wchar_t* text) {
     if (!d3dfont) setup();
     if (d3dfont && manager && text) {
         // creates the drawing rectangle in screen coordinates
         RECT rect;
         SetRect(&rect, (int)(width * r.topLeftX),
          (int)(height * r.topLeftY), (int)(width * r.bottomRightX),
          (int)(height * r.bottomRightY));
         // draws text within the drawing rectangle rect
         d3dfont->DrawText(manager, text, -1, &rect, align, colour);
     }
 }

Suspend, Restore, and Release

The suspend() method marks the connection between the D3DXFont object and the display device as detached:

 void APIText::suspend() { if (d3dfont) d3dfont->OnLostDevice(); }

The restore() method resets the connection between the D3DXFont COM object and the display device: 

 bool APIText::restore() {
     bool rc = false;
     if (d3dfont) rc = d3dfont->OnResetDevice() == D3D_OK;
     return rc;
 }

The release() method releases the interface to the D3DXFont COM object:

 void APIText::release() { 
     suspend();
     if (d3dfont) {
         d3dfont->Release();
         d3dfont = nullptr;
     }
 }

Destroy

The destructor releases the connection to the Graphics API:

 APIText::~APIText() { release(); }

Utilities

The Utilities module defines the sprintf() functions used by the position() and orient() functions in constructing text strings. 

The iUtilities interface includes the prototype for this function:

 // iUtilities.h
 // ...
 int sprintf(wchar_t* str, int a, int b, int c);
 

The sprintf() function receives the address of a C-style null-terminated wide-character string and three integer values and converts those values into a wide-character string at that same address where the values are separated by commas:

 int sprintf(wchar_t* str, int a, int b, int c) { 
     wchar_t s[11];

     strcpy(str, itowc(s, a), 10);
     strcat(str, L", ", 12);
     strcat(str, itowc(s, b), 22);
     strcat(str, L", ", 24);
     strcat(str, itowc(s, c), 34);
     return (int)strlen(str);
 }

The itowc() function receives the address of a C-style null-terminated wide-character string and an integer value and converts it into a wide-character string at the specified address:

 const wchar_t* itowc(wchar_t* s, int value) {
     wchar_t  r[12];
     wchar_t* rp = r;
     wchar_t* ss = s;

     if (!value) *ss++ = L'0';
     else {
         if (value < 0) {
             value = -value;
             *ss++ = L'-';
         }
         while (value) {
             *rp++ = value % 10 + L'0';
             value /= 10;
         }
         rp--;
         while (rp != r - 1)
             *ss++ = *rp--;
     }
     *ss = L'\0';
     return s;
 }

Exercises

  • Add a text string that reports the orientation of the local z axis of the *child box. Your upgrade will affect the Design class definition and the initialize() method.
  • Add a text string that reports the state of attachment or detachment for the *child box with respect to the parent box. 



Previous Reading  Previous: Visibility Next: Lights   Next Reading


  Designed by Chris Szalwinski   Copying From This Site   
Logo