Scripted AI Objects: Difference between revisions

From FlightGear wiki
Jump to navigation Jump to search
(→‎I. The missile itself: Various fuses)
Line 403: Line 403:
# Launch: rail, dropped, ejected (from the pylons, about 5–25 ft/s)...
# Launch: rail, dropped, ejected (from the pylons, about 5–25 ft/s)...
# Physics: Size, weight, warhead weight, fuel weight, max-g over load etc....
# Physics: Size, weight, warhead weight, fuel weight, max-g over load etc....
# Fusing: Delay, impact, delayed impact, pressure altitude, radar altitude, radar proximity, triggered by navigation computer etc...


=== II. Missile detection ===
=== II. Missile detection ===

Revision as of 11:59, 8 November 2014

Note: This project is currently under active development. And we're looking for volunteers interested in contributing.
So if you'd like to help in one way or another, please get in touch or just help improve the article in the meantime!
Useful Skills:
PropertyList XML File, Property Tree, Nasal scripting, fgcommands, AI traffic


Contributors: Red_Leader and 5H1N0B1

Mentors: Xiii, Hooray and Philosopher (get in touch to learn more)
It's possible that this article hasn't been updated in a while, so to catch up with the latest developments, you are advised not to start working on anything directly related to this without first coordinating your ideas with fellow FlightGear contributors using the FlightGear developers mailing list or the FlightGear forums. See also the talk page.

AI guided weapons system
An F-14B with a fired missile.
An F-14B with a fired missile.
Started in 11/2014
Description Script-able AI guided munitions
Contributor(s) Red_Leader and 5H1N0B1 (since 10/2014)
Status Design discussions & prototyping
Topic branches:
fgdata topics/scriptable-ai-submodule


Objective

To come up with generic Nasal modules and helper classes for creating script-able AI guided munitions/objects in an aircraft-agnostic fashion.

Ideally, multiple instances per aircraft should be supported, including AI aircraft (e.g. for Bombable), while factoring out the code specific to controlling AI objects into a reusable module, so that existing use-cases can be updated accordingly (tanker.nas, fox2.nas, missile.nas and maybe eventually bombable.nas), but also so that new efforts can be easily supported (e.g. UAVs/drones).

The upcoming framework will hopefully also be flexible enough to recursively support sub-payloads which may have their own FDM/guidance logic. Equally, this would involve AI-guided aircraft with AI-guided munitions - e.g. f14-b jets firing fox2/aim9 missiles at each other, or AI aircraft being refueled by an AI tanker etc.

Status (11/2014)

Several prototypes exist already, and we're hoping to unify the code and come up with a generic module that satisfies all main use-cases, while still being sufficiently flexible and extensible. For additional info, please refer to the talk page.

If you'd like to get involved in one way or another, please also get in touch.

Gallery


Here's the snippet of code that was used for these screen shots:

io.include('Nasal/ai/aim9/aim9.missile');
 
Aim9.staticInit(); # static initialization (e.g. read in missile XML specs only once)
 
# use the current position as source
var source = geo.aircraft_position().apply_course_distance(0,100);
# come up with a target position using an offset
var target = geo.aircraft_position().apply_course_distance(0, 20000);
 
# populate the surrounding sky for testing purposes
 for (var i=-80;i<=80;i+=10) {
    var aim9 = Aim9.new(tgt:target, src:source);
    var (lat,lon,alt) = geo.aircraft_position().apply_course_distance(i,650).latlon();
    alt = alt*M2FT + i*3; # vertical offset
 
    aim9.setState( Aim9State.LAUNCH );
    aim9.aiObj.set_pos(lat, lon, alt);
}

