]> git.mxchange.org Git - flightgear.git/blob - src/Cockpit/hud.cxx
Merge commit 'refs/merge-requests/1552' of git@gitorious.org:fg/flightgear into next
[flightgear.git] / src / Cockpit / hud.cxx
1 // hud.cxx -- hud defines and prototypes
2 //
3 // Written by Michele America, started September 1997.
4 //
5 // Copyright (C) 1997  Michele F. America  - micheleamerica@geocities.com
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 //
21 // $Id$
22
23 #include <simgear/compiler.h>
24 #include <simgear/structure/exception.hxx>
25
26 #include <string>
27 #include <fstream>
28
29 #ifdef HAVE_CONFIG_H
30 #  include <config.h>
31 #endif
32
33 #include <math.h>
34 #include <stdlib.h>
35 #include <stdio.h>              // char related functions
36 #include <string.h>             // strcmp()
37
38 #include <simgear/constants.h>
39 #include <simgear/debug/logstream.hxx>
40 #include <simgear/misc/sg_path.hxx>
41 #include <simgear/props/props_io.hxx>
42
43 #include <osg/GLU>
44
45 #include <GUI/new_gui.hxx>           // FGFontCache
46 #include <Main/globals.hxx>
47 #include <Scenery/scenery.hxx>
48 #include <Airports/runways.hxx>
49 #include <Main/viewer.hxx>
50
51 #include "hud.hxx"
52
53
54 static HUD_Properties *HUDprop = 0;
55
56 static char units[5];
57
58 deque<SGSharedPtr<instr_item> > HUD_deque;
59
60 fgTextList HUD_TextList;
61 fgLineList HUD_LineList;
62 fgLineList HUD_StippleLineList;
63
64 fntRenderer *HUDtext = 0;
65 fntTexFont *HUD_Font = 0;
66 float HUD_TextSize = 0;
67 int HUD_style = 0;
68
69 float HUD_matrix[16];
70
71 int readHud( istream &input );
72 int readInstrument ( const SGPropertyNode * node);
73
74 static void drawHUD(osg::State*);
75 static void fgUpdateHUDVirtual(osg::State*);
76
77
78 class locRECT {
79 public:
80     RECT rect;
81
82     locRECT( UINT left, UINT top, UINT right, UINT bottom);
83     RECT get_rect(void) { return rect; }
84 };
85
86 locRECT :: locRECT( UINT left, UINT top, UINT right, UINT bottom)
87 {
88     rect.left   =  left;
89     rect.top    =  top;
90     rect.right  =  right;
91     rect.bottom =  bottom;
92
93 }
94 // #define DEBUG
95
96
97
98
99 int readInstrument(const SGPropertyNode * node)
100 {
101     static const SGPropertyNode *startup_units_node
102         = fgGetNode("/sim/startup/units");
103
104     instr_item *HIptr;
105
106     if ( !strcmp(startup_units_node->getStringValue(), "feet") ) {
107         strcpy(units, " ft");
108     } else {
109         strcpy(units, " m");
110     }
111
112     const SGPropertyNode * ladder_group = node->getNode("ladders");
113
114     if (ladder_group != 0) {
115         int nLadders = ladder_group->nChildren();
116         for (int j = 0; j < nLadders; j++) {
117             HIptr = static_cast<instr_item *>(new HudLadder(ladder_group->getChild(j)));
118             HUD_deque.insert(HUD_deque.begin(), HIptr);
119         }
120     }
121
122     const SGPropertyNode * card_group = node->getNode("cards");
123     if (card_group != 0) {
124         int nCards = card_group->nChildren();
125         for (int j = 0; j < nCards; j++) {
126             const char *type = card_group->getChild(j)->getStringValue("type", "gauge");
127
128             if (!strcmp(type, "gauge"))
129                 HIptr = static_cast<instr_item *>(new gauge_instr(card_group->getChild(j)));
130             else if (!strcmp(type, "dial") || !strcmp(type, "tape"))
131                 HIptr = static_cast<instr_item *>(new hud_card(card_group->getChild(j)));
132             else {
133                 SG_LOG(SG_INPUT, SG_WARN, "HUD: unknown 'card' type: " << type);
134                 continue;
135             }
136             HUD_deque.insert(HUD_deque.begin(), HIptr);
137         }
138     }
139
140     const SGPropertyNode * label_group = node->getNode("labels");
141     if (label_group != 0) {
142         int nLabels = label_group->nChildren();
143         for (int j = 0; j < nLabels; j++) {
144             HIptr = static_cast<instr_item *>(new instr_label(label_group->getChild(j)));
145             HUD_deque.insert(HUD_deque.begin(), HIptr);
146         }
147     }
148
149     const SGPropertyNode * tbi_group = node->getNode("tbis");
150     if (tbi_group != 0) {
151         int nTbis = tbi_group->nChildren();
152         for (int j = 0; j < nTbis; j++) {
153             HIptr = static_cast<instr_item *>(new fgTBI_instr(tbi_group->getChild(j)));
154             HUD_deque.insert(HUD_deque.begin(), HIptr);
155         }
156     }
157
158     const SGPropertyNode * rwy_group = node->getNode("runways");
159     if (rwy_group != 0) {
160         int nRwy = rwy_group->nChildren();
161         for (int j = 0; j < nRwy; j++) {
162             HIptr = static_cast<instr_item *>(new runway_instr(rwy_group->getChild(j)));
163             HUD_deque.insert(HUD_deque.begin(), HIptr);
164         }
165     }
166     return 0;
167 } //end readinstrument
168
169
170 int readHud( istream &input )
171 {
172
173     SGPropertyNode root;
174
175     try {
176         readProperties(input, &root);
177     } catch (const sg_exception &e) {
178         guiErrorMessage("Error reading HUD: ", e);
179         return 0;
180     }
181
182
183     SG_LOG(SG_INPUT, SG_DEBUG, "Read properties for  " <<
184            root.getStringValue("name"));
185
186     if (!root.getNode("depreciated"))
187         SG_LOG(SG_INPUT, SG_ALERT, "WARNING: use of depreciated old HUD");
188
189     HUD_deque.erase( HUD_deque.begin(), HUD_deque.end());
190
191
192     SG_LOG(SG_INPUT, SG_DEBUG, "Reading Hud instruments");
193
194     const SGPropertyNode * instrument_group = root.getChild("instruments");
195     int nInstruments = instrument_group->nChildren();
196
197     for (int i = 0; i < nInstruments; i++) {
198
199         const SGPropertyNode * node = instrument_group->getChild(i);
200
201         SGPath path( globals->get_fg_root() );
202         path.append(node->getStringValue("path"));
203
204         SG_LOG(SG_INPUT, SG_DEBUG, "Reading Instrument "
205                << node->getName()
206                << " from "
207                << path.str());
208
209         SGPropertyNode root2;
210         try {
211             readProperties(path.str(), &root2);
212         } catch (const sg_exception &e) {
213             guiErrorMessage("Error reading HUD instrument: ", e);
214             continue;
215         }
216         readInstrument(&root2);
217     }//for loop(i)
218
219     return 0;
220 }
221
222
223 // fgHUDInit
224 //
225 // Constructs a HUD object and then adds in instruments. At the present
226 // the instruments are hard coded into the routine. Ultimately these need
227 // to be defined by the aircraft's instrumentation records so that the
228 // display for a Piper Cub doesn't show the speed range of a North American
229 // mustange and the engine readouts of a B36!
230 //
231 int fgHUDInit()
232 {
233
234     HUD_style = 1;
235
236     SG_LOG( SG_COCKPIT, SG_INFO, "Initializing current aircraft HUD" );
237
238     string hud_path =
239         fgGetString("/sim/hud/path", "Huds/Default/default.xml");
240     SGPath path(globals->get_fg_root());
241     path.append(hud_path);
242
243     ifstream input(path.c_str());
244     if (!input.good()) {
245         SG_LOG(SG_INPUT, SG_ALERT,
246                "Cannot read Hud configuration from " << path.str());
247     } else {
248         readHud(input);
249         input.close();
250     }
251
252     if ( HUDtext ) {
253         // this chunk of code is not necessarily thread safe if the
254         // compiler optimizer reorders these statements.  Note that
255         // "delete ptr" does not set "ptr = NULL".  We have to do that
256         // ourselves.
257         fntRenderer *tmp = HUDtext;
258         HUDtext = NULL;
259         delete tmp;
260     }
261
262     FGFontCache *fc = globals->get_fontcache();
263     const char* fileName = fgGetString("/sim/hud/font/name", "Helvetica.txf");
264     HUD_Font = fc->getTexFont(fileName);
265     if (!HUD_Font)
266         throw sg_io_exception("/sim/hud/font/name is not a texture font",
267                               sg_location(fileName));
268
269     HUD_TextSize = fgGetFloat("/sim/hud/font/size", 10);
270
271     HUDtext = new fntRenderer();
272     HUDtext->setFont(HUD_Font);
273     HUDtext->setPointSize(HUD_TextSize);
274     HUD_TextList.setFont( HUDtext );
275
276     if (!HUDprop)
277         HUDprop = new HUD_Properties;
278     return 0;  // For now. Later we may use this for an error code.
279
280 }
281
282
283 int fgHUDInit2()
284 {
285
286     HUD_style = 2;
287
288     SG_LOG( SG_COCKPIT, SG_INFO, "Initializing current aircraft HUD" );
289
290     SGPath path(globals->get_fg_root());
291     path.append("Huds/Minimal/default.xml");
292
293
294     ifstream input(path.c_str());
295     if (!input.good()) {
296         SG_LOG(SG_INPUT, SG_ALERT,
297                "Cannot read Hud configuration from " << path.str());
298     } else {
299         readHud(input);
300         input.close();
301     }
302
303     if (!HUDprop)
304         HUDprop = new HUD_Properties;
305     return 0;  // For now. Later we may use this for an error code.
306
307 }
308 //$$$ End - added, Neetha, 28 Nov 2k
309
310
311 // fgUpdateHUD
312 //
313 // Performs a once around the list of calls to instruments installed in
314 // the HUD object with requests for redraw. Kinda. It will when this is
315 // all C++.
316 //
317 void fgUpdateHUD( osg::State* state ) {
318
319     static const SGPropertyNode *enable3d_node = fgGetNode("/sim/hud/enable3d");
320     if ( HUD_style == 1 && enable3d_node->getBoolValue() ) {
321         fgUpdateHUDVirtual(state);
322         return;
323     }
324
325     static const float normal_aspect = float(640) / float(480);
326     // note: aspect_ratio is Y/X
327     float current_aspect = 1.0f/globals->get_current_view()->get_aspect_ratio();
328     if ( current_aspect > normal_aspect ) {
329         float aspect_adjust = current_aspect / normal_aspect;
330         float adjust = 320.0f*aspect_adjust - 320.0f;
331         fgUpdateHUD( state, -adjust, 0.0f, 640.0f+adjust, 480.0f );
332     } else {
333         float aspect_adjust = normal_aspect / current_aspect;
334         float adjust = 240.0f*aspect_adjust - 240.0f;
335         fgUpdateHUD( state, 0.0f, -adjust, 640.0f, 480.0f+adjust );
336     }
337 }
338
339 void fgUpdateHUDVirtual(osg::State* state)
340 {
341     FGViewer* view = globals->get_current_view();
342
343     // Standard fgfs projection, with essentially meaningless clip
344     // planes (we'll map the whole HUD plane to z=-1)
345     glMatrixMode(GL_PROJECTION);
346     glPushMatrix();
347     glLoadIdentity();
348     gluPerspective(view->get_v_fov(), 1/view->get_aspect_ratio(), 0.1, 10);
349
350     glMatrixMode(GL_MODELVIEW);
351     glPushMatrix();
352     glLoadIdentity();
353
354     // Standard fgfs view direction computation
355     float lookat[3];
356     lookat[0] = -sin(SG_DEGREES_TO_RADIANS * view->getHeadingOffset_deg());
357     lookat[1] = tan(SG_DEGREES_TO_RADIANS * view->getPitchOffset_deg());
358     lookat[2] = -cos(SG_DEGREES_TO_RADIANS * view->getHeadingOffset_deg());
359     if (fabs(lookat[1]) > 9999)
360         lookat[1] = 9999; // FPU sanity
361     gluLookAt(0, 0, 0, lookat[0], lookat[1], lookat[2], 0, 1, 0);
362
363     // Map the -1:1 square to a 55.0x41.25 degree wide patch at z=1.
364     // This is the default fgfs field of view, which the HUD files are
365     // written to assume.
366     float dx = 0.52056705; // tan(55/2)
367     float dy = dx * 0.75;  // assumes 4:3 aspect ratio
368     float m[16];
369     m[0] = dx; m[4] =  0; m[ 8] = 0; m[12] = 0;
370     m[1] =  0; m[5] = dy; m[ 9] = 0; m[13] = 0;
371     m[2] =  0; m[6] =  0; m[10] = 1; m[14] = 0;
372     m[3] =  0; m[7] =  0; m[11] = 0; m[15] = 1;
373     glMultMatrixf(m);
374
375     // Convert the 640x480 "HUD standard" coordinate space to a square
376     // about the origin in the range [-1:1] at depth of -1
377     glScalef(1./320, 1./240, 1);
378     glTranslatef(-320, -240, -1);
379
380     // Do the deed
381     drawHUD(state);
382
383     // Clean up our mess
384     glMatrixMode(GL_PROJECTION);
385     glPopMatrix();
386     glMatrixMode(GL_MODELVIEW);
387     glPopMatrix();
388 }
389
390
391 void fgUpdateHUD( osg::State* state, GLfloat x_start, GLfloat y_start,
392                   GLfloat x_end, GLfloat y_end )
393 {
394     glMatrixMode(GL_PROJECTION);
395     glPushMatrix();
396     glLoadIdentity();
397     gluOrtho2D(x_start, x_end, y_start, y_end);
398
399     glMatrixMode(GL_MODELVIEW);
400     glPushMatrix();
401     glLoadIdentity();
402
403     drawHUD(state);
404
405     glMatrixMode(GL_PROJECTION);
406     glPopMatrix();
407     glMatrixMode(GL_MODELVIEW);
408     glPopMatrix();
409 }
410
411
412 void drawHUD(osg::State* state)
413 {
414     if ( !HUD_deque.size() ) // Trust everyone, but ALWAYS cut the cards!
415         return;
416
417     HUD_TextList.erase();
418     HUD_LineList.erase();
419     // HUD_StippleLineList.erase();
420
421     glDisable(GL_DEPTH_TEST);
422     glDisable(GL_LIGHTING);
423
424     static const SGPropertyNode *heading_enabled
425         = fgGetNode("/autopilot/locks/heading", true);
426     static const SGPropertyNode *altitude_enabled
427         = fgGetNode("/autopilot/locks/altitude", true);
428
429     static char hud_hdg_text[256];
430     static char hud_gps_text0[256];
431     static char hud_gps_text1[256];
432     static char hud_gps_text2[256];
433     static char hud_alt_text[256];
434
435     glEnable(GL_BLEND);
436     if (HUDprop->isTransparent())
437         glBlendFunc(GL_SRC_ALPHA, GL_ONE);
438     else
439         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
440
441     if (HUDprop->isAntialiased()) {
442         glEnable(GL_LINE_SMOOTH);
443         glAlphaFunc(GL_GREATER, HUDprop->alphaClamp());
444         glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE);
445         //glLineWidth(1.5);
446     } else {
447         //glLineWidth(1.0);
448     }
449
450     HUDprop->setColor();
451     for_each(HUD_deque.begin(), HUD_deque.end(), HUDdraw());
452
453     //HUD_TextList.add( fgText(40, 10, get_formated_gmt_time(), 0) );
454
455
456     int apY = 480 - 80;
457
458
459     if (strcmp( heading_enabled->getStringValue(), "dg-heading-hold") == 0 ) {
460         snprintf( hud_hdg_text, 256, "hdg = %.1f\n",
461                   fgGetDouble("/autopilot/settings/heading-bug-deg") );
462         HUD_TextList.add( fgText( 40, apY, hud_hdg_text ) );
463         apY -= 15;
464     } else if ( strcmp(heading_enabled->getStringValue(), "true-heading-hold") == 0 ) {
465         snprintf( hud_hdg_text, 256, "hdg = %.1f\n",
466                   fgGetDouble("/autopilot/settings/true-heading-deg") );
467         HUD_TextList.add( fgText( 40, apY, hud_hdg_text ) );
468         apY -= 15;
469     }
470   
471   // GPS current waypoint information
472     SGPropertyNode_ptr gps = fgGetNode("/instrumentation/gps", true);
473     SGPropertyNode_ptr curWp = gps->getChild("wp")->getChild("wp",1);
474     
475     if ((gps->getDoubleValue("raim") > 0.5) && curWp) {
476       // GPS is receiving a valid signal
477       snprintf(hud_gps_text0, 256, "WPT:%5s BRG:%03.0f %5.1fnm",
478         curWp->getStringValue("ID"),
479         curWp->getDoubleValue("bearing-mag-deg"),
480         curWp->getDoubleValue("distance-nm"));
481       HUD_TextList.add( fgText( 40, apY, hud_gps_text0 ) );
482       apY -= 15;
483        
484        // curWp->getStringValue("TTW")
485       snprintf(hud_gps_text2, 256, "ETA %s", curWp->getStringValue("TTW"));
486       HUD_TextList.add( fgText( 40, apY, hud_gps_text2 ) );
487       apY -= 15;
488       
489       double courseError = curWp->getDoubleValue("course-error-nm");
490       if (fabs(courseError) >= 0.01) {
491         // generate an arrow indicatinng if the pilot should turn left or right
492         char dir = (courseError < 0.0) ? '<' : '>';
493         snprintf(hud_gps_text1, 256, "GPS TRK:%03.0f XTRK:%c%4.2fnm",
494           gps->getDoubleValue("indicated-track-magnetic-deg"), dir, fabs(courseError));
495       } else { // on course, don't bother showing the XTRK error
496         snprintf(hud_gps_text1, 256, "GPS TRK:%03.0f",
497           gps->getDoubleValue("indicated-track-magnetic-deg"));
498       }
499       
500       HUD_TextList.add( fgText( 40, apY, hud_gps_text1) );
501       apY -= 15;
502     } // of valid GPS output
503     
504   ////////////////////
505
506     if ( strcmp( altitude_enabled->getStringValue(), "altitude-hold" ) == 0 ) {
507         snprintf( hud_alt_text, 256, "alt = %.0f\n",
508                   fgGetDouble("/autopilot/settings/target-altitude-ft") );
509         HUD_TextList.add( fgText( 40, apY, hud_alt_text ) );
510         apY -= 15;
511     } else if ( strcmp( altitude_enabled->getStringValue(), "agl-hold" ) == 0 ){
512         snprintf( hud_alt_text, 256, "agl = %.0f\n",
513                   fgGetDouble("/autopilot/settings/target-agl-ft") );
514         HUD_TextList.add( fgText( 40, apY, hud_alt_text ) );
515         apY -= 15;
516     }
517
518     HUD_TextList.draw();
519     HUD_LineList.draw();
520
521     // glEnable(GL_LINE_STIPPLE);
522     // glLineStipple( 1, 0x00FF );
523     // HUD_StippleLineList.draw();
524     // glDisable(GL_LINE_STIPPLE);
525
526     if (HUDprop->isAntialiased()) {
527         glDisable(GL_ALPHA_TEST);
528         glDisable(GL_LINE_SMOOTH);
529         //glLineWidth(1.0);
530     }
531
532     if (HUDprop->isTransparent())
533         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
534
535     glEnable(GL_DEPTH_TEST);
536     glEnable(GL_LIGHTING);
537 }
538
539
540 void fgTextList::draw()
541 {
542     if (!Font)
543         return;
544
545     vector<fgText>::iterator curString = List.begin();
546     vector<fgText>::iterator lastString = List.end();
547
548     glPushAttrib(GL_COLOR_BUFFER_BIT);
549     glEnable(GL_TEXTURE_2D);
550     glEnable(GL_BLEND);
551     if (HUDprop->isTransparent())
552         glBlendFunc(GL_SRC_ALPHA, GL_ONE);
553     else
554         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
555
556     if (HUDprop->isAntialiased()) {
557         glEnable(GL_ALPHA_TEST);
558         glAlphaFunc(GL_GREATER, HUDprop->alphaClamp());
559     }
560
561     Font->begin();
562     for (; curString != lastString; curString++)
563         curString->Draw(Font);
564     Font->end();
565
566     glDisable(GL_TEXTURE_2D);
567     glPopAttrib();
568 }
569
570
571 // HUD property listener class
572 //
573 HUD_Properties::HUD_Properties() :
574     _current(fgGetNode("/sim/hud/current-color", true)),
575     _visibility(fgGetNode("/sim/hud/visibility", true)),
576     _antialiasing(fgGetNode("/sim/hud/color/antialiased", true)),
577     _transparency(fgGetNode("/sim/hud/color/transparent", true)),
578     _red(fgGetNode("/sim/hud/color/red", true)),
579     _green(fgGetNode("/sim/hud/color/green", true)),
580     _blue(fgGetNode("/sim/hud/color/blue", true)),
581     _alpha(fgGetNode("/sim/hud/color/alpha", true)),
582     _alpha_clamp(fgGetNode("/sim/hud/color/alpha-clamp", true)),
583     _brightness(fgGetNode("/sim/hud/color/brightness", true)),
584     _visible(false),
585     _antialiased(false),
586     _transparent(false),
587     _a(0.67),
588     _cl(0.01)
589 {
590     _visibility->addChangeListener(this);
591     _antialiasing->addChangeListener(this);
592     _transparency->addChangeListener(this);
593     _red->addChangeListener(this);
594     _green->addChangeListener(this);
595     _blue->addChangeListener(this);
596     _alpha->addChangeListener(this);
597     _alpha_clamp->addChangeListener(this);
598     _brightness->addChangeListener(this);
599     _current->addChangeListener(this, true);
600 }
601
602
603 void HUD_Properties::valueChanged(SGPropertyNode *node)
604 {
605     if (!strcmp(node->getName(), "current-color")) {
606         int i = node->getIntValue();
607         if (i < 0)
608             i = 0;
609         SGPropertyNode *n = fgGetNode("/sim/hud/palette", true);
610         if ((n = n->getChild("color", i, false))) {
611             if (n->hasValue("red"))
612                 _red->setFloatValue(n->getFloatValue("red", 1.0));
613             if (n->hasValue("green"))
614                 _green->setFloatValue(n->getFloatValue("green", 1.0));
615             if (n->hasValue("blue"))
616                 _blue->setFloatValue(n->getFloatValue("blue", 1.0));
617             if (n->hasValue("alpha"))
618                 _alpha->setFloatValue(n->getFloatValue("alpha", 0.67));
619             if (n->hasValue("alpha-clamp"))
620                 _alpha_clamp->setFloatValue(n->getFloatValue("alpha-clamp", 0.01));
621             if (n->hasValue("brightness"))
622                 _brightness->setFloatValue(n->getFloatValue("brightness", 0.75));
623             if (n->hasValue("antialiased"))
624                 _antialiasing->setBoolValue(n->getBoolValue("antialiased", false));
625             if (n->hasValue("transparent"))
626                 _transparency->setBoolValue(n->getBoolValue("transparent", false));
627         }
628     }
629     _visible = _visibility->getBoolValue();
630     _transparent = _transparency->getBoolValue();
631     _antialiased = _antialiasing->getBoolValue();
632     float brt = _brightness->getFloatValue();
633     _r = clamp(brt * _red->getFloatValue());
634     _g = clamp(brt * _green->getFloatValue());
635     _b = clamp(brt * _blue->getFloatValue());
636     _a = clamp(_alpha->getFloatValue());
637     _cl = clamp(_alpha_clamp->getFloatValue());
638 }
639
640
641 void HUD_Properties::setColor() const
642 {
643     if (_antialiased)
644         glColor4f(_r, _g, _b, _a);
645     else
646         glColor3f(_r, _g, _b);
647 }
648
649