pth_to_lyt 1.0.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.
@@ -0,0 +1,6 @@
1
+ {
2
+ "endOfLine": "crlf",
3
+ "semi": true,
4
+ "singleAttributePerLine": true,
5
+ "tabWidth": 4
6
+ }
@@ -0,0 +1 @@
1
+ export declare function convertPTHtoLYT(pthFolderPath: string, lytFolderPath: string, trackPrefix: string, lytName: string): void;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.convertPTHtoLYT = convertPTHtoLYT;
4
+ const PTH_parser_1 = require("input/PTH.parser");
5
+ const PTH_reader_1 = require("input/PTH.reader");
6
+ const LYT_transform_1 = require("output/LYT.transform");
7
+ const LYT_writer_1 = require("output/LYT.writer");
8
+ function convertPTHtoLYT(pthFolderPath, lytFolderPath, trackPrefix, lytName) {
9
+ const pthPath = `${pthFolderPath}/${trackPrefix}.pth`;
10
+ const lytPath = `${lytFolderPath}/${trackPrefix}_${lytName}.lyt`;
11
+ const raw = (0, PTH_reader_1.readPTH)(pthPath);
12
+ const data = (0, PTH_parser_1.parserPTH)(raw);
13
+ const objectArray = (0, LYT_transform_1.transformLYT)(data.mainNodes, data.numberNodes);
14
+ (0, LYT_writer_1.writeLYT)(lytPath, objectArray);
15
+ }
@@ -0,0 +1,2 @@
1
+ export { convertPTHtoLYT } from "./core/PTHToLYT";
2
+ export { calculateDriveLimits, calculateHalfWidth, calculateHeading, calculateMidPoint, transformLYT, transformLYTObjectPosition, } from "./output/LYT.transform";
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.transformLYTObjectPosition = exports.transformLYT = exports.calculateMidPoint = exports.calculateHeading = exports.calculateHalfWidth = exports.calculateDriveLimits = exports.convertPTHtoLYT = void 0;
4
+ var PTHToLYT_1 = require("./core/PTHToLYT");
5
+ Object.defineProperty(exports, "convertPTHtoLYT", { enumerable: true, get: function () { return PTHToLYT_1.convertPTHtoLYT; } });
6
+ var LYT_transform_1 = require("./output/LYT.transform");
7
+ Object.defineProperty(exports, "calculateDriveLimits", { enumerable: true, get: function () { return LYT_transform_1.calculateDriveLimits; } });
8
+ Object.defineProperty(exports, "calculateHalfWidth", { enumerable: true, get: function () { return LYT_transform_1.calculateHalfWidth; } });
9
+ Object.defineProperty(exports, "calculateHeading", { enumerable: true, get: function () { return LYT_transform_1.calculateHeading; } });
10
+ Object.defineProperty(exports, "calculateMidPoint", { enumerable: true, get: function () { return LYT_transform_1.calculateMidPoint; } });
11
+ Object.defineProperty(exports, "transformLYT", { enumerable: true, get: function () { return LYT_transform_1.transformLYT; } });
12
+ Object.defineProperty(exports, "transformLYTObjectPosition", { enumerable: true, get: function () { return LYT_transform_1.transformLYTObjectPosition; } });
@@ -0,0 +1,2 @@
1
+ import { PTH } from "./types";
2
+ export declare function parserPTH(buffer: Buffer<ArrayBuffer>): PTH;
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parserPTH = parserPTH;
4
+ function parserPTH(buffer) {
5
+ const systemHeader = readSystemHeader(buffer);
6
+ const localHeader = readLocalHeader(buffer);
7
+ const numberNodes = readNumberNodes(buffer);
8
+ const mainNodes = readMainNodes(buffer, numberNodes);
9
+ return {
10
+ systemHeader,
11
+ localHeader,
12
+ numberNodes,
13
+ mainNodes,
14
+ };
15
+ }
16
+ function readSystemHeader(buffer) {
17
+ let offset = 0;
18
+ const magic = buffer.toString("ascii", offset, offset + 6);
19
+ offset += 6;
20
+ if (magic !== "SRPATH") {
21
+ throw new Error("Invalid File (SRPATH not found)");
22
+ }
23
+ const version = buffer.readUInt8(offset++);
24
+ const revision = buffer.readUInt8(offset++);
25
+ if (version > 0 || revision > 252) {
26
+ throw new Error("Wrong version");
27
+ }
28
+ const flags = buffer.readInt32LE(offset);
29
+ return {
30
+ version,
31
+ revision,
32
+ flags,
33
+ };
34
+ }
35
+ function readLocalHeader(buffer) {
36
+ let offset = 12;
37
+ const miniRev = buffer.readUInt8(offset++);
38
+ return {
39
+ miniRev,
40
+ };
41
+ }
42
+ function readNumberNodes(buffer) {
43
+ let offset = 16;
44
+ const numberNodes = buffer.readUInt16LE(offset);
45
+ if (numberNodes <= 0)
46
+ throw new Error("The number of nodes is too low");
47
+ return numberNodes;
48
+ }
49
+ function readNode(offset, buffer) {
50
+ const node = {
51
+ centre: {
52
+ x: buffer.readInt32LE(offset + 4),
53
+ y: buffer.readInt32LE(offset + 8),
54
+ },
55
+ dir: {
56
+ x: buffer.readFloatLE(offset + 16),
57
+ y: buffer.readFloatLE(offset + 20),
58
+ },
59
+ limits: {
60
+ driveLeft: buffer.readFloatLE(offset + 36),
61
+ driveRight: buffer.readFloatLE(offset + 40),
62
+ },
63
+ };
64
+ return node;
65
+ }
66
+ function readMainNodes(buffer, length) {
67
+ const startOffset = 56;
68
+ const nodes = [];
69
+ for (let i = 0; i < length; i++) {
70
+ const offset = startOffset + 44 * i;
71
+ nodes[i] = readNode(offset, buffer);
72
+ }
73
+ return nodes;
74
+ }
@@ -0,0 +1 @@
1
+ export declare function readPTH(path: string): Buffer<ArrayBuffer>;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.readPTH = readPTH;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ function readPTH(path) {
9
+ const buffer = fs_1.default.readFileSync(path);
10
+ if (buffer === undefined) {
11
+ throw new Error("Invalid File");
12
+ }
13
+ return buffer;
14
+ }
@@ -0,0 +1,28 @@
1
+ export type PTH = {
2
+ systemHeader: SystemHeader;
3
+ localHeader: LocalHeader;
4
+ numberNodes: number;
5
+ mainNodes: PTHNode[];
6
+ };
7
+ export type SystemHeader = {
8
+ version: number;
9
+ revision: number;
10
+ flags: number;
11
+ };
12
+ export type LocalHeader = {
13
+ miniRev: number;
14
+ };
15
+ export type PTHNode = {
16
+ centre: {
17
+ x: number;
18
+ y: number;
19
+ };
20
+ dir: {
21
+ x: number;
22
+ y: number;
23
+ };
24
+ limits: {
25
+ driveLeft: number;
26
+ driveRight: number;
27
+ };
28
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,8 @@
1
+ import { PTHNode } from "input/types";
2
+ import { DriveLimits, LYTObject, Vector2 } from "./types";
3
+ export declare function transformLYT(nodes: PTHNode[], numberNodes: number): LYTObject[];
4
+ export declare function transformLYTObjectPosition(value: number): number;
5
+ export declare function calculateHeading(dirX: number, dirY: number): number;
6
+ export declare function calculateDriveLimits(node: PTHNode): DriveLimits;
7
+ export declare function calculateMidPoint(driveLimits: DriveLimits): Vector2;
8
+ export declare function calculateHalfWidth(driveLimits: DriveLimits, widthOffset?: number): number;
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.transformLYT = transformLYT;
4
+ exports.transformLYTObjectPosition = transformLYTObjectPosition;
5
+ exports.calculateHeading = calculateHeading;
6
+ exports.calculateDriveLimits = calculateDriveLimits;
7
+ exports.calculateMidPoint = calculateMidPoint;
8
+ exports.calculateHalfWidth = calculateHalfWidth;
9
+ const constants_1 = require("./constants");
10
+ // LFS NOTATION
11
+ // 0 = world y axis direction
12
+ // LYT NOTATION
13
+ // 128 : heading of zero
14
+ // 192 : heading of 90 degrees
15
+ // 0 : heading of 180 degrees
16
+ // 64 : heading of -90 degrees
17
+ function transformLYT(nodes, numberNodes) {
18
+ const ZBYTE = 240;
19
+ const CHECKPOINT_INDEX = 252;
20
+ const FIRST_CHECKPOINT_FLAG = 0x01;
21
+ const MAX_NODE = 180;
22
+ const objectArray = [];
23
+ const length = numberNodes < MAX_NODE ? numberNodes : MAX_NODE;
24
+ const gap = numberNodes / length;
25
+ for (let i = 0; i < length; i++) {
26
+ const nodeIndex = Math.round(gap * i);
27
+ const node = nodes[nodeIndex];
28
+ const heading = calculateHeading(node.dir.x, node.dir.y);
29
+ const driveLimits = calculateDriveLimits(node);
30
+ const halfWidth = calculateHalfWidth(driveLimits);
31
+ const midPoint = calculateMidPoint(driveLimits);
32
+ const lytObjectX = transformLYTObjectPosition(midPoint.x);
33
+ const lytObjectY = transformLYTObjectPosition(midPoint.y);
34
+ objectArray.push({
35
+ x: lytObjectX,
36
+ y: lytObjectY,
37
+ z: ZBYTE,
38
+ flags: (halfWidth << 2) | FIRST_CHECKPOINT_FLAG,
39
+ index: CHECKPOINT_INDEX,
40
+ heading: heading,
41
+ });
42
+ }
43
+ return objectArray;
44
+ }
45
+ function transformLYTObjectPosition(value) {
46
+ return Math.round(value / constants_1.SCALAR_FACTOR);
47
+ }
48
+ function calculateHeading(dirX, dirY) {
49
+ const angleRadians = Math.atan2(dirY, dirX);
50
+ const angleDegrees = angleRadians * (180 / Math.PI);
51
+ //"Heading represents 360 degrees in 256 values."
52
+ const heading = Math.round(((angleDegrees - 90 + 180) * 256) / 360) & 0xff;
53
+ return heading;
54
+ }
55
+ function calculateDriveLimits(node) {
56
+ // "A node is represented by a line perpendicular to its direction."
57
+ // perpendicular = 90 degrees
58
+ // rotate 90 degrees to left for find drive limits direct
59
+ const perpendicularX = -node.dir.y;
60
+ const perpendicularY = node.dir.x;
61
+ // driveLeft negative
62
+ // driveRight positive
63
+ const left = {
64
+ x: node.centre.x - perpendicularX * node.limits.driveLeft * constants_1.LFS_METER,
65
+ y: node.centre.y - perpendicularY * node.limits.driveLeft * constants_1.LFS_METER,
66
+ };
67
+ const right = {
68
+ x: node.centre.x - perpendicularX * node.limits.driveRight * constants_1.LFS_METER,
69
+ y: node.centre.y - perpendicularY * node.limits.driveRight * constants_1.LFS_METER,
70
+ };
71
+ return { left, right };
72
+ }
73
+ function calculateMidPoint(driveLimits) {
74
+ const x = (driveLimits.right.x + driveLimits.left.x) / 2;
75
+ const y = (driveLimits.right.y + driveLimits.left.y) / 2;
76
+ return { x, y };
77
+ }
78
+ // "half width in metres (1 to 31 ...)."
79
+ function calculateHalfWidth(driveLimits, widthOffset = 2) {
80
+ const distanceX = Math.pow(driveLimits.right.x - driveLimits.left.x, 2);
81
+ const distanceY = Math.pow(driveLimits.right.y - driveLimits.left.y, 2);
82
+ const distance = Math.sqrt(distanceX + distanceY);
83
+ const halfWidth = Math.round(distance / constants_1.LFS_METER / 2 + widthOffset);
84
+ return halfWidth > 31 ? 31 : halfWidth;
85
+ }
@@ -0,0 +1,2 @@
1
+ import { LYTObject } from "./types";
2
+ export declare function writeLYT(path: string, objectArray: LYTObject[]): void;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.writeLYT = writeLYT;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ function writeLYT(path, objectArray) {
9
+ const bufferHeader = buildBufferHeader(objectArray.length);
10
+ const bufferObjects = buildBufferLYTObject(objectArray);
11
+ const bufferList = [bufferHeader, bufferObjects];
12
+ const buffer = Buffer.concat(bufferList);
13
+ fs_1.default.writeFileSync(path, buffer);
14
+ }
15
+ function buildBufferHeader(length) {
16
+ const buffer = Buffer.alloc(12);
17
+ buffer.write("LFSLYT", 0, "ascii");
18
+ buffer.writeUInt8(0, 6);
19
+ buffer.writeUInt8(252, 7);
20
+ buffer.writeUInt16LE(length, 8);
21
+ buffer.writeUInt8(0, 10);
22
+ buffer.writeUInt8(9, 11);
23
+ return buffer;
24
+ }
25
+ function buildBufferLYTObject(objectArray) {
26
+ const buffer = Buffer.alloc(8 * objectArray.length);
27
+ let offset = 0;
28
+ for (let i = 0; i < objectArray.length; i++) {
29
+ const object = objectArray[i];
30
+ buffer.writeInt16LE(object.x, offset);
31
+ offset += 2;
32
+ buffer.writeInt16LE(object.y, offset);
33
+ offset += 2;
34
+ buffer.writeUInt8(object.z, offset);
35
+ offset++;
36
+ buffer.writeUInt8(object.flags, offset);
37
+ offset++;
38
+ buffer.writeUInt8(object.index, offset);
39
+ offset++;
40
+ buffer.writeUInt8(object.heading, offset);
41
+ offset++;
42
+ }
43
+ return buffer;
44
+ }
@@ -0,0 +1,3 @@
1
+ export declare const LFS_METER: number;
2
+ export declare const LYT_METER: number;
3
+ export declare const SCALAR_FACTOR: number;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SCALAR_FACTOR = exports.LYT_METER = exports.LFS_METER = void 0;
4
+ exports.LFS_METER = 65536; // 1m
5
+ exports.LYT_METER = 16; // 1m
6
+ exports.SCALAR_FACTOR = exports.LFS_METER / exports.LYT_METER;
@@ -0,0 +1,16 @@
1
+ export type LYTObject = {
2
+ x: number;
3
+ y: number;
4
+ z: number;
5
+ flags: number;
6
+ index: number;
7
+ heading: number;
8
+ };
9
+ export type Vector2 = {
10
+ x: number;
11
+ y: number;
12
+ };
13
+ export type DriveLimits = {
14
+ right: Vector2;
15
+ left: Vector2;
16
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "pth_to_lyt",
3
+ "version": "1.0.0",
4
+ "main": "dist/main.js",
5
+ "private": false,
6
+ "scripts": {
7
+ "start": "tsx watch src/main.ts",
8
+ "build": "tsc",
9
+ "serve:production": "node dist/main.js"
10
+ },
11
+ "devDependencies": {
12
+ "@types/node": "^20.10.5",
13
+ "prettier": "^3.7.4",
14
+ "tsx": "^4.7.0",
15
+ "typescript": "^5.3.3"
16
+ }
17
+ }
@@ -0,0 +1,24 @@
1
+ import { parserPTH } from "input/PTH.parser";
2
+ import { readPTH } from "input/PTH.reader";
3
+ import { PTH } from "input/types";
4
+ import { transformLYT } from "output/LYT.transform";
5
+ import { writeLYT } from "output/LYT.writer";
6
+ import { LYTObject } from "output/types";
7
+
8
+ export function convertPTHtoLYT(
9
+ pthFolderPath: string,
10
+ lytFolderPath: string,
11
+ trackPrefix: string,
12
+ lytName: string,
13
+ ) {
14
+ const pthPath: string = `${pthFolderPath}/${trackPrefix}.pth`;
15
+ const lytPath: string = `${lytFolderPath}/${trackPrefix}_${lytName}.lyt`;
16
+
17
+ const raw: Buffer<ArrayBuffer> = readPTH(pthPath);
18
+ const data: PTH = parserPTH(raw);
19
+ const objectArray: LYTObject[] = transformLYT(
20
+ data.mainNodes,
21
+ data.numberNodes,
22
+ );
23
+ writeLYT(lytPath, objectArray);
24
+ }
package/src/index.ts ADDED
@@ -0,0 +1,9 @@
1
+ export { convertPTHtoLYT } from "./core/PTHToLYT";
2
+ export {
3
+ calculateDriveLimits,
4
+ calculateHalfWidth,
5
+ calculateHeading,
6
+ calculateMidPoint,
7
+ transformLYT,
8
+ transformLYTObjectPosition,
9
+ } from "./output/LYT.transform";
@@ -0,0 +1,86 @@
1
+ import { PTHNode, LocalHeader, PTH, SystemHeader } from "./types";
2
+
3
+ export function parserPTH(buffer: Buffer<ArrayBuffer>): PTH {
4
+ const systemHeader: SystemHeader = readSystemHeader(buffer);
5
+ const localHeader: LocalHeader = readLocalHeader(buffer);
6
+ const numberNodes: number = readNumberNodes(buffer);
7
+ const mainNodes: PTHNode[] = readMainNodes(buffer, numberNodes);
8
+
9
+ return {
10
+ systemHeader,
11
+ localHeader,
12
+ numberNodes,
13
+ mainNodes,
14
+ };
15
+ }
16
+
17
+ function readSystemHeader(buffer: Buffer<ArrayBuffer>): SystemHeader {
18
+ let offset: number = 0;
19
+ const magic: string = buffer.toString("ascii", offset, offset + 6);
20
+ offset += 6;
21
+
22
+ if (magic !== "SRPATH") {
23
+ throw new Error("Invalid File (SRPATH not found)");
24
+ }
25
+
26
+ const version: number = buffer.readUInt8(offset++);
27
+ const revision: number = buffer.readUInt8(offset++);
28
+
29
+ if (version > 0 || revision > 252) {
30
+ throw new Error("Wrong version");
31
+ }
32
+
33
+ const flags: number = buffer.readInt32LE(offset);
34
+
35
+ return {
36
+ version,
37
+ revision,
38
+ flags,
39
+ };
40
+ }
41
+
42
+ function readLocalHeader(buffer: Buffer<ArrayBuffer>): LocalHeader {
43
+ let offset: number = 12;
44
+ const miniRev: number = buffer.readUInt8(offset++);
45
+
46
+ return {
47
+ miniRev,
48
+ };
49
+ }
50
+
51
+ function readNumberNodes(buffer: Buffer<ArrayBuffer>): number {
52
+ let offset: number = 16;
53
+ const numberNodes = buffer.readUInt16LE(offset);
54
+ if (numberNodes <= 0) throw new Error("The number of nodes is too low");
55
+
56
+ return numberNodes;
57
+ }
58
+
59
+ function readNode(offset: number, buffer: Buffer<ArrayBuffer>): PTHNode {
60
+ const node: PTHNode = {
61
+ centre: {
62
+ x: buffer.readInt32LE(offset + 4),
63
+ y: buffer.readInt32LE(offset + 8),
64
+ },
65
+ dir: {
66
+ x: buffer.readFloatLE(offset + 16),
67
+ y: buffer.readFloatLE(offset + 20),
68
+ },
69
+ limits: {
70
+ driveLeft: buffer.readFloatLE(offset + 36),
71
+ driveRight: buffer.readFloatLE(offset + 40),
72
+ },
73
+ };
74
+ return node;
75
+ }
76
+
77
+ function readMainNodes(buffer: Buffer<ArrayBuffer>, length: number): PTHNode[] {
78
+ const startOffset: number = 56;
79
+ const nodes: PTHNode[] = [];
80
+ for (let i: number = 0; i < length; i++) {
81
+ const offset: number = startOffset + 44 * i;
82
+ nodes[i] = readNode(offset, buffer);
83
+ }
84
+
85
+ return nodes;
86
+ }
@@ -0,0 +1,10 @@
1
+ import fs from "fs";
2
+
3
+ export function readPTH(path: string): Buffer<ArrayBuffer> {
4
+ const buffer: Buffer<ArrayBuffer> = fs.readFileSync(path);
5
+ if (buffer === undefined) {
6
+ throw new Error("Invalid File");
7
+ }
8
+
9
+ return buffer;
10
+ }
@@ -0,0 +1,31 @@
1
+ export type PTH = {
2
+ systemHeader: SystemHeader;
3
+ localHeader: LocalHeader;
4
+ numberNodes: number;
5
+ mainNodes: PTHNode[];
6
+ };
7
+
8
+ export type SystemHeader = {
9
+ version: number;
10
+ revision: number;
11
+ flags: number;
12
+ };
13
+
14
+ export type LocalHeader = {
15
+ miniRev: number;
16
+ };
17
+
18
+ export type PTHNode = {
19
+ centre: {
20
+ x: number;
21
+ y: number;
22
+ };
23
+ dir: {
24
+ x: number;
25
+ y: number;
26
+ };
27
+ limits: {
28
+ driveLeft: number;
29
+ driveRight: number;
30
+ };
31
+ };
@@ -0,0 +1,114 @@
1
+ import { LFS_METER, SCALAR_FACTOR } from "./constants";
2
+ import { PTHNode } from "input/types";
3
+ import { DriveLimits, LYTObject, Vector2 } from "./types";
4
+
5
+ // LFS NOTATION
6
+ // 0 = world y axis direction
7
+
8
+ // LYT NOTATION
9
+ // 128 : heading of zero
10
+ // 192 : heading of 90 degrees
11
+ // 0 : heading of 180 degrees
12
+ // 64 : heading of -90 degrees
13
+
14
+ export function transformLYT(
15
+ nodes: PTHNode[],
16
+ numberNodes: number,
17
+ ): LYTObject[] {
18
+ const ZBYTE: number = 240;
19
+ const CHECKPOINT_INDEX: number = 252;
20
+ const FIRST_CHECKPOINT_FLAG: number = 0x01;
21
+ const MAX_NODE: number = 180;
22
+
23
+ const objectArray: LYTObject[] = [];
24
+ const length: number = numberNodes < MAX_NODE ? numberNodes : MAX_NODE;
25
+ const gap: number = numberNodes / length;
26
+
27
+ for (let i: number = 0; i < length; i++) {
28
+ const nodeIndex: number = Math.round(gap * i);
29
+ const node: PTHNode = nodes[nodeIndex];
30
+
31
+ const heading: number = calculateHeading(node.dir.x, node.dir.y);
32
+ const driveLimits: DriveLimits = calculateDriveLimits(node);
33
+ const halfWidth: number = calculateHalfWidth(driveLimits);
34
+ const midPoint: Vector2 = calculateMidPoint(driveLimits);
35
+
36
+ const lytObjectX: number = transformLYTObjectPosition(midPoint.x);
37
+ const lytObjectY: number = transformLYTObjectPosition(midPoint.y);
38
+
39
+ objectArray.push({
40
+ x: lytObjectX,
41
+ y: lytObjectY,
42
+ z: ZBYTE,
43
+ flags: (halfWidth << 2) | FIRST_CHECKPOINT_FLAG,
44
+ index: CHECKPOINT_INDEX,
45
+ heading: heading,
46
+ });
47
+ }
48
+
49
+ return objectArray;
50
+ }
51
+
52
+ export function transformLYTObjectPosition(value: number): number {
53
+ return Math.round(value / SCALAR_FACTOR);
54
+ }
55
+
56
+ export function calculateHeading(dirX: number, dirY: number): number {
57
+ const angleRadians: number = Math.atan2(dirY, dirX);
58
+ const angleDegrees: number = angleRadians * (180 / Math.PI);
59
+ //"Heading represents 360 degrees in 256 values."
60
+ const heading: number =
61
+ Math.round(((angleDegrees - 90 + 180) * 256) / 360) & 0xff;
62
+
63
+ return heading;
64
+ }
65
+
66
+ export function calculateDriveLimits(node: PTHNode): DriveLimits {
67
+ // "A node is represented by a line perpendicular to its direction."
68
+ // perpendicular = 90 degrees
69
+ // rotate 90 degrees to left for find drive limits direct
70
+ const perpendicularX: number = -node.dir.y;
71
+ const perpendicularY: number = node.dir.x;
72
+
73
+ // driveLeft negative
74
+ // driveRight positive
75
+ const left = {
76
+ x: node.centre.x - perpendicularX * node.limits.driveLeft * LFS_METER,
77
+ y: node.centre.y - perpendicularY * node.limits.driveLeft * LFS_METER,
78
+ };
79
+
80
+ const right = {
81
+ x: node.centre.x - perpendicularX * node.limits.driveRight * LFS_METER,
82
+ y: node.centre.y - perpendicularY * node.limits.driveRight * LFS_METER,
83
+ };
84
+
85
+ return { left, right };
86
+ }
87
+
88
+ export function calculateMidPoint(driveLimits: DriveLimits): Vector2 {
89
+ const x: number = (driveLimits.right.x + driveLimits.left.x) / 2;
90
+ const y: number = (driveLimits.right.y + driveLimits.left.y) / 2;
91
+
92
+ return { x, y };
93
+ }
94
+
95
+ // "half width in metres (1 to 31 ...)."
96
+ export function calculateHalfWidth(
97
+ driveLimits: DriveLimits,
98
+ widthOffset: number = 2,
99
+ ): number {
100
+ const distanceX: number = Math.pow(
101
+ driveLimits.right.x - driveLimits.left.x,
102
+ 2,
103
+ );
104
+ const distanceY: number = Math.pow(
105
+ driveLimits.right.y - driveLimits.left.y,
106
+ 2,
107
+ );
108
+ const distance: number = Math.sqrt(distanceX + distanceY);
109
+ const halfWidth: number = Math.round(
110
+ distance / LFS_METER / 2 + widthOffset,
111
+ );
112
+
113
+ return halfWidth > 31 ? 31 : halfWidth;
114
+ }
@@ -0,0 +1,51 @@
1
+ import fs from "fs";
2
+ import { LYTObject } from "./types";
3
+
4
+ export function writeLYT(path: string, objectArray: LYTObject[]) {
5
+ const bufferHeader: Buffer<ArrayBuffer> = buildBufferHeader(
6
+ objectArray.length,
7
+ );
8
+ const bufferObjects: Buffer<ArrayBuffer> =
9
+ buildBufferLYTObject(objectArray);
10
+ const bufferList: Buffer<ArrayBuffer>[] = [bufferHeader, bufferObjects];
11
+ const buffer: Buffer<ArrayBuffer> = Buffer.concat(bufferList);
12
+
13
+ fs.writeFileSync(path, buffer);
14
+ }
15
+
16
+ function buildBufferHeader(length: number) {
17
+ const buffer: Buffer<ArrayBuffer> = Buffer.alloc(12);
18
+
19
+ buffer.write("LFSLYT", 0, "ascii");
20
+ buffer.writeUInt8(0, 6);
21
+ buffer.writeUInt8(252, 7);
22
+ buffer.writeUInt16LE(length, 8);
23
+ buffer.writeUInt8(0, 10);
24
+ buffer.writeUInt8(9, 11);
25
+
26
+ return buffer;
27
+ }
28
+
29
+ function buildBufferLYTObject(objectArray: LYTObject[]) {
30
+ const buffer: Buffer<ArrayBuffer> = Buffer.alloc(8 * objectArray.length);
31
+ let offset: number = 0;
32
+
33
+ for (let i: number = 0; i < objectArray.length; i++) {
34
+ const object: LYTObject = objectArray[i];
35
+
36
+ buffer.writeInt16LE(object.x, offset);
37
+ offset += 2;
38
+ buffer.writeInt16LE(object.y, offset);
39
+ offset += 2;
40
+ buffer.writeUInt8(object.z, offset);
41
+ offset++;
42
+ buffer.writeUInt8(object.flags, offset);
43
+ offset++;
44
+ buffer.writeUInt8(object.index, offset);
45
+ offset++;
46
+ buffer.writeUInt8(object.heading, offset);
47
+ offset++;
48
+ }
49
+
50
+ return buffer;
51
+ }
@@ -0,0 +1,3 @@
1
+ export const LFS_METER: number = 65536; // 1m
2
+ export const LYT_METER: number = 16; // 1m
3
+ export const SCALAR_FACTOR: number = LFS_METER / LYT_METER;
@@ -0,0 +1,18 @@
1
+ export type LYTObject = {
2
+ x: number;
3
+ y: number;
4
+ z: number;
5
+ flags: number;
6
+ index: number;
7
+ heading: number;
8
+ };
9
+
10
+ export type Vector2 = {
11
+ x: number;
12
+ y: number;
13
+ };
14
+
15
+ export type DriveLimits = {
16
+ right: Vector2;
17
+ left: Vector2;
18
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "baseUrl": "./src",
4
+ "rootDir": "./src",
5
+ "outDir": "./dist",
6
+ "strict": true,
7
+ "declaration": true,
8
+ "moduleResolution": "Node",
9
+ "target": "es2015",
10
+ "module": "commonjs",
11
+ "skipLibCheck": true,
12
+ "skipDefaultLibCheck": true,
13
+ "esModuleInterop": true,
14
+ "noImplicitAny": true
15
+ },
16
+ "ts-node": {
17
+ "esm": true
18
+ },
19
+ "include": ["**/*.ts", "src"],
20
+ "exclude": ["dist", "node_modules"]
21
+ }