Roadmap

  • Prototype dedicated ai.nas module (Red Leader) 40}% completed
  • Prototype a missile.nas module using ai.nas (Red Leader) 20}% completed
  • Create a new Nasal sub module under $FG_ROOT/Nasal/ai and moving ai.nas/missile.nas there (Hooray) Done Done
  • Come up with a simple XML-configurable mechanism for storing missile specs (currently being prototyped by Hooray) 20}% completed
    • maybe extend the XML specs to declare all object states / 3D models for each state (see m2000-5's usage of reload_model() to accomplish this) ?
  • refactor and generalize "missile view" code from missile.nas and move it to FGCamera, so that we can better reuse the code (Marius_A) Pending Pending
  • extend test.nas to add more test cases, for different types of AI guided objects (missiles, bombs, aircraft, drones) () Not done Not done
  • examine the FDM/guidance logic in fox2.nas and missile.nas to split up and generalize things by moving/parameterizing existing code to appropriate base classes (Hooray) 30}% completed
  • Generalize infrastructure modules (ai.nas and missile.nas)
    • Review tanker.nas to ensure that ai.nas is sufficiently generic (Red_Leader and Hooray) 20}% completed
    • Come up with a modified version of tanker.nas using ai.nas, by updating the Tanker class (Red Leader) Pending Pending
    • Determine if we should factor out aircraft specific functionality analogous to missile.nas?
    • Review bombable.nas for missing functionality to add to ai.nas and/or missile.nas (Red Leader) Pending Pending
    • Review fox2.nas and missile.nas to generalize the new unified modules accordingly (suggested by Hooray) Not done Not done
  • identify fdm/guidance logic common to tanker/missile use and generalize the existing code accordingly to move it into proper base classes () Not done Not done
  • Get several aircraft developers involved to update their aircraft accordingly, and ask for feedback Not done Not done

Coding ideas

  • Use forindex/foreach loops in a few places.
  • Using named arguments in calls with many parameters.
  • maketimer() should be favored over settimer().
  • Using isa() for sanity checking.
  • Reset/re-init listeners should be registered to deal with sim resets/repositioning.
  • Listeners must be managed by appending them to a vector for cleanup in del();.
  • Come up with a simple ObjState class to manage an AIObject's state, should be sub-classed by AI.Missile, e.g. for handling launch, ignition, cruise etc (check out galvedro's failure management code for state machine handling)
  • Consider adding a dedicated ObjectManager for handling updates (FDM!) in a smarter way, i.e. based on viewer distance, to prioritize updates according to range/visibility?

Classes (design)

ai.Obj

ai.AirborneObject

ai.Missile

ai.Aircraft

ai.Missile

Note  Should sub-class/implement AirborneObject instead of just Obj

ai.FDM

Mode Property/sub-tree Comments
input controls control surface deflections
output velocities ...
output orientation ...

ai.Autopilot

ai.Guidance

Upcoming API

WIP.png Work in progress
This article or section will be worked on in the upcoming hours or days.
See history for the latest developments.

Using an existing missile

Currently, there are the following types of missiles in the process of being unified and generalized:

Description Filename Status/Progress Comments
Aim9 $FG_ROOT/Nasal/ai/aim9/aim9.missile 10}% completed used for prototyping
Fox2 $FG_ROOT/Nasal/ai/fox2/fox2.missile Pending Pending hopefully soon...

The idea is to use the Aim9 and Fox2 in order to come up with a sufficiently generic framework, which is why those two should probably be developed concurrently. Common/shared functionality should usually live at the base class level, while missile-specific features should only be part of the actual missile class. If features are likely to be found on other types of missiles (e.g. guidance system components), it would make sense to introduce additional base classes, for better code reuse.

To create a new missile object, add the following snippet to your code (e.g. in a GUI dialog, joystick binding, Nasal Console, etc.).

# this will load the class
io.include('Nasal/ai/aim9/aim9.missile');

# use the current position as source
var source = geo.aircraft_position();

# come up with a target position using an offset
var target = geo.aircraft_position().apply_course_distance(0, 20000);

# create a new missile object
var aim9 = Aim9.new(tgt:target, src:source );

# actually launch the missile
aim9.launch();

For testing purposes, you can also directly invoke the following snippet using the Nasal Console:

ai.selftest();

This will run a few sanity checks and then instantiate a missile for testing/development purposes, which can be tracked via the Property Browser.

Implementing a new Missile

