I've added a new video showing current progress. http://youtu.be/ywbzyvG2HPM
As I was not able to make the MSCI's force position play nicely with any of the MSCI's internal movement commands, (i.e. attack run on target, follow object, defensive move, etc), I decided to write my own attack run command using the MSCI's set rotation and force position. In the video you will see me applying an attack script to all player owned ships in the immediate area, they will then begin manoeuvering and attacking the player ship with the following caveats:
- - Jerky movement/rotation for slower ships.
- Gimbal Lock as one the target vector axis collapses onto another, (this is easily solved).
- No speed indicator for attacking ships.
- Target indicator in the wrong position as it can't handle strafe vectors, (the internal target indicator code assumes the ship is always in motion along it's local z axis.
- Rotation rates do not match turning rates of individual ships.
- Attacking ship's fixed turrets, (front lasers), have no idea where to aim as the attacker and attacked apply more strafing.
The actual manoeuvers performed in this video are extremely simple.
- - Each attacking ship is rotating in yaw and pitch deltas trying to always face the player.
- To simulate a modicum of what players are used to with dog-fighting sims, the attacking ship does a proportional roll to it's yaw delta.
- There is a constant up and right strafe vector applied to each attacking ship that is set to a percentage of it's maximum speed, (this should obviously be controlled by a state machine based on the manoeuver the ship is trying to perform and maybe could be even limited by a "can't use unless charged up" functionality).
- A constant forward vector based on the ship's maximum speed, in reality I would like these super charged high level ships to do their utmost to stay behind the player causing maximum grief.

Technical:
X3's MSCI uses Euler angles http://en.wikipedia.org/wiki/Euler_angles, (specifically Tait–Bryan x-y-z), which is great when all you want is an extremely simple user interface for the interaction of 3D co-ordinate systems and your average Joe. Unfortunately this is not so great when the MSCI programmer wants to do more than just single axis rotations on static objects.
There's a couple of mandatory functions you need when working with Euler angles. 1: Euler to Bi-tangent, Tangent and Normal. 2: 3D vector to Euler. Because of the one to many and many to one mappings of Euler angles to 3d vector and vice versa, we will find ourselves in situations where one basis axis collapses onto another, (Gimbal Lock), thus leaving us with 2 Axis instead of 3 for our calculations and mathematical mayhem. With the system that X3 has chosen, the discontinuities occur in the vertical basis axis and are simple to resolve by restricting your calculations with no go zones.
All maths in the MSCI is 32 bit signed integer, given that I chose a fairly standard power of 10 +/- xxxxxxx.yyy fixed point format that allows for around +/-2147483 unique integers and 3 full decimal places of fractional component.
1: Converting Euler angles to basis vectors involves some trigonometry; assuming normalised vectors, (remember our normalised vectors will have a magnitude of 1000 because of the fixed point format, not a magnitude of 1), our basis vectors become the following:
- alpha = yaw, beta = pitch, gamma = roll, positive angles represent anti-clockwise rotations where 0-65536 = 0-360 degrees.
- for X3 alpha is world horizontal rotation, (around the world vertical axis), beta is world vertical rotation, (around the world horizontal axis), gamma is local tangent rotation, (around the object's forward direction).
- * Bi-tangent, i.e. forward
fwd.x = -cos(pitch) * sin(yaw)
fwd.y = sin(pitch)
fwd.z = cos(yaw) * cos(pitch)
* Tangent, i.e. transverse
tan.x = sin(yaw) * sin(pitch) * sin(roll) + cos(yaw) * cos(roll)
tan.y = cos(pitch) * sin(roll)
tan.z = cos(roll) * sin(yaw) - cos(yaw) * sin(pitch) * sin(roll)
* Normal, i.e. up
nrm.x = cos(roll) * sin(yaw) * sin(pitch) - cos(yaw) * sin(roll)
nrm.y = cos(pitch) * cos(roll)
nrm.z = -cos(yaw) * cos(roll) * sin(pitch) - sin(yaw) * sin(roll)

So, given a 3D vector, how do we obtain yaw and pitch? *Note* you can't obtain roll from a 3D vector as there are an infinite number of rotations that vector can represent around it's local axis. If we had access to and were using Quaternions, this would be a different story. For the system X3 has chosen:
- yaw = atan2(z, x) - PI/2
pitch = atan(y, sqrt(x*x + z*z))
So how did I calculate atan in the MSCI, really simply actually. On the PC I generated two lookup tables, (as MSCI source code that I simply copy'n'pasted into an MSCI file), one for atan with a domain of -1 to 1 and another with a domain of -1000 to 1000. These two separate lookup tables of 2000 entries each give me the ability to not have to forgo resolution if I had tried to use just one lookup table to represent all of atan. atan has a domain of -infinity to +infinity but because I can guarantee that my normalised vectors will always be within the domain of -1000 to 1000 I can restrict my atan lookup table to this restricted domain.
Even though I am using two lookup tables to allow much better resolution and fidelity, 2000 entries for the -1000 to 1000 domain atan lookup table is nowhere near enough, so within my MSCI atan subroutine I use some simple linear interpolation to produce a higher resolution result. To validate this function I should compare it to the the math.h atan2 function across a wide variety of inputs, noting, min, max and avg errors, but as I am only after a visual effect and there are so many other sources of errors in the MSCI, (fixed point, non deterministic script execution, etc), there really was no need.
Old:
I'm adding NPC strafing to the leveling NPC's with my LIFE mod and have come up against a couple of issues.
Firstly here is my progress so far:
- * This shows a bunch of player owned ships running the idle command strafing along their own local basis tangent vectors:
http://www.youtube.com/watch?v=1leVrfDNbyw
* Visual debugging of the local basis tangent vector; which was done by positioning a sacrificial lamb at a 40 meter distance from the player ship using the calculated tangent vector:
http://www.youtube.com/watch?v=CqnRHkX2xPY
* Baseline control:
http://www.youtube.com/watch?v=fTPgAIjrPDk
* Strafing with scale=1:
http://www.youtube.com/watch?v=VSZ3nUeUxPk
* Strafing with scale=2:
http://www.youtube.com/watch?v=OCEnx88zao0
* Strafing with scale=5
http://www.youtube.com/watch?v=F-nk-CRwuNY
For these series of tests the strafing is a constant amount that is always on and is based on the ship's local basis tangent vector, i.e. all strafing is done to the right.
Questions:
- * So why isn't the strafing script I have written that uses force position playing nice with the internal position update commands such as "attack run on target"?
* Does the internal update code know about force position, i.e. is the update code's copy of the current position updated by force position, or does force position merely update the final position before the draw matrices are built without notifying anyone of the change in position?
* Is the non-deterministic sample rate of the script I am running causing some of the force positions to be ignored or written over before they are visually seen by the internal position updates?
- * I've tried running the script on a different task slot id.
* I've tried running individual scripts, 1 per ship with different task slot id's.
* I've tried running a global script that isn't associated with any object and hence task slots/commands.
Cheers.