1
0
Fork 0
flightgear/src/Main/viewer.cxx
curt 8ae364a220 Jim Wilson:
This is a small fix for what turned out to be a major bug.  Ground elevation
was calculated incorrectly when distant from one of the view locations. This
resulted in several problems including bizarre gear trimming, mid air
"crashes" (as in thinking we hit the ground) and so on when close to or on the
ground.

Unfortunately it does require a second ssg traversal when in tower view
(only), but the increased load isn't all that noticable.  For the time being
this really is the best solution.  In a future update I will be eliminating
the unecessary per frame traversals for the static views (without having to
maintain multiple ssgRoots).

When we go to multiple FDM instances we will perhaps need to put the ssg
traversal and ground elevation queries for the FDMs into an event timer that
updates the FDMs ground elevation in a round robin fashion (maybe every 1/n
seconds where n is the number of FDM instances running).
2002-05-20 16:13:37 +00:00

749 lines
19 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// viewer.cxx -- class for managing a viewer in the flightgear world.
//
// Written by Curtis Olson, started August 1997.
// overhaul started October 2000.
// partially rewritten by Jim Wilson jim@kelcomaine.com using interface
// by David Megginson March 2002
//
// Copyright (C) 1997 - 2000 Curtis L. Olson - curt@flightgear.org
//
// 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., 675 Mass Ave, Cambridge, MA 02139, USA.
//
// $Id$
#include <simgear/compiler.h>
#include "fg_props.hxx"
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <simgear/debug/logstream.hxx>
#include <simgear/constants.h>
#include <simgear/math/point3d.hxx>
#include <simgear/math/polar3d.hxx>
#include <simgear/math/sg_geodesy.hxx>
#include <Scenery/scenery.hxx>
#include <simgear/math/vector.hxx>
#include <Main/globals.hxx>
#include <Model/acmodel.hxx>
#include <Model/model.hxx>
#include "viewer.hxx"
//////////////////////////////////////////////////////////////////
// Norman's Optimized matrix rotators! //
//////////////////////////////////////////////////////////////////
// Since these are pure rotation matrices we can save some bookwork
// by considering them to be 3x3 until the very end -- NHV
static void MakeVIEW_OFFSET( sgMat4 dst,
const float angle1, const sgVec3 axis1,
const float angle2, const sgVec3 axis2 )
{
// make rotmatrix1 from angle and axis
float s = (float) sin ( angle1 ) ;
float c = (float) cos ( angle1 ) ;
float t = SG_ONE - c ;
sgMat3 mat1;
float tmp = t * axis1[0];
mat1[0][0] = tmp * axis1[0] + c ;
mat1[0][1] = tmp * axis1[1] + s * axis1[2] ;
mat1[0][2] = tmp * axis1[2] - s * axis1[1] ;
tmp = t * axis1[1];
mat1[1][0] = tmp * axis1[0] - s * axis1[2] ;
mat1[1][1] = tmp * axis1[1] + c ;
mat1[1][2] = tmp * axis1[2] + s * axis1[0] ;
tmp = t * axis1[2];
mat1[2][0] = tmp * axis1[0] + s * axis1[1] ;
mat1[2][1] = tmp * axis1[1] - s * axis1[0] ;
mat1[2][2] = tmp * axis1[2] + c ;
// make rotmatrix2 from angle and axis
s = (float) sin ( angle2 ) ;
c = (float) cos ( angle2 ) ;
t = SG_ONE - c ;
sgMat3 mat2;
tmp = t * axis2[0];
mat2[0][0] = tmp * axis2[0] + c ;
mat2[0][1] = tmp * axis2[1] + s * axis2[2] ;
mat2[0][2] = tmp * axis2[2] - s * axis2[1] ;
tmp = t * axis2[1];
mat2[1][0] = tmp * axis2[0] - s * axis2[2] ;
mat2[1][1] = tmp * axis2[1] + c ;
mat2[1][2] = tmp * axis2[2] + s * axis2[0] ;
tmp = t * axis2[2];
mat2[2][0] = tmp * axis2[0] + s * axis2[1] ;
mat2[2][1] = tmp * axis2[1] - s * axis2[0] ;
mat2[2][2] = tmp * axis2[2] + c ;
// multiply matrices
for ( int j = 0 ; j < 3 ; j++ ) {
dst[0][j] = mat2[0][0] * mat1[0][j] +
mat2[0][1] * mat1[1][j] +
mat2[0][2] * mat1[2][j];
dst[1][j] = mat2[1][0] * mat1[0][j] +
mat2[1][1] * mat1[1][j] +
mat2[1][2] * mat1[2][j];
dst[2][j] = mat2[2][0] * mat1[0][j] +
mat2[2][1] * mat1[1][j] +
mat2[2][2] * mat1[2][j];
}
// fill in 4x4 matrix elements
dst[0][3] = SG_ZERO;
dst[1][3] = SG_ZERO;
dst[2][3] = SG_ZERO;
dst[3][0] = SG_ZERO;
dst[3][1] = SG_ZERO;
dst[3][2] = SG_ZERO;
dst[3][3] = SG_ONE;
}
////////////////////////////////////////////////////////////////////////
// Implementation of FGViewer.
////////////////////////////////////////////////////////////////////////
// Constructor
FGViewer::FGViewer( fgViewType Type, bool from_model, int from_model_index, bool at_model, int at_model_index, double x_offset_m, double y_offset_m, double z_offset_m, double near_m ):
_scaling_type(FG_SCALING_MAX),
_fov_deg(55.0),
_dirty(true),
_lon_deg(0),
_lat_deg(0),
_alt_ft(0),
_target_lon_deg(0),
_target_lat_deg(0),
_target_alt_ft(0),
_roll_deg(0),
_pitch_deg(0),
_heading_deg(0),
_heading_offset_deg(0),
_pitch_offset_deg(0),
_roll_offset_deg(0),
_goal_heading_offset_deg(0.0),
_goal_pitch_offset_deg(0.0)
{
sgdZeroVec3(_absolute_view_pos);
_type = Type;
_from_model = from_model;
_from_model_index = from_model_index;
_at_model = at_model;
_at_model_index = at_model_index;
_x_offset_m = x_offset_m;
_y_offset_m = y_offset_m;
_z_offset_m = z_offset_m;
_ground_level_nearplane_m = near_m;
//a reasonable guess for init, so that the math doesn't blow up
}
// Destructor
FGViewer::~FGViewer( void ) {
}
void
FGViewer::init ()
{
if ( _from_model )
_location = (FGLocation *) globals->get_aircraft_model()->get3DModel()->getFGLocation();
else
_location = (FGLocation *) new FGLocation;
if ( _type == FG_LOOKAT ) {
if ( _at_model )
_target_location = (FGLocation *) globals->get_aircraft_model()->get3DModel()->getFGLocation();
else
_target_location = (FGLocation *) new FGLocation;
}
}
void
FGViewer::bind ()
{
}
void
FGViewer::unbind ()
{
}
void
FGViewer::setType ( int type )
{
if (type == 0)
_type = FG_LOOKFROM;
if (type == 1)
_type = FG_LOOKAT;
}
void
FGViewer::setLongitude_deg (double lon_deg)
{
_dirty = true;
_lon_deg = lon_deg;
}
void
FGViewer::setLatitude_deg (double lat_deg)
{
_dirty = true;
_lat_deg = lat_deg;
}
void
FGViewer::setAltitude_ft (double alt_ft)
{
_dirty = true;
_alt_ft = alt_ft;
}
void
FGViewer::setPosition (double lon_deg, double lat_deg, double alt_ft)
{
_dirty = true;
_lon_deg = lon_deg;
_lat_deg = lat_deg;
_alt_ft = alt_ft;
}
void
FGViewer::setTargetLongitude_deg (double lon_deg)
{
_dirty = true;
_target_lon_deg = lon_deg;
}
void
FGViewer::setTargetLatitude_deg (double lat_deg)
{
_dirty = true;
_target_lat_deg = lat_deg;
}
void
FGViewer::setTargetAltitude_ft (double alt_ft)
{
_dirty = true;
_target_alt_ft = alt_ft;
}
void
FGViewer::setTargetPosition (double lon_deg, double lat_deg, double alt_ft)
{
_dirty = true;
_target_lon_deg = lon_deg;
_target_lat_deg = lat_deg;
_target_alt_ft = alt_ft;
}
void
FGViewer::setRoll_deg (double roll_deg)
{
_dirty = true;
_roll_deg = roll_deg;
}
void
FGViewer::setPitch_deg (double pitch_deg)
{
_dirty = true;
_pitch_deg = pitch_deg;
}
void
FGViewer::setHeading_deg (double heading_deg)
{
_dirty = true;
_heading_deg = heading_deg;
}
void
FGViewer::setOrientation (double roll_deg, double pitch_deg, double heading_deg)
{
_dirty = true;
_roll_deg = roll_deg;
_pitch_deg = pitch_deg;
_heading_deg = heading_deg;
}
void
FGViewer::setTargetRoll_deg (double target_roll_deg)
{
_dirty = true;
_target_roll_deg = target_roll_deg;
}
void
FGViewer::setTargetPitch_deg (double target_pitch_deg)
{
_dirty = true;
_target_pitch_deg = target_pitch_deg;
}
void
FGViewer::setTargetHeading_deg (double target_heading_deg)
{
_dirty = true;
_target_heading_deg = target_heading_deg;
}
void
FGViewer::setTargetOrientation (double target_roll_deg, double target_pitch_deg, double target_heading_deg)
{
_dirty = true;
_target_roll_deg = target_roll_deg;
_target_pitch_deg = target_pitch_deg;
_target_heading_deg = target_heading_deg;
}
void
FGViewer::setXOffset_m (double x_offset_m)
{
_dirty = true;
_x_offset_m = x_offset_m;
}
void
FGViewer::setYOffset_m (double y_offset_m)
{
_dirty = true;
_y_offset_m = y_offset_m;
}
void
FGViewer::setZOffset_m (double z_offset_m)
{
_dirty = true;
_z_offset_m = z_offset_m;
}
void
FGViewer::setPositionOffsets (double x_offset_m, double y_offset_m, double z_offset_m)
{
_dirty = true;
_x_offset_m = x_offset_m;
_y_offset_m = y_offset_m;
_z_offset_m = z_offset_m;
}
void
FGViewer::setRollOffset_deg (double roll_offset_deg)
{
_dirty = true;
_roll_offset_deg = roll_offset_deg;
}
void
FGViewer::setPitchOffset_deg (double pitch_offset_deg)
{
_dirty = true;
_pitch_offset_deg = pitch_offset_deg;
}
void
FGViewer::setHeadingOffset_deg (double heading_offset_deg)
{
_dirty = true;
_heading_offset_deg = heading_offset_deg;
}
void
FGViewer::setGoalRollOffset_deg (double goal_roll_offset_deg)
{
_dirty = true;
_goal_roll_offset_deg = goal_roll_offset_deg;
}
void
FGViewer::setGoalPitchOffset_deg (double goal_pitch_offset_deg)
{
_dirty = true;
_goal_pitch_offset_deg = goal_pitch_offset_deg;
if ( _goal_pitch_offset_deg < -90 ) {
_goal_pitch_offset_deg = -90.0;
}
if ( _goal_pitch_offset_deg > 90.0 ) {
_goal_pitch_offset_deg = 90.0;
}
}
void
FGViewer::setGoalHeadingOffset_deg (double goal_heading_offset_deg)
{
_dirty = true;
_goal_heading_offset_deg = goal_heading_offset_deg;
while ( _goal_heading_offset_deg < 0.0 ) {
_goal_heading_offset_deg += 360;
}
while ( _goal_heading_offset_deg > 360 ) {
_goal_heading_offset_deg -= 360;
}
}
void
FGViewer::setOrientationOffsets (double roll_offset_deg, double pitch_offset_deg, double heading_offset_deg)
{
_dirty = true;
_roll_offset_deg = roll_offset_deg;
_pitch_offset_deg = pitch_offset_deg;
_heading_offset_deg = heading_offset_deg;
}
double *
FGViewer::get_absolute_view_pos ()
{
if (_dirty)
recalc();
return _absolute_view_pos;
}
float *
FGViewer::getRelativeViewPos ()
{
if (_dirty)
recalc();
return _relative_view_pos;
}
float *
FGViewer::getZeroElevViewPos ()
{
if (_dirty)
recalc();
return _zero_elev_view_pos;
}
void
FGViewer::updateFromModelLocation (FGLocation * location)
{
sgCopyMat4(LOCAL, location->getCachedTransformMatrix());
}
void
FGViewer::recalcOurOwnLocation (FGLocation * location, double lon_deg, double lat_deg, double alt_ft,
double roll_deg, double pitch_deg, double heading_deg)
{
// update from our own data...
location->setPosition( lon_deg, lat_deg, alt_ft );
location->setOrientation( roll_deg, pitch_deg, heading_deg );
sgCopyMat4(LOCAL, location->getTransformMatrix());
}
// recalc() is done every time one of the setters is called (making the
// cached data "dirty") on the next "get". It calculates all the outputs
// for viewer.
void
FGViewer::recalc ()
{
if (_type == FG_LOOKFROM) {
recalcLookFrom();
} else {
recalcLookAt();
}
set_clean();
}
// recalculate for LookFrom view type...
void
FGViewer::recalcLookFrom ()
{
sgVec3 right, forward;
sgVec3 eye_pos;
sgVec3 position_offset; // eye position offsets (xyz)
// LOOKFROM mode...
// Update location data...
if ( _from_model ) {
// update or data from model location
updateFromModelLocation(_location);
} else {
// update from our own data...
recalcOurOwnLocation( _location, _lon_deg, _lat_deg, _alt_ft,
_roll_deg, _pitch_deg, _heading_deg );
}
// copy data from location class to local items...
copyLocationData();
// make sg vectors view up, right and forward vectors from LOCAL
sgSetVec3( _view_up, LOCAL[2][0], LOCAL[2][1], LOCAL[2][2] );
sgSetVec3( right, LOCAL[1][0], LOCAL[1][1], LOCAL[1][2] );
sgSetVec3( forward, -LOCAL[0][0], -LOCAL[0][1], -LOCAL[0][2] );
// Note that when in "lookfrom" view the "view up" vector is always applied
// to the viewer. View up is based on verticle of the aircraft itself. (see
// "LOCAL" matrix above)
// Orientation Offsets matrix
MakeVIEW_OFFSET( VIEW_OFFSET,
_heading_offset_deg * SG_DEGREES_TO_RADIANS, _view_up,
_pitch_offset_deg * SG_DEGREES_TO_RADIANS, right );
// Make the VIEW matrix.
sgSetVec4(VIEW[0], right[0], right[1], right[2],SG_ZERO);
sgSetVec4(VIEW[1], forward[0], forward[1], forward[2],SG_ZERO);
sgSetVec4(VIEW[2], _view_up[0], _view_up[1], _view_up[2],SG_ZERO);
sgSetVec4(VIEW[3], SG_ZERO, SG_ZERO, SG_ZERO,SG_ONE);
// rotate matrix to get a matrix to apply Eye Position Offsets
sgMat4 VIEW_UP; // L0 forward L1 right L2 up
sgCopyVec4(VIEW_UP[0], LOCAL[1]);
sgCopyVec4(VIEW_UP[1], LOCAL[2]);
sgCopyVec4(VIEW_UP[2], LOCAL[0]);
sgZeroVec4(VIEW_UP[3]);
// Eye Position Offsets to vector
sgSetVec3( position_offset, _x_offset_m, _y_offset_m, _z_offset_m );
sgXformVec3( position_offset, position_offset, VIEW_UP);
// add the offsets including rotations to the translation vector
sgAddVec3( _view_pos, position_offset );
// multiply the OFFSETS (for heading and pitch) into the VIEW
sgPostMultMat4(VIEW, VIEW_OFFSET);
// add the position data to the matrix
sgSetVec4(VIEW[3], _view_pos[0], _view_pos[1], _view_pos[2],SG_ONE);
}
void
FGViewer::recalcLookAt ()
{
sgVec3 right;
sgVec3 eye_pos, at_pos;
sgVec3 position_offset; // eye position offsets (xyz)
// The position vectors originate from the view point or target location
// depending on the type of view.
// LOOKAT mode...
// Update location data for target...
if ( _at_model ) {
// update or data from model location
updateFromModelLocation(_target_location);
} else {
// if not model then calculate our own target position...
recalcOurOwnLocation( _target_location, _target_lon_deg, _target_lat_deg, _target_alt_ft,
_target_roll_deg, _target_pitch_deg, _target_heading_deg );
}
// calculate the "at" target object positon relative to eye or view's tile center...
sgdVec3 dVec3;
sgdSetVec3(dVec3, _location->get_tile_center()[0], _location->get_tile_center()[1], _location->get_tile_center()[2]);
sgdSubVec3(dVec3, _target_location->get_absolute_view_pos(), dVec3 );
sgSetVec3(at_pos, dVec3[0], dVec3[1], dVec3[2]);
// Update location data for eye...
if ( _from_model ) {
// update or data from model location
updateFromModelLocation(_location);
} else {
// update from our own data, just the rotation here...
recalcOurOwnLocation( _location, _lon_deg, _lat_deg, _alt_ft,
_roll_deg, _pitch_deg, _heading_deg );
}
// save the eye positon...
sgCopyVec3(eye_pos, _location->get_view_pos());
// copy data from location class to local items...
copyLocationData();
// make sg vectors view up, right and forward vectors from LOCAL
sgSetVec3( _view_up, LOCAL[2][0], LOCAL[2][1], LOCAL[2][2] );
sgSetVec3( right, LOCAL[1][0], LOCAL[1][1], LOCAL[1][2] );
// Note that when in "lookat" view the "world up" vector is always applied
// to the viewer. World up is based on verticle at a given lon/lat (see
// matrix "UP" above).
// Orientation Offsets matrix
MakeVIEW_OFFSET( VIEW_OFFSET,
(_heading_offset_deg -_heading_deg) * SG_DEGREES_TO_RADIANS, _world_up,
_pitch_offset_deg * SG_DEGREES_TO_RADIANS, right );
// add in the Orientation Offsets here
sgSetVec3( position_offset, _x_offset_m, _y_offset_m, _z_offset_m );
sgXformVec3( position_offset, position_offset, UP);
sgXformVec3( position_offset, position_offset, VIEW_OFFSET );
// add the Position offsets from object to the eye position
sgAddVec3( eye_pos, eye_pos, position_offset );
// Make the VIEW matrix for a "LOOKAT".
sgMakeLookAtMat4( VIEW, eye_pos, at_pos, _view_up );
}
// copy results from location class to viewer...
// FIXME: some of these should be changed to reference directly to FGLocation...
void
FGViewer::copyLocationData()
{
// Get our friendly vectors from the eye location...
sgCopyVec3(_zero_elev_view_pos, _location->get_zero_elev());
sgCopyVec3(_relative_view_pos, _location->get_view_pos());
sgdCopyVec3(_absolute_view_pos, _location->get_absolute_view_pos());
sgCopyMat4(UP, _location->getCachedUpMatrix());
sgCopyVec3(_world_up, _location->get_world_up());
// these are the vectors that the sun and moon code like to get...
sgCopyVec3(_surface_east, _location->get_surface_east());
sgCopyVec3(_surface_south, _location->get_surface_south());
// Update viewer's postion data for the eye location...
_lon_deg = _location->getLongitude_deg();
_lat_deg = _location->getLatitude_deg();
_alt_ft = _location->getAltitudeASL_ft();
_roll_deg = _location->getRoll_deg();
_pitch_deg = _location->getPitch_deg();
_heading_deg = _location->getHeading_deg();
// Update viewer's postion data for the target (at object) location
if (_type == FG_LOOKAT) {
_target_lon_deg = _target_location->getLongitude_deg();
_target_lat_deg = _target_location->getLatitude_deg();
_target_alt_ft = _target_location->getAltitudeASL_ft();
_target_roll_deg = _target_location->getRoll_deg();
_target_pitch_deg = _target_location->getPitch_deg();
_target_heading_deg = _target_location->getHeading_deg();
}
// copy coordinates to outputs for viewer...
sgCopyVec3(_zero_elev, _zero_elev_view_pos);
sgCopyVec3(_view_pos, _relative_view_pos);
}
double
FGViewer::get_h_fov()
{
switch (_scaling_type) {
case FG_SCALING_WIDTH: // h_fov == fov
return _fov_deg;
case FG_SCALING_MAX:
if (_aspect_ratio < 1.0) {
// h_fov == fov
return _fov_deg;
} else {
// v_fov == fov
return atan(tan(_fov_deg/2 * SG_DEGREES_TO_RADIANS) / _aspect_ratio) *
SG_RADIANS_TO_DEGREES * 2;
}
default:
assert(false);
}
return 0.0;
}
double
FGViewer::get_v_fov()
{
switch (_scaling_type) {
case FG_SCALING_WIDTH: // h_fov == fov
return atan(tan(_fov_deg/2 * SG_DEGREES_TO_RADIANS) * _aspect_ratio) *
SG_RADIANS_TO_DEGREES * 2;
case FG_SCALING_MAX:
if (_aspect_ratio < 1.0) {
// h_fov == fov
return atan(tan(_fov_deg/2 * SG_DEGREES_TO_RADIANS) * _aspect_ratio) *
SG_RADIANS_TO_DEGREES * 2;
} else {
// v_fov == fov
return _fov_deg;
}
default:
assert(false);
}
return 0.0;
}
void
FGViewer::update (double dt)
{
int i;
int dt_ms = int(dt * 1000);
for ( i = 0; i < dt_ms; i++ ) {
if ( fabs( _goal_heading_offset_deg - _heading_offset_deg) < 1 ) {
setHeadingOffset_deg( _goal_heading_offset_deg );
break;
} else {
// move current_view.headingoffset towards
// current_view.goal_view_offset
if ( _goal_heading_offset_deg > _heading_offset_deg )
{
if ( _goal_heading_offset_deg - _heading_offset_deg < 180 ){
incHeadingOffset_deg( 0.5 );
} else {
incHeadingOffset_deg( -0.5 );
}
} else {
if ( _heading_offset_deg - _goal_heading_offset_deg < 180 ){
incHeadingOffset_deg( -0.5 );
} else {
incHeadingOffset_deg( 0.5 );
}
}
if ( _heading_offset_deg > 360 ) {
incHeadingOffset_deg( -360 );
} else if ( _heading_offset_deg < 0 ) {
incHeadingOffset_deg( 360 );
}
}
}
for ( i = 0; i < dt_ms; i++ ) {
if ( fabs( _goal_pitch_offset_deg - _pitch_offset_deg ) < 1 ) {
setPitchOffset_deg( _goal_pitch_offset_deg );
break;
} else {
// move current_view.pitch_offset_deg towards
// current_view.goal_pitch_offset
if ( _goal_pitch_offset_deg > _pitch_offset_deg )
{
incPitchOffset_deg( 1.0 );
} else {
incPitchOffset_deg( -1.0 );
}
if ( _pitch_offset_deg > 90 ) {
setPitchOffset_deg(90);
} else if ( _pitch_offset_deg < -90 ) {
setPitchOffset_deg( -90 );
}
}
}
}