Conceptually, each missile is split into 3 distinct components:

  • the FDM (controlled by the autopilot)
  • the autopilot (controlled by the guidance system)
  • the guidance system (controlling the AP, often in combination with tracking/sensory input)

Each of these components must implement a corresponding base class by sub-classing a corresponding interface class:

  • new FDM classes must inherit from ai.FDM
  • new Autopilot classes must inherit from ai.Autopilot
  • new Guidance classes must inherit from ai.Guidance

The constructor of the guidance system will typically require a reference to the autopilot object, while the autopilot object requires a reference to the FDM object.

However, these classes can just be empty stubs inheriting from the proper base class — i.e. control is passed down to each component, so you don't necessarily need to implement an autopilot or FDM if you don't want to.

This would typically be the case for unguided/unpropelled payloads, i.e. those lacking dedicated propulsion, guidance/control and autopilot systems. Thus, only the FDM class would ultimately be running to deliver the payload, which would make it possible to re-implement submodel-based payloads using Nasal.

Equally, this separation of concerns also makes it possible to have payloads that may deliver their own payloads - possibly having their own FDMs.

In the mid-term, our hope is to grow a library of generic building blocks that can be easily reused by people without necessarily having to be experienced programmers. Long-term, the idea is to establish the property tree as the main/sole interfacing mechanism for each component, analogous to the corresponding C++ subsystems in FlightGear (FDM, AP, RM).

While those are currently not yet exposed to scripting space (i.e. inaccessible), this is one of those cases where it would simply make sense to reuse existing C++ code, especially exposing the FDM and autopilot subsystems to Nasal would mean that existing FDMs and autopilot components could be reused for modeling missiles/payloads, i.e. better code-reuse, while also reducing the amount of specialized Nasal code that would normally be overlapping with functionality already existing in C++ space. Which also means there will be less Nasal overhead, as well as better performance due to using C++.

As long as the property-level interface between the AP and the FDM is closely modeled after the underlying conventions in the C++ code, we could easily "plug in" existing FDMs and Autopilots.

By convention, the three file types mentioned above are using a corresponding file extension to make their purpose more self-explanatory — while these names are arbitrary, they are intended to be descriptive. For instance, consider the aim9 directory, which contains:

  • aim9.fdm - the actual FDM
  • aim9.autopilot - the autopilot implementation
  • aim9.guidance - the guidance system

This division also makes it possible for several people to easily collaborate without introducing unnecessary merge conflicts, i.e. by having one team porting/developing the flight dynamics, and another one handling the guidance/autopilot system.

Finally, there's a main file called aim9.missile, which merely includes these three files using the io.include(); directive to set up a corresponding missile type:

io.include("aim9.fdm");
io.include("aim9.autopilot");
io.include("aim9.guidance");
 
var Aim9 = {
    parents: [ai.Missile],
 
    new: func(tgt, src){
 
         # set up the specs for this missile type
         var specs = { parents: [ai.ObjType],
              type: 'missile',
              model_path:'Aircraft/Generic/Stores/AIM-9/AIM-9.xml',
              FDM:Aim9FDM,
              AP: Aim9Autopilot,
              GUIDANCE: Aim9Guidance,
         };
 
         return ai.Missile.new(tgt:tgt, src:src, type:specs);
    },
 
};

Internally, the system is set up such that the guidance system will ensure that the autopilot object is updated, which in turn updates the FDM object (a dedicated model manager may follow sooner or later). Equally, we may want to add base classes for modeling different guidance stages.

Custom missile types can be easily implemented by sub-classing the corresponding base-classes and implementing the required interfaces. To get started more quickly, it makes sense to copy & adapt an existing missile like the Aim9.

Implementing the FDM

To implement a FDM for your missile object, simply create a new file with a .fdm extension, and add a new class that inherits from ai.FDM. You'll need to declare a few required methods and provide a corresponding method body for each:

  • update()
# filename: demo.fdm
var DemoFDM = {
 #constructor
 new: func(missile) {
 var m = {parents: [ai.FDM] };
 m.obj=missile;
 return m;
 },
 # destructor
 del: func() {
 },
 update: func() {
 },
};

