@fboes/aerofly-custom-missions 1.13.0 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/dist/dto-flight/AeroflyNavRouteBase.js +3 -2
- package/dist/dto-flight/AeroflyNavRouteBase.test.js +45 -15
- package/dist/dto-flight/AeroflyNavRouteRunway.js +5 -5
- package/dist/dto-flight/AeroflyNavRouteTransition.js +5 -4
- package/dist/dto-flight/AeroflySettingsFlight.js +12 -14
- package/dist/dto-flight/AeroflySettingsFlight.test.js +17 -22
- package/dist/node/AeroflyTypes.js +60 -0
- package/dist/node/Convert.js +54 -23
- package/dist/node/Convert.test.js +51 -21
- package/docs/flight.json +1 -1
- package/docs/flight.mcf +1 -1
- package/docs/flight.xml +1 -1
- package/package.json +1 -1
- package/src/dto-flight/AeroflyFlight.ts +0 -0
- package/src/dto-flight/AeroflyNavRouteBase.test.ts +50 -15
- package/src/dto-flight/AeroflyNavRouteBase.ts +5 -4
- package/src/dto-flight/AeroflyNavRouteRunway.ts +5 -5
- package/src/dto-flight/AeroflyNavRouteTransition.ts +6 -5
- package/src/dto-flight/AeroflyNavRouteWaypoint.ts +1 -1
- package/src/dto-flight/AeroflySettingsFlight.test.ts +22 -30
- package/src/dto-flight/AeroflySettingsFlight.ts +21 -20
- package/src/node/AeroflyTypes.ts +70 -0
- package/src/node/Convert.test.ts +115 -36
- package/src/node/Convert.ts +79 -27
- package/types/dto-flight/AeroflyNavRouteBase.d.ts +2 -2
- package/types/dto-flight/AeroflyNavRouteBase.d.ts.map +1 -1
- package/types/dto-flight/AeroflyNavRouteRunway.d.ts +1 -1
- package/types/dto-flight/AeroflyNavRouteRunway.d.ts.map +1 -1
- package/types/dto-flight/AeroflyNavRouteTransition.d.ts +1 -1
- package/types/dto-flight/AeroflyNavRouteTransition.d.ts.map +1 -1
- package/types/dto-flight/AeroflyNavRouteWaypoint.d.ts +1 -1
- package/types/dto-flight/AeroflyNavRouteWaypoint.d.ts.map +1 -1
- package/types/dto-flight/AeroflySettingsFlight.d.ts +9 -4
- package/types/dto-flight/AeroflySettingsFlight.d.ts.map +1 -1
- package/types/dto-flight/AeroflySettingsFuelLoad.d.ts.map +1 -1
- package/types/node/AeroflyTypes.d.ts +39 -0
- package/types/node/AeroflyTypes.d.ts.map +1 -0
- package/types/node/Convert.d.ts +29 -5
- package/types/node/Convert.d.ts.map +1 -1
|
@@ -1,30 +1,65 @@
|
|
|
1
1
|
import { describe, it } from "node:test";
|
|
2
|
+
import { strict as assert } from "node:assert";
|
|
2
3
|
import { AeroflyNavRouteDestination } from "../index.js";
|
|
3
4
|
|
|
4
5
|
describe("AeroflyNavRouteBase", () => {
|
|
5
|
-
it("should create
|
|
6
|
-
const
|
|
7
|
-
{
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
{
|
|
6
|
+
it("should create UIDs almost matching the ingame UIDs", () => {
|
|
7
|
+
const testCases = [
|
|
8
|
+
{
|
|
9
|
+
wp: new AeroflyNavRouteDestination("MAX-NE", 179.999, 89.999),
|
|
10
|
+
assertUid: 18446694595666707712n,
|
|
11
|
+
ingameUid: 0n,
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
wp: new AeroflyNavRouteDestination("MAX-SW", -179.999, -89.999),
|
|
15
|
+
assertUid: 51677066167552n,
|
|
16
|
+
ingameUid: 0n,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
wp: new AeroflyNavRouteDestination("KEYW", -81.7599558, 24.5561197),
|
|
20
|
+
assertUid: 5033914504046314752n,
|
|
21
|
+
ingameUid: 5033914504046249216n,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
wp: new AeroflyNavRouteDestination("EGLL", -0.45277777, 51.47138888),
|
|
25
|
+
assertUid: 9200173076798702848n,
|
|
26
|
+
ingameUid: 9199731073154483456n,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
wp: new AeroflyNavRouteDestination("YSSY", 151.177, -33.4961),
|
|
30
|
+
assertUid: 16969826619723154688n,
|
|
31
|
+
ingameUid: 16969837613165511936n,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
wp: new AeroflyNavRouteDestination("RJTT", 139.779, 35.709),
|
|
35
|
+
assertUid: 16385782762532177152n,
|
|
36
|
+
ingameUid: 16385890514078663936n,
|
|
37
|
+
},
|
|
13
38
|
];
|
|
14
39
|
|
|
15
|
-
for (const
|
|
16
|
-
|
|
40
|
+
for (const testCase of testCases) {
|
|
41
|
+
assert.strictEqual(
|
|
42
|
+
testCase.wp.getUidFallback(),
|
|
43
|
+
testCase.assertUid,
|
|
44
|
+
`${testCase.wp.identifier} was ${testCase.ingameUid}`,
|
|
45
|
+
);
|
|
17
46
|
}
|
|
18
47
|
});
|
|
19
48
|
|
|
20
|
-
it("should
|
|
49
|
+
it("should unpack UIDs for airports to code 1280", () => {
|
|
21
50
|
const unpackPayload = (packed: bigint) => {
|
|
22
51
|
return Number(packed & 0xffffn);
|
|
23
52
|
};
|
|
24
53
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
54
|
+
const testCases = [
|
|
55
|
+
{ uid: 5033914504046314752n, code: 1280, airport: "KEYW" },
|
|
56
|
+
{ uid: 9200173076798702848n, code: 1280, airport: "EGLL" },
|
|
57
|
+
{ uid: 16969826619723154688n, code: 1280, airport: "YSSY" },
|
|
58
|
+
{ uid: 16385782762532177152n, code: 1280, airport: "RJTT" },
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
for (const testCase of testCases) {
|
|
62
|
+
assert.strictEqual(unpackPayload(testCase.uid), testCase.code, testCase.airport);
|
|
63
|
+
}
|
|
29
64
|
});
|
|
30
65
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { convertLonLatToVector, convertVectorToLonLat
|
|
1
|
+
import { convertLonLatToVector, convertVectorToLonLat } from "../node/Convert.js";
|
|
2
2
|
import { AeroflyConfigurationNode } from "../node/AeroflyConfigurationNode.js";
|
|
3
|
+
import { AeroflyVector3Float, type AeroflyVector3FloatArray } from "../node/AeroflyTypes.js";
|
|
3
4
|
|
|
4
5
|
export type AeroflyNavRouteType =
|
|
5
6
|
| "origin"
|
|
@@ -77,8 +78,8 @@ export class AeroflyNavRouteBase {
|
|
|
77
78
|
* @param {AeroflyVector3Float} position to set longitude and latitude from
|
|
78
79
|
* @returns {this} for chaining
|
|
79
80
|
*/
|
|
80
|
-
setPosition(position:
|
|
81
|
-
this.position = position;
|
|
81
|
+
setPosition(position: AeroflyVector3FloatArray): this {
|
|
82
|
+
this.position = AeroflyVector3Float.fromArray(position);
|
|
82
83
|
return this;
|
|
83
84
|
}
|
|
84
85
|
|
|
@@ -88,7 +89,7 @@ export class AeroflyNavRouteBase {
|
|
|
88
89
|
.appendChild(
|
|
89
90
|
"vector3_float64",
|
|
90
91
|
"Position",
|
|
91
|
-
this.position,
|
|
92
|
+
this.position.toArray(),
|
|
92
93
|
`Lon ${this.longitude.toFixed(6)}, Lat ${this.latitude.toFixed(6)}`,
|
|
93
94
|
)
|
|
94
95
|
.appendChild(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { convertFeetToMeter, convertMeterToFeet
|
|
1
|
+
import { convertDirectionToHeading, convertFeetToMeter, convertMeterToFeet } from "../node/Convert.js";
|
|
2
2
|
import { AeroflyConfigurationNode } from "../node/AeroflyConfigurationNode.js";
|
|
3
3
|
import { AeroflyNavRouteBase, type AeroflyNavRouteType } from "./AeroflyNavRouteBase.js";
|
|
4
|
+
import { AeroflyVector3Float } from "../node/AeroflyTypes.js";
|
|
4
5
|
|
|
5
6
|
class AeroflyNavRouteRunway extends AeroflyNavRouteBase {
|
|
6
7
|
/**
|
|
@@ -66,12 +67,11 @@ class AeroflyNavRouteRunway extends AeroflyNavRouteBase {
|
|
|
66
67
|
* @returns {AeroflyVector3Float | null} runway direction, null if not set
|
|
67
68
|
*/
|
|
68
69
|
get direction(): AeroflyVector3Float | null {
|
|
69
|
-
// TODO:
|
|
70
|
-
return this.direction_degree !== null ? [0, 0, 0] : null;
|
|
70
|
+
return this.direction_degree !== null ? new AeroflyVector3Float(0, 0, 0) : null; // TODO: Get direction vector
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
set direction(direction: AeroflyVector3Float) {
|
|
74
|
-
this.direction_degree =
|
|
74
|
+
this.direction_degree = convertDirectionToHeading(direction, this.position);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
getElement(index: number = 0): AeroflyConfigurationNode {
|
|
@@ -80,7 +80,7 @@ class AeroflyNavRouteRunway extends AeroflyNavRouteBase {
|
|
|
80
80
|
element.appendChild(
|
|
81
81
|
"vector3_float64",
|
|
82
82
|
"Direction",
|
|
83
|
-
this.direction,
|
|
83
|
+
this.direction.toArray(),
|
|
84
84
|
`Runway direction ${Math.round(this.direction_degree ?? 0)}°`,
|
|
85
85
|
);
|
|
86
86
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { convertFeetToMeter, convertMeterToFeet
|
|
1
|
+
import { convertFeetToMeter, convertMeterToFeet } from "../node/Convert.js";
|
|
2
2
|
import { AeroflyConfigurationNode } from "../node/AeroflyConfigurationNode.js";
|
|
3
3
|
import { AeroflyNavRouteBase, type AeroflyNavRouteType } from "./AeroflyNavRouteBase.js";
|
|
4
|
+
import { AeroflyVector3Float } from "../node/AeroflyTypes.js";
|
|
4
5
|
|
|
5
6
|
class AeroflyNavRouteTransition extends AeroflyNavRouteBase {
|
|
6
7
|
/**
|
|
@@ -52,14 +53,14 @@ class AeroflyNavRouteTransition extends AeroflyNavRouteBase {
|
|
|
52
53
|
* @returns {AeroflyVector3Float} deliberately empty
|
|
53
54
|
*/
|
|
54
55
|
get position(): AeroflyVector3Float {
|
|
55
|
-
return
|
|
56
|
+
return new AeroflyVector3Float(0, 0, 0); // sic!
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
/**
|
|
59
60
|
* @returns {AeroflyVector3Float} deliberately empty
|
|
60
61
|
*/
|
|
61
62
|
get direction(): AeroflyVector3Float {
|
|
62
|
-
return
|
|
63
|
+
return new AeroflyVector3Float(0, 0, 0); // sic!
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
getElement(index: number = 0): AeroflyConfigurationNode {
|
|
@@ -67,7 +68,7 @@ class AeroflyNavRouteTransition extends AeroflyNavRouteBase {
|
|
|
67
68
|
|
|
68
69
|
element
|
|
69
70
|
.appendChild("string8u", "Airport", this.airport)
|
|
70
|
-
.appendChild("vector3_float64", "Direction", this.direction)
|
|
71
|
+
.appendChild("vector3_float64", "Direction", this.direction.toArray())
|
|
71
72
|
.appendChild(
|
|
72
73
|
"float64",
|
|
73
74
|
"Elevation",
|
|
@@ -115,7 +116,7 @@ export class AeroflyNavRouteArrival extends AeroflyNavRouteTransition {
|
|
|
115
116
|
|
|
116
117
|
element
|
|
117
118
|
.appendChild("string8u", "Airport", this.airport)
|
|
118
|
-
.appendChild("vector3_float64", "Direction", this.direction)
|
|
119
|
+
.appendChild("vector3_float64", "Direction", this.direction.toArray())
|
|
119
120
|
.appendChild(
|
|
120
121
|
"float64",
|
|
121
122
|
"Elevation",
|
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
convertLonLatToVector,
|
|
4
4
|
convertMeterToFeet,
|
|
5
5
|
convertVectorToLonLat,
|
|
6
|
-
type AeroflyVector3Float,
|
|
7
6
|
} from "../node/Convert.js";
|
|
8
7
|
import { AeroflyConfigurationNode } from "../node/AeroflyConfigurationNode.js";
|
|
9
8
|
import { AeroflyNavRouteBase } from "./AeroflyNavRouteBase.js";
|
|
9
|
+
import type { AeroflyVector3Float } from "../node/AeroflyTypes.js";
|
|
10
10
|
|
|
11
11
|
export class AeroflyNavRouteWaypoint extends AeroflyNavRouteBase {
|
|
12
12
|
/**
|
|
@@ -38,36 +38,6 @@ describe("AeroflySettingsFlight", () => {
|
|
|
38
38
|
assertValidAeroflyStructure(flight.toString());
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
it("should use valid orientation from main.mcf", () => {
|
|
42
|
-
const flight = new AeroflySettingsFlight(-122.3088, 47.4502, 1000, 90, 150);
|
|
43
|
-
|
|
44
|
-
// Canada
|
|
45
|
-
flight.orientation = [
|
|
46
|
-
-0.763419555334921, 0.645896043270811, 0.00298057365988389, -0.564914354076689, -0.665451427226094,
|
|
47
|
-
-0.487899754622681, -0.313149094027667, -0.374155982565915, 0.872894578754952,
|
|
48
|
-
];
|
|
49
|
-
|
|
50
|
-
console.log("Orientation:", flight.heading_degree); // should be 270, is 216
|
|
51
|
-
|
|
52
|
-
// South Africa
|
|
53
|
-
flight.orientation = [
|
|
54
|
-
0.324874838801671, -0.945743434574979, 0.0050690306677758, -0.526455468124437, -0.185291526716742,
|
|
55
|
-
-0.829766045466347, 0.785685038164447, 0.266901491350839, -0.5580883574482,
|
|
56
|
-
];
|
|
57
|
-
|
|
58
|
-
console.log("Orientation:", flight.heading_degree); // should be 270, is 301
|
|
59
|
-
|
|
60
|
-
// Japan
|
|
61
|
-
flight.orientation = [
|
|
62
|
-
0.717429258146935, 0.696613268777701, 0.00502128644729529, -0.393795668419003, 0.411487688662976,
|
|
63
|
-
-0.821950639401925, -0.574647919242008, 0.587714076606685, 0.569536595021937,
|
|
64
|
-
];
|
|
65
|
-
|
|
66
|
-
console.log("Orientation:", flight.heading_degree); // should be 270, is 331
|
|
67
|
-
|
|
68
|
-
assertValidAeroflyStructure(flight.toString());
|
|
69
|
-
});
|
|
70
|
-
|
|
71
41
|
it("should create fallbacks for flight configurations", () => {
|
|
72
42
|
const flight = new AeroflySettingsFlight(-122.3088, 47.4502, 1000, 90, 150, {
|
|
73
43
|
configuration: "OnGround",
|
|
@@ -89,4 +59,26 @@ describe("AeroflySettingsFlight", () => {
|
|
|
89
59
|
assert.ok(flight.flaps > 0, "Flaps extended");
|
|
90
60
|
assert.strictEqual(flight.onGround, false);
|
|
91
61
|
});
|
|
62
|
+
|
|
63
|
+
it("should convert between speed and velocity vector", () => {
|
|
64
|
+
const flight = new AeroflySettingsFlight(-122.3088, 47.4502, 1000, 90, 150);
|
|
65
|
+
|
|
66
|
+
assert.strictEqual(flight.speed_kts, 150);
|
|
67
|
+
assert.strictEqual(flight.speed_ms, 77.1666);
|
|
68
|
+
|
|
69
|
+
const velocity = flight.velocity;
|
|
70
|
+
assert.ok(velocity.x > 60);
|
|
71
|
+
assert.ok(velocity.y < -40);
|
|
72
|
+
assert.ok(velocity.z > 0);
|
|
73
|
+
|
|
74
|
+
// Reset speed
|
|
75
|
+
flight.speed_ms = 0;
|
|
76
|
+
assert.strictEqual(flight.speed_kts, 0);
|
|
77
|
+
assert.strictEqual(flight.speed_ms, 0);
|
|
78
|
+
|
|
79
|
+
// Set speed from velocity vector
|
|
80
|
+
flight.velocity = velocity;
|
|
81
|
+
assert.strictEqual(flight.speed_kts, 150);
|
|
82
|
+
assert.strictEqual(flight.speed_ms, 77.1666);
|
|
83
|
+
});
|
|
92
84
|
});
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { AeroflyConfigurationNode } from "../node/AeroflyConfigurationNode.js";
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
AeroflyMatrix3Float,
|
|
4
|
+
AeroflyVector3Float,
|
|
5
|
+
type AeroflyMatrix3FloatArray,
|
|
6
|
+
type AeroflyVector3FloatArray,
|
|
7
|
+
} from "../node/AeroflyTypes.js";
|
|
8
|
+
import {
|
|
5
9
|
convertFeetToMeter,
|
|
6
10
|
convertLonLatToVector,
|
|
7
11
|
convertVectorToLonLat,
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
convertHeadingToOrientation,
|
|
13
|
+
convertOrientationToHeading,
|
|
10
14
|
convertMeterToFeet,
|
|
11
15
|
} from "../node/Convert.js";
|
|
12
16
|
|
|
@@ -103,15 +107,15 @@ export class AeroflySettingsFlight {
|
|
|
103
107
|
}
|
|
104
108
|
|
|
105
109
|
static createInCartesian(
|
|
106
|
-
position:
|
|
107
|
-
velocity:
|
|
108
|
-
orientation:
|
|
110
|
+
position: AeroflyVector3FloatArray,
|
|
111
|
+
velocity: AeroflyVector3FloatArray,
|
|
112
|
+
orientation: AeroflyMatrix3FloatArray,
|
|
109
113
|
additionalAttributes: Partial<AeroflySettingsFlight> = {},
|
|
110
114
|
): AeroflySettingsFlight {
|
|
111
115
|
const flight = new AeroflySettingsFlight(0, 0, 0, 0, 0, additionalAttributes);
|
|
112
|
-
flight.position = position;
|
|
113
|
-
flight.velocity = velocity;
|
|
114
|
-
flight.orientation = orientation;
|
|
116
|
+
flight.position = AeroflyVector3Float.fromArray(position);
|
|
117
|
+
flight.velocity = AeroflyVector3Float.fromArray(velocity);
|
|
118
|
+
flight.orientation = AeroflyMatrix3Float.fromArray(orientation);
|
|
115
119
|
return flight;
|
|
116
120
|
}
|
|
117
121
|
|
|
@@ -142,7 +146,6 @@ export class AeroflySettingsFlight {
|
|
|
142
146
|
break;
|
|
143
147
|
default:
|
|
144
148
|
this.throttle = 0;
|
|
145
|
-
this.speed_kts = 0;
|
|
146
149
|
break;
|
|
147
150
|
}
|
|
148
151
|
}
|
|
@@ -166,21 +169,19 @@ export class AeroflySettingsFlight {
|
|
|
166
169
|
*/
|
|
167
170
|
get velocity(): AeroflyVector3Float {
|
|
168
171
|
const speed = this.speed_ms;
|
|
169
|
-
|
|
170
|
-
return [Math.cos(heading_rad) * speed, Math.sin(heading_rad) * speed, 0];
|
|
172
|
+
return this.orientation.multiplyVector(new AeroflyVector3Float(speed, 0, 0));
|
|
171
173
|
}
|
|
172
174
|
|
|
173
175
|
set velocity(velocity: AeroflyVector3Float) {
|
|
174
|
-
|
|
175
|
-
this.speed_ms = 0;
|
|
176
|
+
this.speed_ms = Math.sqrt(velocity.x ** 2 + velocity.y ** 2 + velocity.z ** 2);
|
|
176
177
|
}
|
|
177
178
|
|
|
178
179
|
get orientation(): AeroflyMatrix3Float {
|
|
179
|
-
return
|
|
180
|
+
return convertHeadingToOrientation(this.heading_degree, this.position);
|
|
180
181
|
}
|
|
181
182
|
|
|
182
183
|
set orientation(orientation: AeroflyMatrix3Float) {
|
|
183
|
-
this.heading_degree =
|
|
184
|
+
this.heading_degree = convertOrientationToHeading(orientation, this.position);
|
|
184
185
|
}
|
|
185
186
|
|
|
186
187
|
/**
|
|
@@ -210,14 +211,14 @@ export class AeroflySettingsFlight {
|
|
|
210
211
|
.appendChild(
|
|
211
212
|
"vector3_float64",
|
|
212
213
|
"position",
|
|
213
|
-
this.position,
|
|
214
|
+
this.position.toArray(),
|
|
214
215
|
`Lon ${this.longitude.toFixed(6)}, Lat ${this.latitude.toFixed(6)}, ${Math.ceil(this.altitude_ft)} ft`,
|
|
215
216
|
)
|
|
216
|
-
.appendChild("vector3_float64", "velocity", this.velocity, `${Math.round(this.speed_kts)} kts`)
|
|
217
|
+
.appendChild("vector3_float64", "velocity", this.velocity.toArray(), `${Math.round(this.speed_kts)} kts`)
|
|
217
218
|
.appendChild(
|
|
218
219
|
"matrix3_float64",
|
|
219
220
|
"orientation",
|
|
220
|
-
this.orientation,
|
|
221
|
+
this.orientation.toArray(),
|
|
221
222
|
`${Math.round(this.heading_degree)}° heading`,
|
|
222
223
|
)
|
|
223
224
|
.appendChild("float64", "gear", this.gear)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export type AeroflyVector3FloatArray = [number, number, number];
|
|
2
|
+
|
|
3
|
+
export class AeroflyVector3Float {
|
|
4
|
+
constructor(
|
|
5
|
+
public x: number,
|
|
6
|
+
public y: number,
|
|
7
|
+
public z: number,
|
|
8
|
+
) {}
|
|
9
|
+
|
|
10
|
+
normalize(): AeroflyVector3Float {
|
|
11
|
+
const norm = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
|
|
12
|
+
|
|
13
|
+
if (norm === 0) {
|
|
14
|
+
return new AeroflyVector3Float(0, 0, 0);
|
|
15
|
+
}
|
|
16
|
+
return new AeroflyVector3Float(this.x / norm, this.y / norm, this.z / norm);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
cross(b: AeroflyVector3Float): AeroflyVector3Float {
|
|
20
|
+
return new AeroflyVector3Float(
|
|
21
|
+
this.y * b.z - this.z * b.y,
|
|
22
|
+
this.z * b.x - this.x * b.z,
|
|
23
|
+
this.x * b.y - this.y * b.x,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static fromArray(array: AeroflyVector3FloatArray): AeroflyVector3Float {
|
|
28
|
+
return new this(...array);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
toArray(): AeroflyVector3FloatArray {
|
|
32
|
+
return [this.x, this.y, this.z];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type AeroflyMatrix3FloatArray = [number, number, number, number, number, number, number, number, number];
|
|
37
|
+
|
|
38
|
+
export class AeroflyMatrix3Float {
|
|
39
|
+
constructor(
|
|
40
|
+
public xx: number,
|
|
41
|
+
public yx: number,
|
|
42
|
+
public zx: number,
|
|
43
|
+
public xy: number,
|
|
44
|
+
public yy: number,
|
|
45
|
+
public zy: number,
|
|
46
|
+
public xz: number,
|
|
47
|
+
public yz: number,
|
|
48
|
+
public zz: number,
|
|
49
|
+
) {}
|
|
50
|
+
|
|
51
|
+
transpose(): AeroflyMatrix3Float {
|
|
52
|
+
return new AeroflyMatrix3Float(this.xx, this.xy, this.xz, this.yx, this.yy, this.yz, this.zx, this.zy, this.zz);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
multiplyVector(v: AeroflyVector3Float): AeroflyVector3Float {
|
|
56
|
+
return new AeroflyVector3Float(
|
|
57
|
+
this.xx * v.x + this.xy * v.y + this.xz * v.z,
|
|
58
|
+
this.yx * v.x + this.yy * v.y + this.yz * v.z,
|
|
59
|
+
this.zx * v.x + this.zy * v.y + this.zz * v.z,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
static fromArray(array: AeroflyMatrix3FloatArray): AeroflyMatrix3Float {
|
|
64
|
+
return new this(...array);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
toArray(): AeroflyMatrix3FloatArray {
|
|
68
|
+
return [this.xx, this.yx, this.zx, this.xy, this.yy, this.zy, this.xz, this.yz, this.zz];
|
|
69
|
+
}
|
|
70
|
+
}
|
package/src/node/Convert.test.ts
CHANGED
|
@@ -2,14 +2,14 @@ import { describe, it } from "node:test";
|
|
|
2
2
|
import { strict as assert } from "node:assert";
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
convertHeadingToOrientation,
|
|
6
6
|
convertFeetToMeter,
|
|
7
7
|
convertLonLatToVector,
|
|
8
|
-
|
|
8
|
+
convertOrientationToHeading,
|
|
9
9
|
convertMeterToFeet,
|
|
10
10
|
convertVectorToLonLat,
|
|
11
|
-
type AeroflyMatrix3Float,
|
|
12
11
|
} from "./Convert.js";
|
|
12
|
+
import { AeroflyMatrix3Float, AeroflyVector3Float } from "./AeroflyTypes.js";
|
|
13
13
|
|
|
14
14
|
describe("Convert", () => {
|
|
15
15
|
it("should convert feet to meters correctly", () => {
|
|
@@ -54,45 +54,124 @@ describe("Convert", () => {
|
|
|
54
54
|
);
|
|
55
55
|
});
|
|
56
56
|
|
|
57
|
-
it("should convert
|
|
58
|
-
const
|
|
59
|
-
const
|
|
57
|
+
it("should convert orientation matrix to heading degree", () => {
|
|
58
|
+
const positionKEYW = new AeroflyVector3Float(831922.0452075421, -5744695.679418418, 2634432.6068597846);
|
|
59
|
+
const positionYSSY = new AeroflyVector3Float(-4640429.92586909, 2553500.8064244, -3541489.45541516);
|
|
60
60
|
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
61
|
+
const testCases = [
|
|
62
|
+
{
|
|
63
|
+
orientation: new AeroflyMatrix3Float(
|
|
64
|
+
-0.0668066264395785,
|
|
65
|
+
0.431792064197595,
|
|
66
|
+
0.899495685348041,
|
|
67
|
+
-0.98941528906399,
|
|
68
|
+
-0.145060537051883,
|
|
69
|
+
-0.00385050082983023,
|
|
70
|
+
0.128818711490931,
|
|
71
|
+
-0.890232022500988,
|
|
72
|
+
0.43691267512355,
|
|
73
|
+
),
|
|
74
|
+
position: positionKEYW,
|
|
75
|
+
heading: 0,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
orientation: new AeroflyMatrix3Float(
|
|
79
|
+
0.985978516472605,
|
|
80
|
+
0.166832368887515,
|
|
81
|
+
-0.00365044461148948,
|
|
82
|
+
-0.066064640816506,
|
|
83
|
+
0.41034317377044,
|
|
84
|
+
0.90953501470465,
|
|
85
|
+
0.153237816116869,
|
|
86
|
+
-0.896540819166301,
|
|
87
|
+
0.41561127424596,
|
|
88
|
+
),
|
|
89
|
+
position: positionKEYW,
|
|
90
|
+
heading: 90,
|
|
91
|
+
},
|
|
68
92
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
93
|
+
{
|
|
94
|
+
orientation: new AeroflyMatrix3Float(
|
|
95
|
+
-0.465135059130883,
|
|
96
|
+
-0.885151853036744,
|
|
97
|
+
0.0124729239927332,
|
|
98
|
+
-0.489418463305539,
|
|
99
|
+
0.268872261996669,
|
|
100
|
+
0.829564508946978,
|
|
101
|
+
-0.737644185595572,
|
|
102
|
+
0.379755057628484,
|
|
103
|
+
-0.558271575187768,
|
|
104
|
+
),
|
|
105
|
+
position: positionYSSY,
|
|
106
|
+
heading: 90,
|
|
107
|
+
},
|
|
108
|
+
];
|
|
75
109
|
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
);
|
|
110
|
+
for (const testCase of testCases) {
|
|
111
|
+
const calculatedHeading = convertOrientationToHeading(testCase.orientation, testCase.position);
|
|
112
|
+
const message = `Expected heading to be ${testCase.heading.toFixed(4)}, got ${calculatedHeading.toFixed(4)}`;
|
|
113
|
+
assert.strictEqual(Math.round(calculatedHeading) % 360, Math.round(testCase.heading) % 360, message);
|
|
114
|
+
}
|
|
82
115
|
});
|
|
83
116
|
|
|
84
|
-
it("should
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
117
|
+
it("should convert heading degree to orientation matrix (ignoring pitch, roll, yaw, etc.)", () => {
|
|
118
|
+
const positionKEYW = new AeroflyVector3Float(831922.0452075421, -5744695.679418418, 2634432.6068597846);
|
|
119
|
+
const positionYSSY = new AeroflyVector3Float(-4640429.92586909, 2553500.8064244, -3541489.45541516);
|
|
120
|
+
|
|
121
|
+
const testCases = [
|
|
122
|
+
{
|
|
123
|
+
orientation: new AeroflyMatrix3Float(
|
|
124
|
+
-0.0668066264395785,
|
|
125
|
+
0.431792064197595,
|
|
126
|
+
0.899495685348041,
|
|
127
|
+
-0.98941528906399,
|
|
128
|
+
-0.145060537051883,
|
|
129
|
+
-0.00385050082983023,
|
|
130
|
+
0.128818711490931,
|
|
131
|
+
-0.890232022500988,
|
|
132
|
+
0.43691267512355,
|
|
133
|
+
),
|
|
134
|
+
position: positionKEYW,
|
|
135
|
+
heading: 0,
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
orientation: new AeroflyMatrix3Float(
|
|
139
|
+
0.985978516472605,
|
|
140
|
+
0.166832368887515,
|
|
141
|
+
-0.00365044461148948,
|
|
142
|
+
-0.066064640816506,
|
|
143
|
+
0.41034317377044,
|
|
144
|
+
0.90953501470465,
|
|
145
|
+
0.153237816116869,
|
|
146
|
+
-0.896540819166301,
|
|
147
|
+
0.41561127424596,
|
|
148
|
+
),
|
|
149
|
+
position: positionKEYW,
|
|
150
|
+
heading: 90,
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
{
|
|
154
|
+
orientation: new AeroflyMatrix3Float(
|
|
155
|
+
-0.465135059130883,
|
|
156
|
+
-0.885151853036744,
|
|
157
|
+
0.0124729239927332,
|
|
158
|
+
-0.489418463305539,
|
|
159
|
+
0.268872261996669,
|
|
160
|
+
0.829564508946978,
|
|
161
|
+
-0.737644185595572,
|
|
162
|
+
0.379755057628484,
|
|
163
|
+
-0.558271575187768,
|
|
164
|
+
),
|
|
165
|
+
position: positionYSSY,
|
|
166
|
+
heading: 90,
|
|
167
|
+
},
|
|
88
168
|
];
|
|
89
|
-
const expectedHeadingDegree = 117.3585;
|
|
90
169
|
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
170
|
+
for (const testCase of testCases) {
|
|
171
|
+
const calculatedOrientation = convertHeadingToOrientation(testCase.heading, testCase.position);
|
|
172
|
+
const calculatedHeading = convertOrientationToHeading(calculatedOrientation, testCase.position);
|
|
173
|
+
|
|
174
|
+
assert.strictEqual(calculatedHeading, testCase.heading);
|
|
175
|
+
}
|
|
97
176
|
});
|
|
98
177
|
});
|