2010-06-24 15:09:33 +00:00
|
|
|
// inputvalue.hxx - provide input to autopilot components
|
|
|
|
//
|
|
|
|
// Written by Torsten Dreyer
|
|
|
|
// Copyright (C) 2010 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.
|
|
|
|
//
|
|
|
|
|
2012-05-04 23:08:20 +00:00
|
|
|
#include <cstdlib>
|
|
|
|
|
2010-06-24 15:09:33 +00:00
|
|
|
#include "inputvalue.hxx"
|
2012-05-04 23:08:20 +00:00
|
|
|
|
2020-12-09 17:06:25 +00:00
|
|
|
#include <simgear/misc/strutils.hxx>
|
|
|
|
|
2010-06-24 15:09:33 +00:00
|
|
|
using namespace FGXMLAutopilot;
|
|
|
|
|
2014-02-09 16:40:19 +00:00
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
PeriodicalValue::PeriodicalValue( SGPropertyNode& prop_root,
|
|
|
|
SGPropertyNode& cfg )
|
2010-06-24 15:09:33 +00:00
|
|
|
{
|
2014-02-09 16:40:19 +00:00
|
|
|
SGPropertyNode_ptr minNode = cfg.getChild( "min" );
|
|
|
|
SGPropertyNode_ptr maxNode = cfg.getChild( "max" );
|
|
|
|
if( !minNode || !maxNode )
|
|
|
|
{
|
|
|
|
SG_LOG
|
|
|
|
(
|
|
|
|
SG_AUTOPILOT,
|
|
|
|
SG_ALERT,
|
|
|
|
"periodical defined, but no <min> and/or <max> tag. Period ignored."
|
|
|
|
);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
minPeriod = new InputValue(prop_root, *minNode);
|
|
|
|
maxPeriod = new InputValue(prop_root, *maxNode);
|
2010-06-24 15:09:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-09 16:40:19 +00:00
|
|
|
//------------------------------------------------------------------------------
|
2012-02-12 10:41:21 +00:00
|
|
|
double PeriodicalValue::normalize( double value ) const
|
2010-06-24 15:09:33 +00:00
|
|
|
{
|
2014-02-09 16:40:19 +00:00
|
|
|
return SGMiscd::normalizePeriodic( minPeriod->get_value(),
|
|
|
|
maxPeriod->get_value(),
|
|
|
|
value );
|
2010-06-24 15:09:33 +00:00
|
|
|
}
|
|
|
|
|
2014-02-09 16:40:19 +00:00
|
|
|
//------------------------------------------------------------------------------
|
2012-02-12 10:41:21 +00:00
|
|
|
double PeriodicalValue::normalizeSymmetric( double value ) const
|
|
|
|
{
|
2012-02-15 13:59:10 +00:00
|
|
|
double minValue = minPeriod->get_value();
|
|
|
|
double maxValue = maxPeriod->get_value();
|
|
|
|
|
|
|
|
value = SGMiscd::normalizePeriodic( minValue, maxValue, value );
|
|
|
|
double width_2 = (maxValue - minValue)/2;
|
2012-02-12 10:41:21 +00:00
|
|
|
return value > width_2 ? width_2 - value : value;
|
|
|
|
}
|
|
|
|
|
2014-02-09 16:40:19 +00:00
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
InputValue::InputValue( SGPropertyNode& prop_root,
|
|
|
|
SGPropertyNode& cfg,
|
|
|
|
double value,
|
|
|
|
double offset,
|
|
|
|
double scale ):
|
2010-06-24 15:09:33 +00:00
|
|
|
_value(0.0),
|
|
|
|
_abs(false)
|
|
|
|
{
|
2014-02-09 16:40:19 +00:00
|
|
|
parse(prop_root, cfg, value, offset, scale);
|
2010-06-24 15:09:33 +00:00
|
|
|
}
|
|
|
|
|
2014-02-09 16:40:19 +00:00
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void InputValue::parse( SGPropertyNode& prop_root,
|
|
|
|
SGPropertyNode& cfg,
|
|
|
|
double aValue,
|
|
|
|
double aOffset,
|
|
|
|
double aScale )
|
2010-06-24 15:09:33 +00:00
|
|
|
{
|
2014-02-09 16:40:19 +00:00
|
|
|
_value = aValue;
|
|
|
|
_property = NULL;
|
|
|
|
_offset = NULL;
|
|
|
|
_scale = NULL;
|
|
|
|
_min = NULL;
|
|
|
|
_max = NULL;
|
|
|
|
_periodical = NULL;
|
2010-06-24 15:09:33 +00:00
|
|
|
|
2014-02-09 16:40:19 +00:00
|
|
|
SGPropertyNode * n;
|
2010-06-24 15:09:33 +00:00
|
|
|
|
2014-02-09 16:40:19 +00:00
|
|
|
if( (n = cfg.getChild("condition")) != NULL )
|
|
|
|
_condition = sgReadCondition(&prop_root, n);
|
2010-06-24 15:09:33 +00:00
|
|
|
|
2014-02-09 16:40:19 +00:00
|
|
|
if( (n = cfg.getChild( "scale" )) != NULL )
|
|
|
|
_scale = new InputValue(prop_root, *n, aScale);
|
2010-06-24 15:09:33 +00:00
|
|
|
|
2014-02-09 16:40:19 +00:00
|
|
|
if( (n = cfg.getChild( "offset" )) != NULL )
|
|
|
|
_offset = new InputValue(prop_root, *n, aOffset);
|
2010-06-24 15:09:33 +00:00
|
|
|
|
2014-02-09 16:40:19 +00:00
|
|
|
if( (n = cfg.getChild( "max" )) != NULL )
|
|
|
|
_max = new InputValue(prop_root, *n);
|
2010-06-24 15:09:33 +00:00
|
|
|
|
2014-02-09 16:40:19 +00:00
|
|
|
if( (n = cfg.getChild( "min" )) != NULL )
|
|
|
|
_min = new InputValue(prop_root, *n);
|
2010-06-24 15:09:33 +00:00
|
|
|
|
2014-02-09 16:40:19 +00:00
|
|
|
if( (n = cfg.getChild( "abs" )) != NULL )
|
|
|
|
_abs = n->getBoolValue();
|
2010-06-24 15:09:33 +00:00
|
|
|
|
2014-02-09 16:40:19 +00:00
|
|
|
if( (n = cfg.getChild( "period" )) != NULL )
|
|
|
|
_periodical = new PeriodicalValue(prop_root, *n);
|
2010-06-24 15:09:33 +00:00
|
|
|
|
|
|
|
|
2014-02-09 16:40:19 +00:00
|
|
|
SGPropertyNode *valueNode = cfg.getChild("value");
|
|
|
|
if( valueNode != NULL )
|
|
|
|
_value = valueNode->getDoubleValue();
|
2010-06-24 15:09:33 +00:00
|
|
|
|
2014-02-09 16:40:19 +00:00
|
|
|
if( (n = cfg.getChild("expression")) != NULL )
|
|
|
|
{
|
|
|
|
_expression = SGReadDoubleExpression(&prop_root, n->getChild(0));
|
|
|
|
return;
|
|
|
|
}
|
2010-06-24 15:09:33 +00:00
|
|
|
|
2014-02-09 16:40:19 +00:00
|
|
|
// if no <property> element, check for <prop> element for backwards
|
|
|
|
// compatibility
|
|
|
|
if( (n = cfg.getChild("property"))
|
|
|
|
|| (n = cfg.getChild("prop" )) )
|
|
|
|
{
|
2020-12-09 17:06:25 +00:00
|
|
|
// tolerate leading & trailing whitespace from XML, in the property name
|
|
|
|
const auto trimmed = simgear::strutils::strip(n->getStringValue());
|
|
|
|
_property = prop_root.getNode(trimmed, true);
|
2014-02-09 16:40:19 +00:00
|
|
|
if( valueNode )
|
|
|
|
{
|
|
|
|
// initialize property with given value
|
|
|
|
// if both <prop> and <value> exist
|
|
|
|
double s = get_scale();
|
|
|
|
if( s != 0 )
|
|
|
|
_property->setDoubleValue( (_value - get_offset())/s );
|
|
|
|
else
|
|
|
|
_property->setDoubleValue(0); // if scale is zero, value*scale is zero
|
2010-06-24 15:09:33 +00:00
|
|
|
}
|
2014-02-09 16:40:19 +00:00
|
|
|
|
|
|
|
return;
|
|
|
|
} // of have a <property> or <prop>
|
|
|
|
|
|
|
|
|
|
|
|
if( !valueNode )
|
|
|
|
{
|
|
|
|
// no <value>, <prop> or <expression> element, use text node
|
|
|
|
const char * textnode = cfg.getStringValue();
|
|
|
|
char * endp = NULL;
|
|
|
|
// try to convert to a double value. If the textnode does not start with a number
|
|
|
|
// endp will point to the beginning of the string. We assume this should be
|
|
|
|
// a property name
|
|
|
|
_value = strtod( textnode, &endp );
|
|
|
|
if( endp == textnode )
|
|
|
|
_property = prop_root.getNode(textnode, true);
|
|
|
|
}
|
2010-06-24 15:09:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void InputValue::set_value( double aValue )
|
|
|
|
{
|
|
|
|
if (!_property)
|
|
|
|
return;
|
|
|
|
|
|
|
|
double s = get_scale();
|
|
|
|
if( s != 0 )
|
|
|
|
_property->setDoubleValue( (aValue - get_offset())/s );
|
|
|
|
else
|
|
|
|
_property->setDoubleValue( 0 ); // if scale is zero, value*scale is zero
|
|
|
|
}
|
|
|
|
|
|
|
|
double InputValue::get_value() const
|
|
|
|
{
|
|
|
|
double value = _value;
|
|
|
|
|
|
|
|
if (_expression) {
|
|
|
|
// compute the expression value
|
|
|
|
value = _expression->getValue(NULL);
|
2020-10-11 21:00:44 +00:00
|
|
|
|
|
|
|
if (SGMiscd::isNaN(value)) {
|
|
|
|
SG_LOG(SG_AUTOPILOT, SG_DEV_ALERT, "AP input: read NaN from expression");
|
|
|
|
}
|
2010-06-24 15:09:33 +00:00
|
|
|
} else if( _property != NULL ) {
|
|
|
|
value = _property->getDoubleValue();
|
2020-10-11 21:00:44 +00:00
|
|
|
|
|
|
|
if (SGMiscd::isNaN(value)) {
|
|
|
|
SG_LOG(SG_AUTOPILOT, SG_DEV_ALERT, "AP input: read NaN from:" << _property->getPath() );
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (SGMiscd::isNaN(value)) {
|
|
|
|
SG_LOG(SG_AUTOPILOT, SG_DEV_ALERT, "AP input is NaN." );
|
|
|
|
}
|
2010-06-24 15:09:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if( _scale )
|
|
|
|
value *= _scale->get_value();
|
|
|
|
|
|
|
|
if( _offset )
|
|
|
|
value += _offset->get_value();
|
|
|
|
|
|
|
|
if( _min ) {
|
|
|
|
double m = _min->get_value();
|
|
|
|
if( value < m )
|
|
|
|
value = m;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( _max ) {
|
|
|
|
double m = _max->get_value();
|
|
|
|
if( value > m )
|
|
|
|
value = m;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( _periodical ) {
|
|
|
|
value = _periodical->normalize( value );
|
|
|
|
}
|
2020-10-11 21:00:44 +00:00
|
|
|
|
2010-06-24 15:09:33 +00:00
|
|
|
return _abs ? fabs(value) : value;
|
|
|
|
}
|
|
|
|
|