This will set up a corresponding child class implementing the required interface - in your demo.missile file you'd then merely include this file to make the class available.

Missile-specific parameters (specs) can be provided via an XML file which is loaded by the class and turned into a key/value lookup map (hash):

 
<?xml version="1.0"?>
<PropertyList>
<!--taken from Aircraft/f-14b/f-14b-set.xml -->
<aim9>
 <count type="int">0</count>
 <nearest-target type="int">-1</nearest-target>
 <sound-on-off type="bool">false</sound-on-off>
 <sound-volume type="double">0.12</sound-volume>
 <target-range-nm type="double">0</target-range-nm>
 <!-- AIM-9L specs -->
 <max-detection-rng-nm type="int">10</max-detection-rng-nm><!--FIXME: up to 10-16 MN with best conditions-->
 <fov-deg type="int">25</fov-deg>                     <!-- seeker optical FOV -->
 <detection-fov-deg type="int">60</detection-fov-deg> <!-- Search pattern diameter (rosette scan) -->
 <track-max-deg type="int">110</track-max-deg>        <!-- Seeker max total angular rotation -->
 <max-g type="int">21</max-g>                         <!-- In turn -->
 <thrust-lbs type="double">230</thrust-lbs>           <!-- guess -->
 <thrust-duration-sec type="int">30</thrust-duration-sec><!-- Mk.36 Mod.7,8 -->
 <weight-launch-lbs>191</weight-launch-lbs>
 <weight-warhead-lbs>20.8</weight-warhead-lbs>
 <drag-coeff type="double">0.05</drag-coeff>          <!-- guess -->
 <drag-area type="double">0.043</drag-area>           <!-- sq ft -->
</aim9>
</PropertyList>

Inside the FDM class, these specs are accessible via the me.specs hash, for example to look up the 'max-g' key, you'd use this:

var maxG = me.obj.specs['max-g'];

This separation makes it possible to implement a generic FDM without it having to contain any FDM/missile specifics usually.

The specs field is a member of the main Aim9 missile object, which is accessible from the FDM via the obj field.

Implementing the Autopilot

To implement a FDM for your missile object, simply create a new file with a .autopilot extension, and add a new class that inherits from ai.Autopilot. You'll need to declare a few required methods and provide a corresponding method body fore each:

  • update()
# filename: demo.autopilot
var DemoAutopilot = {
 #constructor
 new: func(fdm) {
 var m = {parents: [ai.Autopilot] };
 return m;
 },
 # destructor
 del: func() {
 },
 update: func() {
 },
};

This will set up a corresponding child class implementing the required interface - in your demo.missile file you'd then merely include this file to make the class available.

Implementing the Guidance system

To implement a FDM for your missile object, simply create a new file with a .guidance extension, and add a new class that inherits from ai.Guidance. You'll need to declare a few required methods and provide a corresponding method body for each:

  • update()
# filename: demo.guidance
var DemoGuidance = {
 #constructor
 new: func(autopilot) {
 var m = {parents: [ai.Guidance] };
 return m;
 },
 # destructor
 del: func() {
 },
 update: func() {
 },
};

This will set up a corresponding child class implementing the required interface - in your demo.missile file you'd then merely include this file to make the class available.

Original Implementation

fox2.nas

The original missile system was made by Xiii. This file is fox2.nas, and it models part of the F-14B's complex weapons system, which is controlled by weapons.nas. The firing order is as follows.

  • weapons.nas creates a new missile instance using fox2.AIM9.new();.
  • The new missile searches for, and then locks onto, a target provided by the radar.
  • While locked, the position of the HUD locking diamond is updated so that it's over the target.
  • When the missile is told to release();, it calculates the position in lat/lon/alt from offsets in meters.
  • An update(); loop starts, which runs a crude FDM.
  • The missile is guided by heading and pitch commands from the targets elevation and deviation from the missile.
  • The missile checks for an impact or a loss of lock.
  • If it detects an impact, it creates impact properties, which can be used Bombable to damage the target, and some other properties for the explosion animation. Also it displays the distance from the target when the missile exploded on the player screen. Optionally, this information can be displayed over Multiplayer.

