292 lines
8.5 KiB
C++
292 lines
8.5 KiB
C++
//
|
|
// Written and (c) Torsten Dreyer - Torsten(at)t3r_dot_de
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU General Public License as
|
|
// published by the Free Software Foundation; either version 2 of the
|
|
// License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful, but
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software
|
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
//
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_WINDOWS_H
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
#include "FGGLApplication.hxx"
|
|
#include "FGPanelApplication.hxx"
|
|
#include <GL/gl.h>
|
|
#include <GL/glut.h>
|
|
|
|
#include <simgear/math/SGMisc.hxx>
|
|
#include <simgear/misc/sg_path.hxx>
|
|
#include <simgear/props/props_io.hxx>
|
|
#include <simgear/structure/exception.hxx>
|
|
|
|
#include <iostream>
|
|
|
|
#include "panel_io.hxx"
|
|
#include "ApplicationProperties.hxx"
|
|
|
|
using namespace std;
|
|
|
|
inline static string ParseArgs( int argc, char ** argv, const string & token )
|
|
{
|
|
for( int i = 0; i < argc; i++ ) {
|
|
string arg = argv[i];
|
|
if( arg.find( token ) == 0 )
|
|
return arg.substr( token.length() );
|
|
}
|
|
return "";
|
|
}
|
|
|
|
inline static string ParseArgs( int argc, char ** argv, const char * token )
|
|
{
|
|
string s = token;
|
|
return ParseArgs( argc, argv, s );
|
|
}
|
|
|
|
#include "FGPNGTextureLoader.hxx"
|
|
#include "FGRGBTextureLoader.hxx"
|
|
static FGPNGTextureLoader pngTextureLoader;
|
|
static FGRGBTextureLoader rgbTextureLoader;
|
|
|
|
FGPanelApplication::FGPanelApplication( int argc, char ** argv ) :
|
|
FGGLApplication( "FlightGear Panel", argc, argv )
|
|
{
|
|
sglog().setLogLevels( SG_ALL, SG_WARN );
|
|
FGCroppedTexture::registerTextureLoader( "png", &pngTextureLoader );
|
|
FGCroppedTexture::registerTextureLoader( "rgb", &rgbTextureLoader );
|
|
|
|
string panelFilename;
|
|
string fgRoot;
|
|
|
|
for( int i = 1; i < argc; i++ ) {
|
|
panelFilename = ParseArgs( argc, argv, "--panel=" );
|
|
fgRoot = ParseArgs( argc, argv, "--fg-root=" );
|
|
}
|
|
|
|
if( fgRoot.length() > 0 )
|
|
ApplicationProperties::root = fgRoot;
|
|
|
|
if( panelFilename.length() == 0 ) {
|
|
cerr << "Need a panel filename. Use --panel=path_to_filename" << endl;
|
|
throw exception();
|
|
}
|
|
|
|
try {
|
|
SGPath tpath = ApplicationProperties::GetRootPath( panelFilename.c_str() );
|
|
readProperties( tpath.str(), ApplicationProperties::Properties );
|
|
}
|
|
catch( sg_io_exception & e ) {
|
|
cerr << e.getFormattedMessage() << endl;
|
|
throw;
|
|
}
|
|
|
|
for( int i = 1; i < argc; i++ ) {
|
|
string arg = argv[i];
|
|
if( arg.find( "--prop:" ) == 0 ) {
|
|
string s2 = arg.substr( 7 );
|
|
unsigned p = s2.find( "=" );
|
|
if( p != string::npos ) {
|
|
string propertyName = s2.substr( 0, p );
|
|
string propertyValue = s2.substr( p+1 );
|
|
ApplicationProperties::Properties->getNode( propertyName.c_str(), true )->setValue( propertyValue.c_str() );
|
|
}
|
|
}
|
|
}
|
|
|
|
SGPropertyNode_ptr n;
|
|
if( (n = ApplicationProperties::Properties->getNode( "panel" )) != NULL )
|
|
panel = FGReadablePanel::read( n );
|
|
|
|
protocol = new FGPanelProtocol( ApplicationProperties::Properties->getNode( "communication", true ) );
|
|
protocol->init();
|
|
}
|
|
|
|
FGPanelApplication::~FGPanelApplication()
|
|
{
|
|
}
|
|
|
|
void FGPanelApplication::Run()
|
|
{
|
|
int mode = GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE;
|
|
int w = panel == NULL ? 0 : panel->getWidth();
|
|
int h = panel == NULL ? 0 : panel->getHeight();
|
|
if( w == 0 && h == 0 ) {
|
|
w = 1024;
|
|
h = 768;
|
|
} else if( w == 0 ) {
|
|
w = h / 0.75;
|
|
} else if( h == 0 ) {
|
|
h = w * 0.75;
|
|
}
|
|
|
|
bool gameMode = ApplicationProperties::Properties->getNode( "game-mode", true )->getBoolValue();
|
|
FGGLApplication::Run( mode, gameMode, w, h );
|
|
}
|
|
|
|
void FGPanelApplication::Init()
|
|
{
|
|
glAlphaFunc(GL_GREATER, 0.1);
|
|
glutSetCursor( GLUT_CURSOR_NONE );
|
|
ApplicationProperties::fontCache.initializeFonts();
|
|
}
|
|
|
|
void FGPanelApplication::Reshape( int width, int height )
|
|
{
|
|
this->width = width;
|
|
this->height = height;
|
|
glViewport(0, 0, (GLsizei) width, (GLsizei) height);
|
|
}
|
|
|
|
void FGPanelApplication::Idle()
|
|
{
|
|
double d = glutGet(GLUT_ELAPSED_TIME);
|
|
|
|
double dt = Sleep();
|
|
if( dt == 0 )
|
|
return;
|
|
|
|
if( panel != NULL )
|
|
panel->update( dt );
|
|
|
|
glutSwapBuffers();
|
|
|
|
if( protocol != NULL )
|
|
protocol->update( dt );
|
|
|
|
static double dsum = 0.0;
|
|
static unsigned cnt = 0;
|
|
dsum += glutGet(GLUT_ELAPSED_TIME)-d;
|
|
cnt++;
|
|
if( dsum > 1000.0 ) {
|
|
ApplicationProperties::Properties->getNode( "/sim/frame-rate", true )->setDoubleValue(cnt*1000.0/dsum );
|
|
dsum = 0.0;
|
|
cnt = 0;
|
|
}
|
|
}
|
|
|
|
void FGPanelApplication::Key( unsigned char key, int x, int y )
|
|
{
|
|
switch( key ) {
|
|
case 0x1b:
|
|
exit(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
double FGPanelApplication::Sleep()
|
|
{
|
|
SGTimeStamp current_time_stamp;
|
|
static SGTimeStamp last_time_stamp;
|
|
|
|
if ( last_time_stamp.get_seconds() == 0 )
|
|
last_time_stamp.stamp();
|
|
|
|
double model_hz = 60;
|
|
double throttle_hz = ApplicationProperties::getDouble("/sim/frame-rate-throttle-hz", 0.0);
|
|
if ( throttle_hz > 0.0 ) {
|
|
// optionally throttle the frame rate (to get consistent frame
|
|
// rates or reduce cpu usage.
|
|
|
|
double frame_us = 1.0e6 / throttle_hz;
|
|
|
|
// sleep based timing loop.
|
|
//
|
|
// Calling sleep, even usleep() on linux is less accurate than
|
|
// we like, but it does free up the cpu for other tasks during
|
|
// the sleep so it is desirable. Because of the way sleep()
|
|
// is implemented in consumer operating systems like windows
|
|
// and linux, you almost always sleep a little longer than the
|
|
// requested amount.
|
|
//
|
|
// To combat the problem of sleeping too long, we calculate the
|
|
// desired wait time and shorten it by 2000us (2ms) to avoid
|
|
// [hopefully] over-sleep'ing. The 2ms value was arrived at
|
|
// via experimentation. We follow this up at the end with a
|
|
// simple busy-wait loop to get the final pause timing exactly
|
|
// right.
|
|
//
|
|
// Assuming we don't oversleep by more than 2000us, this
|
|
// should be a reasonable compromise between sleep based
|
|
// waiting, and busy waiting.
|
|
|
|
// sleep() will always overshoot by a bit so undersleep by
|
|
// 2000us in the hopes of never oversleeping.
|
|
frame_us -= 2000.0;
|
|
if ( frame_us < 0.0 ) {
|
|
frame_us = 0.0;
|
|
}
|
|
current_time_stamp.stamp();
|
|
|
|
/* Convert to ms */
|
|
double elapsed_us = (current_time_stamp - last_time_stamp).toUSecs();
|
|
if ( elapsed_us < frame_us ) {
|
|
double requested_us = frame_us - elapsed_us;
|
|
#ifdef _WIN32
|
|
::Sleep ((int)(requested_us / 1000.0)) ;
|
|
#else
|
|
usleep ( (useconds_t)(requested_us ) ) ;
|
|
#endif
|
|
}
|
|
// busy wait timing loop.
|
|
//
|
|
// This yields the most accurate timing. If the previous
|
|
// usleep() call is omitted this will peg the cpu
|
|
// (which is just fine if FG is the only app you care about.)
|
|
current_time_stamp.stamp();
|
|
SGTimeStamp next_time_stamp = last_time_stamp;
|
|
next_time_stamp += SGTimeStamp::fromSec(1e-6*frame_us);
|
|
while ( current_time_stamp < next_time_stamp ) {
|
|
current_time_stamp.stamp();
|
|
}
|
|
|
|
} else {
|
|
current_time_stamp.stamp();
|
|
}
|
|
|
|
double real_delta_time_sec = double(current_time_stamp.toUSecs() - last_time_stamp.toUSecs()) / 1000000.0;
|
|
last_time_stamp = current_time_stamp;
|
|
//fprintf(stdout,"\r%4.1lf ", 1/real_delta_time_sec );
|
|
//fflush(stdout);
|
|
|
|
// round the real time down to a multiple of 1/model-hz.
|
|
// this way all systems are updated the _same_ amount of dt.
|
|
static double reminder = 0.0;
|
|
static long global_multi_loop = 0;
|
|
real_delta_time_sec += reminder;
|
|
global_multi_loop = long(floor(real_delta_time_sec*model_hz));
|
|
global_multi_loop = SGMisc<long>::max(0, global_multi_loop);
|
|
reminder = real_delta_time_sec - double(global_multi_loop)/double(model_hz);
|
|
return double(global_multi_loop)/double(model_hz);
|
|
}
|
|
|
|
double ApplicationProperties::getDouble( const char * name, double def )
|
|
{
|
|
SGPropertyNode_ptr n = ApplicationProperties::Properties->getNode( name, false );
|
|
if( n == NULL ) return def;
|
|
return n->getDoubleValue();
|
|
}
|
|
SGPath ApplicationProperties::GetRootPath( const char * sub )
|
|
{
|
|
SGPath path( ApplicationProperties::root );
|
|
if( sub != NULL )
|
|
path.append( sub );
|
|
return path;
|
|
}
|
|
|
|
std::string ApplicationProperties::root = ".";
|
|
SGPropertyNode_ptr ApplicationProperties::Properties = new SGPropertyNode;
|
|
FGFontCache ApplicationProperties::fontCache;
|