SolarSystem.cpp   SolarSystem.cpp 
/* /*
* Stellarium * Stellarium
* Copyright (C) 2002 Fabien Chereau * Copyright (C) 2002 Fabien Chereau
* Copyright (C) 2010 Bogdan Marinov * Copyright (C) 2010 Bogdan Marinov
* Copyright (C) 2011 Alexander Wolf
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2 * as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version. * of the License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software * along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, U SA. * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, U SA.
*/ */
#include "SolarSystem.hpp" #include "SolarSystem.hpp"
#include "StelTexture.hpp" #include "StelTexture.hpp"
#include "stellplanet.h" #include "stellplanet.h"
#include "Orbit.hpp" #include "Orbit.hpp"
#include "StelNavigator.hpp"
#include "StelProjector.hpp" #include "StelProjector.hpp"
#include "StelApp.hpp" #include "StelApp.hpp"
#include "StelCore.hpp" #include "StelCore.hpp"
#include "StelTextureMgr.hpp" #include "StelTextureMgr.hpp"
#include "StelObjectMgr.hpp" #include "StelObjectMgr.hpp"
#include "StelLocaleMgr.hpp" #include "StelLocaleMgr.hpp"
#include "StelSkyCultureMgr.hpp" #include "StelSkyCultureMgr.hpp"
#include "StelFileMgr.hpp" #include "StelFileMgr.hpp"
#include "StelModuleMgr.hpp" #include "StelModuleMgr.hpp"
#include "StelIniParser.hpp" #include "StelIniParser.hpp"
#include "Planet.hpp" #include "Planet.hpp"
#include "MinorPlanet.hpp" #include "MinorPlanet.hpp"
#include "Comet.hpp" #include "Comet.hpp"
#include "StelNavigator.hpp"
#include "StelSkyDrawer.hpp" #include "StelSkyDrawer.hpp"
#include "StelUtils.hpp" #include "StelUtils.hpp"
#include "StelPainter.hpp" #include "StelPainter.hpp"
#include "TrailGroup.hpp" #include "TrailGroup.hpp"
#include "RefractionExtinction.hpp"
#include <functional> #include <functional>
#include <algorithm> #include <algorithm>
#include <QTextStream> #include <QTextStream>
#include <QSettings> #include <QSettings>
#include <QVariant> #include <QVariant>
#include <QString> #include <QString>
#include <QStringList> #include <QStringList>
#include <QMap> #include <QMap>
skipping to change at line 130 skipping to change at line 132
setFlagHints(conf->value("astro/flag_planets_hints").toBool()); setFlagHints(conf->value("astro/flag_planets_hints").toBool());
setFlagLabels(conf->value("astro/flag_planets_labels", true).toBool( )); setFlagLabels(conf->value("astro/flag_planets_labels", true).toBool( ));
setLabelsAmount(conf->value("astro/labels_amount", 3.).toFloat()); setLabelsAmount(conf->value("astro/labels_amount", 3.).toFloat());
setFlagOrbits(conf->value("astro/flag_planets_orbits").toBool()); setFlagOrbits(conf->value("astro/flag_planets_orbits").toBool());
setFlagLightTravelTime(conf->value("astro/flag_light_travel_time", f alse).toBool()); setFlagLightTravelTime(conf->value("astro/flag_light_travel_time", f alse).toBool());
recreateTrails(); recreateTrails();
setFlagTrails(conf->value("astro/flag_object_trails", false).toBool( )); setFlagTrails(conf->value("astro/flag_object_trails", false).toBool( ));
GETSTELMODULE(StelObjectMgr)->registerStelObjectMgr(this); StelObjectMgr *objectManager = GETSTELMODULE(StelObjectMgr);
objectManager->registerStelObjectMgr(this);
connect(objectManager, SIGNAL(selectedObjectChanged(StelModule::Stel
ModuleSelectAction)),
this, SLOT(selectedObjectChange(StelModule::StelModu
leSelectAction)));
texPointer = StelApp::getInstance().getTextureManager().createTextur e("textures/pointeur4.png"); texPointer = StelApp::getInstance().getTextureManager().createTextur e("textures/pointeur4.png");
Planet::hintCircleTex = StelApp::getInstance().getTextureManager().c reateTexture("textures/planet-indicator.png"); Planet::hintCircleTex = StelApp::getInstance().getTextureManager().c reateTexture("textures/planet-indicator.png");
StelApp *app = &StelApp::getInstance();
connect(app, SIGNAL(languageChanged()), this, SLOT(updateI18n()));
connect(app, SIGNAL(colorSchemeChanged(const QString&)), this, SLOT(
setStelStyle(const QString&)));
} }
void SolarSystem::recreateTrails() void SolarSystem::recreateTrails()
{ {
// Create a trail group containing all the planets orbiting the sun (not including satellites) // Create a trail group containing all the planets orbiting the sun (not including satellites)
if (allTrails!=NULL) if (allTrails!=NULL)
delete allTrails; delete allTrails;
allTrails = new TrailGroup(365.f); allTrails = new TrailGroup(365.f);
foreach (const PlanetP& p, getSun()->satellites) foreach (const PlanetP& p, getSun()->satellites)
{ {
allTrails->addObject((QSharedPointer<StelObject>)p, &trailCo lor); allTrails->addObject((QSharedPointer<StelObject>)p, &trailCo lor);
} }
} }
void SolarSystem::drawPointer(const StelCore* core) void SolarSystem::drawPointer(const StelCore* core)
{ {
const StelNavigator* nav = core->getNavigator();
const StelProjectorP prj = core->getProjection(StelCore::FrameJ2000) ; const StelProjectorP prj = core->getProjection(StelCore::FrameJ2000) ;
const QList<StelObjectP> newSelected = GETSTELMODULE(StelObjectMgr)- >getSelectedObject("Planet"); const QList<StelObjectP> newSelected = GETSTELMODULE(StelObjectMgr)- >getSelectedObject("Planet");
if (!newSelected.empty()) if (!newSelected.empty())
{ {
const StelObjectP obj = newSelected[0]; const StelObjectP obj = newSelected[0];
Vec3d pos=obj->getJ2000EquatorialPos(nav); Vec3d pos=obj->getJ2000EquatorialPos(core);
Vec3d screenpos; Vec3d screenpos;
// Compute 2D pos and return if outside screen // Compute 2D pos and return if outside screen
if (!prj->project(pos, screenpos)) if (!prj->project(pos, screenpos))
return; return;
StelPainter sPainter(prj); StelPainter sPainter(prj);
sPainter.setColor(1.0f,0.3f,0.3f); sPainter.setColor(1.0f,0.3f,0.3f);
float size = obj->getAngularSize(core)*M_PI/180.*prj->getPix elPerRadAtCenter()*2.; float size = obj->getAngularSize(core)*M_PI/180.*prj->getPix elPerRadAtCenter()*2.;
size+=40.f + 10.f*std::sin(2.f * StelApp::getInstance().getT otalRunTime()); size+=40.f + 10.f*std::sin(2.f * StelApp::getInstance().getT otalRunTime());
skipping to change at line 360 skipping to change at line 370
{ {
qWarning() << "ERROR : can't find parent sol ar system body for " << englishName; qWarning() << "ERROR : can't find parent sol ar system body for " << englishName;
//abort(); //abort();
continue; continue;
} }
} }
const QString funcName = pd.value(secname+"/coord_func").toS tring(); const QString funcName = pd.value(secname+"/coord_func").toS tring();
posFuncType posfunc=NULL; posFuncType posfunc=NULL;
void* userDataPtr=NULL; void* userDataPtr=NULL;
OsulatingFunctType *osculatingFunc = 0; OsculatingFunctType *osculatingFunc = 0;
bool closeOrbit = pd.value(secname+"/closeOrbit", true).toBo ol(); bool closeOrbit = pd.value(secname+"/closeOrbit", true).toBo ol();
if (funcName=="ell_orbit") if (funcName=="ell_orbit")
{ {
// Read the orbital elements // Read the orbital elements
const double epoch = pd.value(secname+"/orbit_Epoch" ,J2000).toDouble(); const double epoch = pd.value(secname+"/orbit_Epoch" ,J2000).toDouble();
const double eccentricity = pd.value(secname+"/orbit _Eccentricity").toDouble(); const double eccentricity = pd.value(secname+"/orbit _Eccentricity").toDouble();
if (eccentricity >= 1.0) closeOrbit = false; if (eccentricity >= 1.0) closeOrbit = false;
double pericenterDistance = pd.value(secname+"/orbit _PericenterDistance",-1e100).toDouble(); double pericenterDistance = pd.value(secname+"/orbit _PericenterDistance",-1e100).toDouble();
double semi_major_axis; double semi_major_axis;
skipping to change at line 428 skipping to change at line 438
double mean_anomaly = pd.value(secname+"/orbit_MeanA nomaly",-1e100).toDouble(); double mean_anomaly = pd.value(secname+"/orbit_MeanA nomaly",-1e100).toDouble();
double mean_longitude; double mean_longitude;
if (mean_anomaly <= -1e100) { if (mean_anomaly <= -1e100) {
mean_longitude = pd.value(secname+"/orbit_Me anLongitude").toDouble()*(M_PI/180.0); mean_longitude = pd.value(secname+"/orbit_Me anLongitude").toDouble()*(M_PI/180.0);
mean_anomaly = mean_longitude - long_of_peri center; mean_anomaly = mean_longitude - long_of_peri center;
} else { } else {
mean_anomaly *= (M_PI/180.0); mean_anomaly *= (M_PI/180.0);
mean_longitude = mean_anomaly + long_of_peri center; mean_longitude = mean_anomaly + long_of_peri center;
} }
// when the parent is the sun use ecliptic rathe tha n sun equator: // when the parent is the sun use ecliptic rather th an sun equator:
const double parentRotObliquity = parent->getParent( ) const double parentRotObliquity = parent->getParent( )
? parent->getRotObliquity() ? parent->getRotObliquity()
: 0.0; : 0.0;
const double parent_rot_asc_node = parent->getParent () const double parent_rot_asc_node = parent->getParent ()
? parent->getRotAscendingnode() ? parent->getRotAscendingnode()
: 0.0; : 0.0;
double parent_rot_j2000_longitude = 0.0; double parent_rot_j2000_longitude = 0.0;
if (parent->getParent()) { if (parent->getParent()) {
const double c_obl = cos(parentRotObliquity) ; const double c_obl = cos(parentRotObliquity) ;
const double s_obl = sin(parentRotObliquity) ; const double s_obl = sin(parentRotObliquity) ;
const double c_nod = cos(parent_rot_asc_node ); const double c_nod = cos(parent_rot_asc_node );
const double s_nod = sin(parent_rot_asc_node ); const double s_nod = sin(parent_rot_asc_node );
const Vec3d OrbitAxis0( c_nod, s_nod, 0.0); const Vec3d OrbitAxis0( c_nod, s_nod, 0.0);
const Vec3d OrbitAxis1(-s_nod*c_obl, c_nod*c _obl,s_obl); const Vec3d OrbitAxis1(-s_nod*c_obl, c_nod*c _obl,s_obl);
const Vec3d OrbitPole( s_nod*s_obl,-c_nod*s _obl,c_obl); const Vec3d OrbitPole( s_nod*s_obl,-c_nod*s _obl,c_obl);
const Vec3d J2000Pole(StelNavigator::matJ200 0ToVsop87.multiplyWithoutTranslation(Vec3d(0,0,1))); const Vec3d J2000Pole(StelCore::matJ2000ToVs op87.multiplyWithoutTranslation(Vec3d(0,0,1)));
Vec3d J2000NodeOrigin(J2000Pole^OrbitPole); Vec3d J2000NodeOrigin(J2000Pole^OrbitPole);
J2000NodeOrigin.normalize(); J2000NodeOrigin.normalize();
parent_rot_j2000_longitude = atan2(J2000Node Origin*OrbitAxis1,J2000NodeOrigin*OrbitAxis0); parent_rot_j2000_longitude = atan2(J2000Node Origin*OrbitAxis1,J2000NodeOrigin*OrbitAxis0);
} }
// Create an elliptical orbit // Create an elliptical orbit
EllipticalOrbit *orb = new EllipticalOrbit(pericente rDistance, EllipticalOrbit *orb = new EllipticalOrbit(pericente rDistance,
eccentricity, eccentricity,
inclination, inclination,
ascending_node, ascending_node,
skipping to change at line 552 skipping to change at line 562
: 0.0; : 0.0;
double parent_rot_j2000_longitude = 0.0; double parent_rot_j2000_longitude = 0.0;
if (parent->getParent()) { if (parent->getParent()) {
const double c_obl = cos( parentRotObliquity); const double c_obl = cos( parentRotObliquity);
const double s_obl = sin( parentRotObliquity); const double s_obl = sin( parentRotObliquity);
const double c_nod = cos( parent_rot_asc_node); const double c_nod = cos( parent_rot_asc_node);
const double s_nod = sin( parent_rot_asc_node); const double s_nod = sin( parent_rot_asc_node);
const Vec3d OrbitAxis0( c _nod, s_nod, 0.0); const Vec3d OrbitAxis0( c _nod, s_nod, 0.0);
const Vec3d OrbitAxis1(-s _nod*c_obl, c_nod*c_obl,s_obl); const Vec3d OrbitAxis1(-s _nod*c_obl, c_nod*c_obl,s_obl);
const Vec3d OrbitPole( s _nod*s_obl,-c_nod*s_obl,c_obl); const Vec3d OrbitPole( s _nod*s_obl,-c_nod*s_obl,c_obl);
const Vec3d J2000Pole(Ste lNavigator::matJ2000ToVsop87.multiplyWithoutTranslation(Vec3d(0,0,1))); const Vec3d J2000Pole(Ste lCore::matJ2000ToVsop87.multiplyWithoutTranslation(Vec3d(0,0,1)));
Vec3d J2000NodeOrigin(J20 00Pole^OrbitPole); Vec3d J2000NodeOrigin(J20 00Pole^OrbitPole);
J2000NodeOrigin.normalize (); J2000NodeOrigin.normalize ();
parent_rot_j2000_longitud e = atan2(J2000NodeOrigin*OrbitAxis1,J2000NodeOrigin*OrbitAxis0); parent_rot_j2000_longitud e = atan2(J2000NodeOrigin*OrbitAxis1,J2000NodeOrigin*OrbitAxis0);
} }
CometOrbit *orb = new CometOrbit(pericenterDistance, CometOrbit *orb = new CometOrbit(pericenterDistance,
eccentricity, eccentricity,
inclination, inclination,
ascending_node, ascending_node,
arg_of_pericenter, arg_of_pericenter,
time_at_pericenter, time_at_pericenter,
skipping to change at line 808 skipping to change at line 818
// NB: N pole as defined by IAU (NOT right hand rotation rul e) // NB: N pole as defined by IAU (NOT right hand rotation rul e)
// NB: J2000 epoch // NB: J2000 epoch
double J2000NPoleRA = pd.value(secname+"/rot_pole_ra", 0.).t oDouble()*M_PI/180.; double J2000NPoleRA = pd.value(secname+"/rot_pole_ra", 0.).t oDouble()*M_PI/180.;
double J2000NPoleDE = pd.value(secname+"/rot_pole_de", 0.).t oDouble()*M_PI/180.; double J2000NPoleDE = pd.value(secname+"/rot_pole_de", 0.).t oDouble()*M_PI/180.;
if(J2000NPoleRA || J2000NPoleDE) if(J2000NPoleRA || J2000NPoleDE)
{ {
Vec3d J2000NPole; Vec3d J2000NPole;
StelUtils::spheToRect(J2000NPoleRA,J2000NPoleDE,J200 0NPole); StelUtils::spheToRect(J2000NPoleRA,J2000NPoleDE,J200 0NPole);
Vec3d vsop87Pole(StelNavigator::matJ2000ToVsop87.mul tiplyWithoutTranslation(J2000NPole)); Vec3d vsop87Pole(StelCore::matJ2000ToVsop87.multiply WithoutTranslation(J2000NPole));
double ra, de; double ra, de;
StelUtils::rectToSphe(&ra, &de, vsop87Pole); StelUtils::rectToSphe(&ra, &de, vsop87Pole);
rotObliquity = (M_PI_2 - de); rotObliquity = (M_PI_2 - de);
rotAscNode = (ra + M_PI_2); rotAscNode = (ra + M_PI_2);
// qDebug() << "\tCalculated rotational obliquity: " << rotObliquity*180./M_PI << endl; // qDebug() << "\tCalculated rotational obliquity: " << rotObliquity*180./M_PI << endl;
// qDebug() << "\tCalculated rotational ascending no de: " << rotAscNode*180./M_PI << endl; // qDebug() << "\tCalculated rotational ascending no de: " << rotAscNode*180./M_PI << endl;
} }
skipping to change at line 916 skipping to change at line 926
} }
}; };
// Draw all the elements of the solar system // Draw all the elements of the solar system
// We are supposed to be in heliocentric coordinate // We are supposed to be in heliocentric coordinate
void SolarSystem::draw(StelCore* core) void SolarSystem::draw(StelCore* core)
{ {
if (!flagShow) if (!flagShow)
return; return;
StelNavigator* nav = core->getNavigator();
// Compute each Planet distance to the observer // Compute each Planet distance to the observer
Vec3d obsHelioPos = nav->getObserverHeliocentricEclipticPos(); Vec3d obsHelioPos = core->getObserverHeliocentricEclipticPos();
foreach (PlanetP p, systemPlanets) foreach (PlanetP p, systemPlanets)
{ {
p->computeDistance(obsHelioPos); p->computeDistance(obsHelioPos);
} }
// And sort them from the furthest to the closest // And sort them from the furthest to the closest
sort(systemPlanets.begin(),systemPlanets.end(),biggerDistance()); sort(systemPlanets.begin(),systemPlanets.end(),biggerDistance());
if (trailFader.getInterstate()>0.0000001f) if (trailFader.getInterstate()>0.0000001f)
skipping to change at line 1004 skipping to change at line 1012
// Search if any Planet is close to position given in earth equatorial posi tion and return the distance // Search if any Planet is close to position given in earth equatorial posi tion and return the distance
StelObjectP SolarSystem::search(Vec3d pos, const StelCore* core) const StelObjectP SolarSystem::search(Vec3d pos, const StelCore* core) const
{ {
pos.normalize(); pos.normalize();
PlanetP closest; PlanetP closest;
double cos_angle_closest = 0.; double cos_angle_closest = 0.;
Vec3d equPos; Vec3d equPos;
foreach (const PlanetP& p, systemPlanets) foreach (const PlanetP& p, systemPlanets)
{ {
equPos = p->getEquinoxEquatorialPos(core->getNavigator()); equPos = p->getEquinoxEquatorialPos(core);
equPos.normalize(); equPos.normalize();
double cos_ang_dist = equPos*pos; double cos_ang_dist = equPos*pos;
if (cos_ang_dist>cos_angle_closest) if (cos_ang_dist>cos_angle_closest)
{ {
closest = p; closest = p;
cos_angle_closest = cos_ang_dist; cos_angle_closest = cos_ang_dist;
} }
} }
if (cos_angle_closest>0.999) if (cos_angle_closest>0.999)
skipping to change at line 1028 skipping to change at line 1036
else return StelObjectP(); else return StelObjectP();
} }
// Return a stl vector containing the planets located inside the limFov cir cle around position v // Return a stl vector containing the planets located inside the limFov cir cle around position v
QList<StelObjectP> SolarSystem::searchAround(const Vec3d& vv, double limitF ov, const StelCore* core) const QList<StelObjectP> SolarSystem::searchAround(const Vec3d& vv, double limitF ov, const StelCore* core) const
{ {
QList<StelObjectP> result; QList<StelObjectP> result;
if (!getFlagPlanets()) if (!getFlagPlanets())
return result; return result;
Vec3d v = core->getNavigator()->j2000ToEquinoxEqu(vv); Vec3d v = core->j2000ToEquinoxEqu(vv);
v.normalize(); v.normalize();
double cosLimFov = std::cos(limitFov * M_PI/180.); double cosLimFov = std::cos(limitFov * M_PI/180.);
Vec3d equPos; Vec3d equPos;
foreach (const PlanetP& p, systemPlanets) foreach (const PlanetP& p, systemPlanets)
{ {
equPos = p->getEquinoxEquatorialPos(core->getNavigator()); equPos = p->getEquinoxEquatorialPos(core);
equPos.normalize(); equPos.normalize();
if (equPos*v>=cosLimFov) if (equPos*v>=cosLimFov)
{ {
result.append(qSharedPointerCast<StelObject>(p)); result.append(qSharedPointerCast<StelObject>(p));
} }
} }
return result; return result;
} }
// Update i18 names from english names according to passed translator // Update i18 names from english names according to current translator
void SolarSystem::updateI18n() void SolarSystem::updateI18n()
{ {
StelTranslator& trans = StelApp::getInstance().getLocaleMgr().getApp StelTranslator(); StelTranslator& trans = StelApp::getInstance().getLocaleMgr().getApp StelTranslator();
foreach (PlanetP p, systemPlanets) foreach (PlanetP p, systemPlanets)
p->translateName(trans); p->translateName(trans);
} }
QString SolarSystem::getPlanetHashString(void) QString SolarSystem::getPlanetHashString(void)
{ {
QString str; QString str;
skipping to change at line 1208 skipping to change at line 1216
if (constw==objw) if (constw==objw)
{ {
result << p->getNameI18n(); result << p->getNameI18n();
if (result.size()==maxNbItem) if (result.size()==maxNbItem)
return result; return result;
} }
} }
return result; return result;
} }
void SolarSystem::selectedObjectChangeCallBack(StelModuleSelectAction) void SolarSystem::selectedObjectChange(StelModule::StelModuleSelectAction)
{ {
const QList<StelObjectP> newSelected = GETSTELMODULE(StelObjectMgr)- >getSelectedObject("Planet"); const QList<StelObjectP> newSelected = GETSTELMODULE(StelObjectMgr)- >getSelectedObject("Planet");
if (!newSelected.empty()) if (!newSelected.empty())
setSelected(qSharedPointerCast<Planet>(newSelected[0])); setSelected(qSharedPointerCast<Planet>(newSelected[0]));
} }
// Activate/Deactivate planets display // Activate/Deactivate planets display
void SolarSystem::setFlagPlanets(bool b) void SolarSystem::setFlagPlanets(bool b)
{ {
flagShow=b; flagShow=b;
skipping to change at line 1262 skipping to change at line 1270
// Get the list of all the planet english names // Get the list of all the planet english names
QStringList SolarSystem::getAllPlanetEnglishNames() const QStringList SolarSystem::getAllPlanetEnglishNames() const
{ {
QStringList res; QStringList res;
foreach (const PlanetP& p, systemPlanets) foreach (const PlanetP& p, systemPlanets)
res.append(p->englishName); res.append(p->englishName);
return res; return res;
} }
QStringList SolarSystem::getAllPlanetLocalizedNames() const
{
QStringList res;
foreach (const PlanetP& p, systemPlanets)
res.append(p->nameI18);
return res;
}
void SolarSystem::reloadPlanets() void SolarSystem::reloadPlanets()
{ {
//Save flag states //Save flag states
bool flagScaleMoon = getFlagMoonScale(); bool flagScaleMoon = getFlagMoonScale();
float moonScale = getMoonScale(); float moonScale = getMoonScale();
bool flagPlanets = getFlagPlanets(); bool flagPlanets = getFlagPlanets();
bool flagHints = getFlagHints(); bool flagHints = getFlagHints();
bool flagLabels = getFlagLabels(); bool flagLabels = getFlagLabels();
bool flagOrbits = getFlagOrbits(); bool flagOrbits = getFlagOrbits();
 End of changes. 21 change blocks. 
18 lines changed or deleted 37 lines changed or added

This html diff was produced by rfcdiff 1.41. The latest version is available from http://tools.ietf.org/tools/rfcdiff/