Due to its OOP nature, fox2.nas can manage several independent missiles, each with its own target.

missile.nas

The evolution of fox2.nas is missile.nas. 5H1N0B1 generalized missile.nas and made it more generic than fox2.nas, and also added or changed some things.

  • Missiles specs are loaded from a nasal script. This allow different missiles of different specifications to be fired.
  • Missiles can be fired from a rail or dropped (although they aren't ejected yet).
  • Three new flight profile modes have been added.
    • A high-altitude cruise mode, for missiles like the AIM-54 This is a link to a Wikipedia article.
    • A low-altitude cruise mode, for missiles like the Sea Eagle This is a link to a Wikipedia article.
    • Terrain-following mode, for missiles like the Storm Shadow This is a link to a Wikipedia article.
  • Anticipation of the next target position. This makes the missile more accurate.
  • Improved proximity detection
  • Maximum G-force limitation
  • The missile will not lose lock if the target goes out of the HUD. This better simulates "fire-and-forget" missiles.

Aircraft

Aircraft with a guided weapons implementation.

Aircraft using fox2.nas or fox2.nas-derived implementation
Aircraft File Link
Grumman F-14B Tomcat fox2.nas [2]
Mirage 2000-5 missiles.nas (derived from fox2.nas) $FGDATA/Aircraft/Mirage-2000/Nasal/missile.nas
Saab JA-37 Viggen aim-9.nas (derived from fox2.nas) [3]
BAe Sea Harrier (Not released yet) missile.nas (derived from Mirage 2000-5's missile.nas) none


Aircraft using another implementation
Aircraft File Link
Boeing B-1B Lancer guide_to_target.nas $FGDATA/Aircraft/B-1B/Nasal/guide_to_target.nas

Brainstorming

Note  This part is a draft.

Let's split this into different parts:

I. The missile itself

Should be an object. The FDM has to be separated from the missile object itself. The firing order should look like this:

  1. detection
  2. load management
  3. firing order
  4. release missile
  5. update position
  6. update guidance with missile capabilities
  7. proximity detection
  8. update position

There are many types of missiles: We can classify them according to their specifications:

  1. Engine: Solid rocket, ramjet, turbojet...
  2. Orientation control: vectored thrust, moving wings, both...
  3. Flying: just falling (dropped bombs), flight with the engine thrust, wings (cruise missiles)...
  4. Kind of flight: Straight line, high alt cruise and dive, follow a low alt (Sea Eagle), terrain following (Storm Shadow), dropped bomb (GBU-24)...
  5. Terminal maneuver: Straight in, climb and dive, etc....
  6. Detection: Laser, heat, active homing, semi-active homing, anti-radiation missile, datalink, GPS/INS, hybrids...
  7. Launch: rail, dropped, ejected (from the pylons, about 5–25 ft/s)...
  8. Physics: Size, weight, warhead weight, fuel weight, max-g over load etc....
  9. Fusing: Delay, impact, delayed impact, pressure altitude, radar altitude, radar proximity, triggered by navigation computer etc...

II. Missile detection

  • The way that an aircraft detects a missile. This is independent of the missile itself, but you can't fire the missile without it.

III. Missile.nas

"missile.nas" is an improved code of "fox2.nas". The new things are :

  • Added generic missile firing. Each different missile class have their property in "loading_Missiles.nas"<-this is not optimal, perhaps an xml file would do the job...
  • Added rail or dropped capacity (not ejected yet)
  • Added High Alt Cruise mod. For high Altitude, the cruise mode go up to its cruise alt, stay there until the angle between the missile and the target < 45°
  • Added low alt sea cruise mod. Same as High Alt mod : low Alt just above the sea.
  • Added a low alt ground cruise missile. Here the Alt is given not at sea level but ground level. And to have a better behavior, the script calculate What the ground Alt will be in 2 frame/loop, in order to anticipate, and not crash. This is not optimal and can be improved.
  • Added anticipation of the next target position. With a return of experience of the first code (dogfight test), if missile just follows the "actual" position of an aircraft, there is a lot of miss. When your target is getting closer, you can't just "follow" the target. Real missile react in really short time, and can update their trajectory in a micro second. In FG, we have to calculate the next target position in a "relative" time, which depends on when the next loop will be executed (and could be slowed down by terrain loading, high-texture aircraft etc.). This feature could be improved in a more dynamic way. Older missiles have a low update rate, and newer missiles have a higher rate and some have even anticipation stuff themselves.
  • Improve proximity detection. "fox2.nas" calculates the distance with the target in the actual frame and compare it to the preceding frame. If the preceding frame is closer to the target than the actual, it means that the target is getting away from the missile. So the script take the precedent distance and compare it to the "minimal distance for explosion". If the target gets away and minimum distance isn't reached, the missile go into "missed mode". The problem with this calculation is when your missile is flying at very high speed (like Mach 4 or 5), it travels at more than 1 km/sec. So more than 100 or 200 meter between each frame. It mean the "minimal distance for explosion" could be reach between frame1 and frame2, but nor frame1 neither frame2 had reach the "minimal distance for explosion". So in that case, "fox2.nas" will not trigger explosion and it should do. "missile.nas" calculates the distance between the missile and the target in frame1 and frame to but also "on the line" between frame 1 and 2. So it's more accurate in triggering the explosion.
  • Max-G limitation. In "fox2.nas" when the missile's maximum G-force is reached, the script compute the missile in "missed mode". "missile.nas" recalculates the max angle the missile can turn without exceed it's maximum G.
  • "fox2.nas" compute in "missed mode" when the target goes out of the circle of the HUD (to simulate, when the target go out the radar lightening). This function was directly link to the aircraft. In missile.nas, this function is erased. It should be implemented again, in order to correctly simulate passive missiles like aim-7.

IV. Objects needed

  • FDM: it would make sense to model this after the existing property interface supported by JSBSIM/YASim, as that would mean that if/when AI objects can use actual FDMs, we'd have a straightforward migration path — i.e. using the existing convention of having /position /velocities /orientation nodes
  • 3D stuff: Loading missile with smoke, without when out of fuel, and explosion. In the Mirage 2000-5's "missile.nas", the missile smoke and the explosion is generic. But 3 different 3D models are loaded at each step. Not the best way, but couldn't find another way that would stay generic.
  • Guidance, should be split into Autopilot (AP: controlling the FDM properties), and "route manager" (RM), which controls the autopilot - the guidance system would just expose an abstract interface so that custom heuristics can be implemented.

Related

tankers.nas is an example of controlling AI objects from Nasal.
  • Walk View implements a walker "aircraft" using Nasal
  • FGCamera implements a camera API on top of view.nas -might be useful for the existing missile view mode (m2000-5) and should probably be unified
  • Canvas Radar is an effort aiming to generalize the fox2/aim9 sensory logic (mainly RADAR) to make the code aircraft agnostic and reusable for other aircraft/purposes, including RADAR-based AI target filtering, taking terrain/obstacles into account.
  • Bombable - see bombable.nas
The Bombable script implements dog-fighting support on top of FlightGear, without any changes to the C++ side of the code, just by using some fairly advanced scripted code (implemented in the built-in Nasal programming language). You can basically imagine it like a "mod" of FlightGear. In other words, the Bombable script creates a completely new mode in FlightGear. So it's well worth looking into this script to see how Flug has managed to do what he has accomplished. While the script is certainly far from trivial, it is an excellent example for what can be achieved by plugging together distinct FlightGear components in creative ways to create completely new behavior and systems. It would be a good idea to check Flug's code at some point to see if/how certain parts of it could be generalized there - even just moving useful routines to a dedicated module in $FG_ROOT/Nasal or $FG_ROOT/Aircraft would be a good thing. Flug has written some very clever Nasal code as part of the Bombable addon, and we should really try to understand how to generalize and integrate the most useful parts so that people working on similar features can reuse his work. Integrating these two systems would probably be an excellent exercise to get to know FlightGear's internal architecture, its built-in extension language (Nasal) and related systems such as the AI traffic system etc.

External links