terminal.cpp

Go to the documentation of this file.
00001 /*
00002  * terminal.cpp
00003  *
00004  * Copyright (C) 2008-2010  Thomas A. Vaughan
00005  * All rights reserved.
00006  *
00007  *
00008  * Redistribution and use in source and binary forms, with or without
00009  * modification, are permitted provided that the following conditions are met:
00010  *     * Redistributions of source code must retain the above copyright
00011  *       notice, this list of conditions and the following disclaimer.
00012  *     * Redistributions in binary form must reproduce the above copyright
00013  *       notice, this list of conditions and the following disclaimer in the
00014  *       documentation and/or other materials provided with the distribution.
00015  *     * Neither the name of the <organization> nor the
00016  *       names of its contributors may be used to endorse or promote products
00017  *       derived from this software without specific prior written permission.
00018  *
00019  * THIS SOFTWARE IS PROVIDED BY THOMAS A. VAUGHAN ''AS IS'' AND ANY
00020  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00021  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
00022  * DISCLAIMED. IN NO EVENT SHALL THOMAS A. VAUGHAN BE LIABLE FOR ANY
00023  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
00024  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00025  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
00026  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
00027  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
00028  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00029  *
00030  *
00031  * Implementation of a aesop terminal.  Manages display in particular.
00032  */
00033 
00034 // includes --------------------------------------------------------------------
00035 #include "terminal.h"                   // always include our own header first
00036 
00037 #include <sstream>
00038 
00039 #include "datahash/datahash_text.h"
00040 #include "dialog-draw/dialog-draw.h"
00041 #include "gamepad-vgfx-element/gamepad-vgfx-element.h"
00042 #include "geometry/geometry_2d.h"
00043 #include "glut/glut.h"
00044 #include "glut/glut_2d.h"
00045 #include "glut/renderable.h"
00046 #include "glut-font/glut-font.h"
00047 #include "map-kdtree/map-kdtree.h"
00048 #include "model-loader/model-loader.h"
00049 #include "perf/perf.h"
00050 #include "vgfx-opengl-draw/vgfx-opengl-draw.h"
00051 
00052 
00053 namespace aesop {
00054 
00055 /// \ingroup glut_app
00056 /*@{*/
00057 
00058 
00059 // Terminal interface destructor
00060 TerminalHost::~TerminalHost(void) throw() { }
00061 Terminal::~Terminal(void) throw() { }
00062 
00063 
00064 /// The input mode determines how the player interacts with controls.
00065 /// In "normal" mode, the controls (game pad, mouse, keyboard) are used for
00066 /// motion and game action.
00067 /// In "cursor" mode, the controls are used to manipulate on-screen menus and
00068 /// dialogs.
00069 /// Typically the player is allowed to toggle between modes.
00070 enum eInputMode {
00071         eMode_Normal            = 1,            ///< normal gameplay
00072         eMode_Cursor            = 2,            ///< moving cursor
00073 
00074         // keep this last
00075         eMode_Invalid           = 0
00076 };
00077 
00078 
00079 typedef point2d_t<int>          pointi_t;
00080 typedef point2d_t<float>        pointf_t;
00081 
00082 
00083 static const float s_turnSpeed          = 2.0;  // radians per second
00084 
00085 
00086 ////////////////////////////////////////////////////////////////////////////////
00087 //
00088 //      static helper methods
00089 //
00090 ////////////////////////////////////////////////////////////////////////////////
00091 
00092 static float
00093 tweakMouseMotion
00094 (
00095 IN float dx
00096 )
00097 throw()
00098 {
00099         int ix = +1;
00100         if (dx < 0) {
00101                 dx = -dx;
00102                 ix = -1;
00103         }
00104         return 16.0 * ix * dx * dx;
00105 }
00106 
00107 
00108 
00109 static void
00110 clipCoord
00111 (
00112 IN float& x,
00113 IN int maxX
00114 )
00115 throw()
00116 {
00117         if (x < 0.0) {
00118                 x = 0.0;
00119         } else if (x > maxX) {
00120                 x = maxX;
00121         }
00122 }
00123 
00124 
00125 
00126 static float
00127 getRandomX
00128 (
00129 void
00130 )
00131 throw()
00132 {
00133         return rand() / (1.0 * RAND_MAX);
00134 }
00135 
00136 
00137 
00138 static float
00139 getPotRatio
00140 (
00141 IN const gamepad::pot_value_t& pv
00142 )
00143 throw()
00144 {
00145         int delta = pv.maxSeen - pv.minSeen;
00146         if (delta < 1)
00147                 return 0.5;
00148 
00149         return ((float) pv.value - pv.minSeen) / delta;
00150 }
00151 
00152 
00153 
00154 static float
00155 getJoyAxis
00156 (
00157 IN const gamepad::pot_value_t& rawValue
00158 )
00159 throw()
00160 {
00161         float dx = 2.0 * (getPotRatio(rawValue) - 0.5);
00162 
00163         // cut out the middle 20%
00164         int iSign = +1;
00165         if (dx < 0) {
00166                 dx = -dx;
00167                 iSign = -1;
00168         }
00169 
00170         const float cutoff = 0.2;
00171         dx -= cutoff;
00172         if (dx < 0) {
00173                 return 0;
00174         }
00175         const float inverted = 1.0 / (1.0 - cutoff);
00176         return iSign * dx * inverted;
00177 }
00178 
00179 
00180 ////////////////////////////////////////////////////////////////////////////////
00181 //
00182 //      TermImpl -- class that implements the Terminal interface
00183 //
00184 ////////////////////////////////////////////////////////////////////////////////
00185 
00186 class TermImpl : public Terminal {
00187 public:
00188         TermImpl(void) throw();
00189         ~TermImpl(void) throw() { DPRINTF("Destroying terminal"); }
00190 
00191         // public class methods ------------------------------------------------
00192         void initialize(IN TerminalHost * host,
00193                                 IN int playerId,
00194                                 IN smart_ptr<glut::RenderQueue>& rq);
00195 
00196         // aesop::Terminal class interface methods -----------------------------
00197         void setKeys(IN smart_ptr<crypto::DESKey>& desKey,
00198                                 IN smart_ptr<gamepad::Manager>& mgr);
00199         void input(IN gamepad::Gamepad * gp, IN float dt);
00200         void mouseMove(IN int x, IN int y);
00201         void mouseButton(IN int button, IN int state,
00202                                 IN int x, IN int y);
00203         void keyboard(IN int key, IN int mods);
00204         void showDialog(IN const char * id,
00205                                 IN const Datahash * dialog,
00206                                 IN dialog::Host * host);
00207         void destroyDialog(IN const char * id);
00208         void notifyMap(IN MapKdTree * mapKdTree);
00209         void notifyInstance(IN smart_ptr<Instance>& instance);
00210 
00211         // view::Host class interface methods ----------------------------------
00212         void render3D(IN int id, IN const view::render_info_t& ri);
00213         void render2D(IN int id, IN const view::render_info_t& ri);
00214 
00215 private:
00216         // private member data ------------------------------------------------
00217         smart_ptr<dialog::Manager>              m_dialogManager;
00218         smart_ptr<glut::RenderQueue>            m_rQueue;
00219         smart_ptr<vgfx::Drawer>                 m_vgfxDrawer;
00220         smart_ptr<glut::Renderable>             m_model;
00221         smart_ptr<Instance>                     m_instance;
00222         eInputMode                              m_mode;
00223         int                                     m_playerId;
00224         TerminalHost *                          m_host;
00225 //      int                                     m_oldButtons;
00226         pointf_t                                m_cursor;
00227         float                                   m_back[3];
00228         int                                     m_maxX;
00229         int                                     m_maxY;
00230         int                                     m_lastX;
00231         int                                     m_lastY;
00232         bool                                    m_newMap;
00233         map_draw_stats_t                        m_stats;
00234         MapKdTree *                             m_mapKdTree;
00235         glut::Viewer                            m_viewer;
00236         glut::fps_t                             m_fps;
00237 };
00238 
00239 
00240 
00241 TermImpl::TermImpl(void)
00242 throw()
00243 {
00244         DPRINTF("New terminal!");
00245 
00246         //m_oldButtons = 0;
00247         m_mode = eMode_Normal;
00248         m_maxX = m_maxY = 1;
00249         m_mapKdTree = NULL;
00250         m_newMap = false;
00251 
00252         m_lastX = 64;
00253         m_lastY = 64;
00254 
00255         // pick a background color
00256         srand(time(NULL));
00257         m_back[0] = 0.2 + 0.4 * getRandomX();
00258         m_back[1] = 0.2 + 0.4 * getRandomX();
00259         m_back[2] = 0.2 + 0.4 * getRandomX();
00260 }
00261 
00262 
00263 
00264 void
00265 TermImpl::initialize
00266 (
00267 IN TerminalHost * host,
00268 IN int playerId,
00269 IN smart_ptr<glut::RenderQueue>& rq
00270 )
00271 {
00272         ASSERT(host, "null");
00273         ASSERT(playerId > 0, "bad player id: %d", playerId);
00274         ASSERT(rq, "null");
00275 
00276         m_host = host;
00277         m_playerId = playerId;
00278         m_rQueue = rq;
00279 
00280         m_vgfxDrawer = vgfx::createOpenGLDrawer();
00281         ASSERT(m_vgfxDrawer, "null");
00282 
00283         smart_ptr<crypto::DESKey> key = NULL;
00284         smart_ptr<gamepad::Manager> mgr = NULL;
00285         this->setKeys(key, mgr);
00286 
00287         m_fps.init(perf::getNow());
00288 }
00289 
00290 
00291 
00292 ////////////////////////////////////////////////////////////////////////////////
00293 //
00294 //      TermImpl -- aesop::Terminal class interface methods
00295 //
00296 ////////////////////////////////////////////////////////////////////////////////
00297 
00298 void
00299 TermImpl::setKeys
00300 (
00301 IN smart_ptr<crypto::DESKey>& desKey,
00302 IN smart_ptr<gamepad::Manager>& gamepadMgr
00303 )
00304 {
00305         // ASSERT(desKey) -- can be null!
00306         // ASSERT(gamepadMgr) -- can be null!
00307         ASSERT(m_vgfxDrawer, "null");
00308 
00309         smart_ptr<dialog::Drawer> drawer = dialog::createOpenGLDrawer();
00310         ASSERT(drawer, "null");
00311 
00312         m_dialogManager = dialog::Manager::create(desKey, drawer);
00313         ASSERT(m_dialogManager, "failed to create dialog manager");
00314 
00315         // update with gamepad vgfx drawers
00316         if (gamepadMgr) {
00317                 gamepad::registerVgfxTestFactory(gamepadMgr, m_dialogManager,
00318                     m_vgfxDrawer);
00319                 gamepad::registerVgfxSimpleFactory(gamepadMgr, m_dialogManager,
00320                     m_vgfxDrawer);
00321         }
00322 }
00323 
00324 
00325 
00326 void
00327 TermImpl::input
00328 (
00329 IN gamepad::Gamepad * gp,
00330 IN float dt
00331 )
00332 {
00333         // ASSERT(gp) -- can be null!
00334         ASSERT(dt > 0.0, "Bad time increment: %f", dt);
00335 
00336         // if we have a gamepad, get the type
00337         gamepad::Type * type = (gp) ? gp->getType() : NULL;
00338         ASSERT(!gp || type, "gamepad but no type?");
00339 
00340         // toggle modes?
00341         int iLeftJoy = -1;
00342         int iRightJoy = -1;
00343         int iAction1 = -1;
00344         int iTrigger = -1;
00345         int iLeftJoyPush = -1;
00346         if (gp) {
00347                 int iStart2 = type->getLogicalIndex("start2",
00348                     gamepad::eInput_Button);
00349                 iLeftJoy = type->getLogicalIndex("leftJoy",
00350                     gamepad::eInput_Joystick);
00351                 iRightJoy = type->getLogicalIndex("rightJoy",
00352                     gamepad::eInput_Joystick);
00353                 iAction1 = type->getLogicalIndex("action1",
00354                     gamepad::eInput_Button);
00355                 iTrigger = type->getLogicalIndex("rightTrigger",
00356                     gamepad::eInput_Button);
00357                 iLeftJoyPush = type->getLogicalIndex("leftJoyPush",
00358                     gamepad::eInput_Button);
00359                 if (iTrigger < 0) {
00360                         DPRINTF("Failed to retrieve trigger as button, trying pot...");
00361 //                      iTrigger = type->getLogicalIndex("rightTrigger",
00362 //                          gamepad::eInput_Pot);
00363 //                      if (iTrigger < 0) {
00364                                 DPRINTF("Nope!");
00365 //                      }
00366                 }
00367                 if (iStart2 >= 0) {
00368                         if (gamepad::eButtonWasReleased & gp->getButton(iStart2)) {
00369                                 m_mode = (eMode_Normal == m_mode) ? eMode_Cursor : eMode_Normal;
00370                                 DPRINTF("Mode = %d", m_mode);
00371                         }
00372                 }
00373         }
00374 
00375         // exit cursor mode?
00376 //      if (eMode_Cursor == m_mode && input::eGameButton_Escape & released) {
00377 //              m_mode = eMode_Normal;
00378 //      }
00379 
00380         // new map?  get position now
00381         if (m_newMap) {
00382                 // new map!  update our position immediately
00383                 m_newMap = false;
00384                 point3d_t newPos;
00385                 point3d_t delta;
00386                 delta.clear();  // don't move at all
00387                 point3d_t euler;
00388                 euler.clear();  // hmmm--get this from start point?
00389                 m_host->requestMove(m_playerId, delta, euler, newPos);
00390                 m_viewer.setPosition(newPos);
00391         }
00392 
00393         // our requested move
00394         point3d_t delta;
00395         delta.clear();          // zero move by default
00396 
00397         // update cursor
00398         gamepad::joystick_t joyR, joyL;
00399         if (gp && eMode_Cursor == m_mode) {
00400                 gp->getJoystick(iRightJoy, joyR);
00401 
00402                 float dx = getJoyAxis(joyR.x);
00403                 float dy = getJoyAxis(joyR.y);
00404 
00405                 dx = tweakMouseMotion(dx);
00406                 dy = tweakMouseMotion(dy);
00407 
00408                 m_cursor.x += dx;
00409                 m_cursor.y += dy;
00410 
00411                 // clip
00412                 clipCoord(m_cursor.x, m_maxX);
00413                 clipCoord(m_cursor.y, m_maxY);
00414 
00415                 int x = (int) m_cursor.x;
00416                 int y = (int) m_cursor.y;
00417 
00418                 gamepad::eButton button = gp->getButton(iAction1);
00419 
00420                 if (gamepad::eButtonWasPushed & button) {
00421                         m_dialogManager->button(0, 0, x, y);
00422                 } else if (gamepad::eButtonWasReleased & button) {
00423                         m_dialogManager->button(0, 1, x, y);
00424                 } else if (gamepad::eButtonDown & button) {
00425                         m_dialogManager->cursor(x, y);
00426                 }
00427         } else if (gp) {
00428                 // not in cursor mode--update viewer
00429 
00430                 const char * verb = "idle";
00431 
00432                 gp->getJoystick(iRightJoy, joyR);
00433                 gp->getJoystick(iLeftJoy, joyL);
00434 
00435                 // first, rotations (right gamepad)
00436                 float dx = getJoyAxis(joyR.x);
00437                 float dy = getJoyAxis(joyR.y);
00438 
00439                 float rotY = -dx * dt * s_turnSpeed;
00440                 float rotUp = dy * dt * s_turnSpeed;
00441 
00442                 m_viewer.rotateY(rotY);
00443                 m_viewer.rotateUp(rotUp);
00444 
00445                 // next, motion (left stick on gamepad)
00446                 dx = getJoyAxis(joyL.x);
00447                 float dz = getJoyAxis(joyL.y);
00448                 dy = 0.0;
00449 
00450                 ASSERT(dx > -1.01 && dx < 1.01, "bad dx: %f", dx);
00451                 ASSERT(dz > -1.01 && dz < 1.01, "bad dz: %f", dz);
00452 
00453                 // is left joystick pressed?
00454                 float mult = 1.0;
00455                 if (iLeftJoyPush >= 0 &&
00456                     gamepad::eButtonDown & gp->getButton(iLeftJoyPush)) {
00457                         mult = 4.0;
00458                         verb = "run";
00459                 } else {
00460                         float r = (dx * dx) + (dz * dz);
00461                         if (r > 0.1) {
00462                                 verb = "walk";
00463                         }
00464                 }
00465 
00466                 // multiply by speed and time increment
00467                 const float maxSpeed = 4.5;     // 4.5m/s = 16.2 km/h = 10 mph
00468                 //const float maxSpeed = 6.0;   // 4.5m/s = 16.2 km/h = 10 mph
00469                 dx *= dt * mult * maxSpeed;
00470                 dz *= dt * mult * maxSpeed;
00471 
00472                 // update model with verb
00473                 if (m_model) {
00474                         m_model->setAnimation(verb);
00475                 }
00476 
00477                 // here we move the viewer to get an accurate requested position
00478                 // but the authoritative move happens with requestMove() below.
00479                 point3d_t oldPos = m_viewer.getPosition();
00480                 m_viewer.move(dz, dx, 0);       // forward, sideways, up
00481                 point3d_t newPos = m_viewer.getPosition();
00482                 delta = newPos - oldPos;
00483 
00484                 // shoot something?
00485                 if (iTrigger >= 0 &&
00486                     gamepad::eButtonWasReleased & gp->getButton(iTrigger)) {
00487 
00488                         event_t event;
00489                         event.device = eDevice_Gamepad;
00490                         event.event = eEvent_Press;
00491                         event.item = iTrigger;
00492 
00493                         m_host->notifyInput(m_playerId, event);
00494                 }
00495         }
00496 
00497         // determine Euler angles
00498         // TODO: have glut::viewer object use these natively!
00499         point3d_t euler(-m_viewer.getUpRotation(),
00500                         m_viewer.getYRotation(),
00501                         0.0);
00502 
00503         // always send move request, just to get new position
00504         point3d_t newPos;
00505         m_host->requestMove(m_playerId, delta, euler, newPos);
00506         m_viewer.setPosition(newPos);
00507 
00508         // save old state
00509 //      m_oldButtons = gp.buttons;
00510 }
00511 
00512 
00513 
00514 void
00515 TermImpl::mouseMove
00516 (
00517 IN int x,
00518 IN int y
00519 )
00520 {
00521         m_dialogManager->cursor(x, y);
00522 }
00523 
00524 
00525 
00526 void
00527 TermImpl::mouseButton
00528 (
00529 IN int button,
00530 IN int state,
00531 IN int x,
00532 IN int y
00533 )
00534 {
00535         if (state) {
00536                 m_lastX = x;
00537                 m_lastY = y;
00538         }
00539         m_dialogManager->button(button, state, x, y);
00540 }
00541 
00542 
00543 
00544 void
00545 TermImpl::keyboard
00546 (
00547 IN int key,
00548 IN int mods
00549 )
00550 {
00551         if (eMode_Cursor == m_mode) {
00552                 m_dialogManager->keyboard(key, mods);
00553         } else {
00554                 // use for motion?
00555         }
00556 }
00557 
00558 
00559 
00560 void
00561 TermImpl::showDialog
00562 (
00563 IN const char * id,
00564 IN const Datahash * dialog,
00565 IN dialog::Host * host
00566 )
00567 {
00568         ASSERT(id, "null");
00569         ASSERT(dialog, "null");
00570         ASSERT(host, "null");
00571 
00572         m_dialogManager->createDialog(id, m_lastX, m_lastY, dialog, host);
00573 }
00574 
00575 
00576 
00577 void
00578 TermImpl::destroyDialog
00579 (
00580 IN const char * id
00581 )
00582 {
00583         ASSERT(id, "null");
00584 
00585         m_dialogManager->destroyDialog(id);
00586 }
00587 
00588 
00589 
00590 void
00591 TermImpl::notifyMap
00592 (
00593 IN MapKdTree * mapKdTree
00594 )
00595 {
00596         // ASSERT(dyn) -- can be null! (not in map)
00597 
00598         if (m_mapKdTree && !mapKdTree) {
00599                 DPRINTF("LOSING MAP!");
00600         } else if (!m_mapKdTree && mapKdTree) {
00601                 DPRINTF("Gaining map!");
00602                 m_newMap = true;
00603         }
00604 
00605         m_mapKdTree = mapKdTree;
00606 }
00607 
00608 
00609 
00610 void
00611 TermImpl::notifyInstance
00612 (
00613 IN smart_ptr<Instance>& instance
00614 )
00615 {
00616         // ASSERT(instance) -- can be null!
00617 
00618         m_instance = instance;
00619         if (m_instance) {
00620                 m_model = getModel(instance);
00621         } else {
00622                 m_model = NULL;
00623         }
00624 }
00625 
00626 
00627 
00628 ////////////////////////////////////////////////////////////////////////////////
00629 //
00630 //      TermImpl -- view::Host class interface methods
00631 //
00632 ////////////////////////////////////////////////////////////////////////////////
00633 
00634 void
00635 TermImpl::render3D
00636 (
00637 IN int id,
00638 IN const view::render_info_t& ri
00639 )
00640 {
00641         perf::Timer timer("Terminal::render3D");
00642         ASSERT(id > 0, "Bad id: %d", id);
00643         ASSERT(ri.isValid(), "invalid render info?");
00644         ASSERT(m_rQueue, "null");
00645 
00646 //      DPRINTF("Rendering id=%d, %d x %d, aspect=%f",
00647 //          id, width, height, aspect);
00648 
00649         // create render context
00650         glut::render_context_t rc;
00651         rc.camera.setAspect(ri.width, ri.height);
00652 
00653         // create an actual viewer that is above position
00654         // (account for eyes being higher!)
00655         rc.viewer = m_viewer;
00656         point3d_t pos = rc.viewer.getPosition();
00657         pos.y += 0.5;
00658         rc.viewer.setPosition(pos);
00659 
00660         // render 3D scene as background
00661         if (m_mapKdTree) {
00662                 drawMap(m_mapKdTree, rc, m_rQueue, m_stats);
00663         }
00664 }
00665 
00666 
00667 
00668 void
00669 TermImpl::render2D
00670 (
00671 IN int id,
00672 IN const view::render_info_t& ri
00673 )
00674 {
00675         perf::Timer timer("Terminal::render2D");
00676         ASSERT(id > 0, "Bad id: %d", id);
00677 
00678         // update fps
00679         m_fps.tick(perf::getNow());
00680 
00681         // save most recently-seen dimensions
00682         m_maxX = ri.width;
00683         m_maxY = ri.height;
00684 
00685         glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
00686 
00687         // no map rendered?  clear out
00688         if (!m_mapKdTree) {
00689                 glColor3f(m_back[0], m_back[1], m_back[2]);
00690                 glRecti(0, 0, m_maxX, m_maxY);
00691         }
00692 //      glRecti(viewport.left, viewport.top, viewport.right, viewport.bottom);
00693 
00694         // display any dialogs
00695         glColor3f(1.0, 1.0, 1.0);
00696         m_dialogManager->display(m_maxX, m_maxY);
00697 
00698         // the cursor (if any)
00699         if (eMode_Cursor == m_mode) {
00700                 glut::drawCursor((int) m_cursor.x, (int) m_cursor.y);
00701         }
00702 
00703         // fps
00704         glColor3f(1.0, 1.0, 1.0);
00705         char buffer[128];
00706         sprintf(buffer, "%d fps", (int) (m_fps.getFPS() + 0.5));
00707         displayString(eGlutFont_Bitmap_9_by_15, 1, m_maxY - 5, -1, buffer);
00708 
00709         // map stats
00710         sprintf(buffer, "drawing %d objects", m_stats.nObjects);
00711         displayString(eGlutFont_Bitmap_9_by_15, 1, m_maxY - 18, -1, buffer);
00712 
00713         // height + width
00714         sprintf(buffer, "%d x %d", m_maxX, m_maxY);
00715         displayString(eGlutFont_Bitmap_9_by_15, m_maxX - 192, m_maxY - 5, -1,
00716             buffer);
00717 
00718         sprintf(buffer, "player id: %d", id);
00719         displayString(eGlutFont_Bitmap_9_by_15, m_maxX - 192, m_maxY - 21, -1,
00720             buffer);
00721 
00722         // position and facing
00723         if (m_mapKdTree) {
00724                 point3d_t pos = m_viewer.getPosition();
00725                 sprintf(buffer, "position: (%3.1f, %3.1f, %3.1f)",
00726                     pos.x, pos.y, pos.z);
00727                 displayString(eGlutFont_Bitmap_9_by_15, 1, 12, -1, buffer);
00728 
00729                 pos = m_viewer.getFacing();
00730                 sprintf(buffer, "  facing: (%3.1f, %3.1f, %3.1f)",
00731                     pos.x, pos.y, pos.z);
00732                 displayString(eGlutFont_Bitmap_9_by_15, 1, 24, -1, buffer);
00733         }
00734 }
00735 
00736 
00737 
00738 
00739 ////////////////////////////////////////////////////////////////////////////////
00740 //
00741 //      factory methods
00742 //
00743 ////////////////////////////////////////////////////////////////////////////////
00744 
00745 smart_ptr<Terminal>
00746 Terminal::create
00747 (
00748 IN TerminalHost * host,
00749 IN int playerId,
00750 IN smart_ptr<glut::RenderQueue>& rq
00751 )
00752 {
00753         ASSERT(host, "null");
00754         ASSERT(playerId > 0, "bad player id: %d", playerId);
00755         ASSERT(rq, "null");
00756 
00757         smart_ptr<TermImpl> local = new TermImpl;
00758         ASSERT(local, "out of memory");
00759 
00760         local->initialize(host, playerId, rq);
00761 
00762         return local;
00763 }
00764 
00765 
00766 
00767 };      // aesop namespace
00768