Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: vertical display for the A380 #76

Closed
wants to merge 34 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
4939d6c
define a rendering folder to support multiple renderings
svengcz Apr 7, 2023
1af6dba
move the pixel conversion of WGS84 to an own function
svengcz Apr 7, 2023
c401a3c
introduce a GPU function to extract the elevation profile
svengcz Apr 7, 2023
a91e6cb
rename the renderer for the ND
svengcz Apr 7, 2023
8457696
add the code to render the VD
svengcz Apr 7, 2023
036d8e6
add the VD to the map handler
svengcz Apr 7, 2023
f52cf36
compile the kernels during initialization
svengcz Apr 7, 2023
c9b85fd
Merge branch 'main' into feature/verticalDisplay
svengcz Jul 10, 2023
5552ea8
define the DTO classes for the communication with the aircraft
svengcz Jul 10, 2023
31a0550
delete unused class
svengcz Jul 10, 2023
8276898
move shared types into a dedicated types-folder
svengcz Jul 10, 2023
3fca476
define threading messages
svengcz Jul 10, 2023
4ab78c6
use the new threading interfaces
svengcz Jul 10, 2023
c5f002c
fix a refactoring error
svengcz Jul 10, 2023
75ffc3a
define the parameter types
svengcz Jul 10, 2023
872491e
use the DisplaySide in all components
svengcz Jul 10, 2023
c1844e3
send the flight path to the map handler thread
svengcz Jul 10, 2023
ef02229
rename the path for the vertical display
svengcz Jul 10, 2023
58165d2
move the ND types also to the types folder
svengcz Jul 12, 2023
855bb09
handle only map data related topics in the map handler
svengcz Jul 19, 2023
20bbe70
define global constants
svengcz Jul 19, 2023
4f8c342
remove unused parameters
svengcz Jul 19, 2023
c95dfb3
handle the display rendering in a rendering module
svengcz Jul 19, 2023
64927db
introduce a terrain worker that orchestrates the different modules
svengcz Jul 19, 2023
39e06d1
use the new terrain worker
svengcz Jul 19, 2023
58e46f4
fix a debugging error
svengcz Jul 19, 2023
c435bf2
keep the map size dimensions during rendering cycles
svengcz Jul 19, 2023
1338c8c
prevent crashes
svengcz Jul 19, 2023
6f1587a
fix a potential crash
svengcz Jul 20, 2023
ae5e29e
fix code call order issues and create local elevation maps inside the…
svengcz Jul 20, 2023
3338489
use the initialized data of the renderers
svengcz Jul 20, 2023
356bd49
fix output reshaping
svengcz Jul 31, 2023
375bb80
fix the map offset calculation
svengcz Jul 31, 2023
046a3d5
fix the rendering error due to map wrap around
svengcz Jul 31, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions apps/server/src/terrain/communication/simconnect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ import {
SystemEventPauseType,
} from '@flybywiresim/msfs-nodejs';
import { Logger } from '../processing/logging/logger';
import { NavigationDisplayData } from '../processing/navigationdisplaydata';
import { AircraftStatus, PositionData, TerrainRenderingMode } from './types';
import { NavigationDisplayData, AircraftStatus, PositionData, TerrainRenderingMode } from '../types';

export type UpdateCallbacks = {
reset: () => void;
Expand Down
19 changes: 19 additions & 0 deletions apps/server/src/terrain/dto/elevationprofilerequest.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNumber } from 'class-validator';
import { ElevationSamplePathDto } from './elevationsamplepath.dto';

export class ElevationProfileRequestDto {
@ApiProperty({
description: 'The array of all waypoints along the requested path',
type: ElevationSamplePathDto,
})
path: ElevationSamplePathDto

@ApiProperty({
description: 'Number of sample points along the path in meters',
example: '100',
type: Number,
})
@IsNumber({ allowInfinity: false, allowNaN: false })
sampleDistance: number
}
10 changes: 10 additions & 0 deletions apps/server/src/terrain/dto/elevationprofileresponse.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ApiProperty } from '@nestjs/swagger';
import { ArrayMinSize, IsArray, IsNumber } from 'class-validator';

export class ElevationProfileResponseDto {
@ApiProperty({ description: 'Sampled elevation points', type: [Number] })
@IsArray()
@IsNumber({ allowInfinity: false, allowNaN: false }, { each: true })
@ArrayMinSize(2)
elevationProfile: number[]
}
10 changes: 10 additions & 0 deletions apps/server/src/terrain/dto/elevationsamplepath.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ApiProperty } from '@nestjs/swagger';
import { ArrayMinSize, IsArray } from 'class-validator';
import { WaypointDto } from './waypoint.dto';

export class ElevationSamplePathDto {
@ApiProperty({ description: 'The array of all waypoints', type: [WaypointDto] })
@IsArray()
@ArrayMinSize(2)
waypoints: WaypointDto[]
}
24 changes: 24 additions & 0 deletions apps/server/src/terrain/dto/waypoint.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNumber, Max, Min } from 'class-validator';

export class WaypointDto {
@ApiProperty({
description: 'Latitude coordinate according to WGS84',
example: '42.197',
type: Number,
})
@IsNumber({ allowNaN: false, allowInfinity: false })
@Min(-90)
@Max(90)
latitude: number

@ApiProperty({
description: 'Longitude coordinate according to WGS84',
example: '13.225',
type: Number,
})
@IsNumber({ allowNaN: false, allowInfinity: false })
@Min(-180)
@Max(180)
longitude: number
}
10 changes: 0 additions & 10 deletions apps/server/src/terrain/fileformat/quadtreenode.ts

This file was deleted.

2 changes: 1 addition & 1 deletion apps/server/src/terrain/fileformat/tile.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { gunzipSync } from 'zlib';
import { ElevationGrid } from './elevationgrid';
import { ElevationGrid } from '../types';
import { TerrainMap } from './terrainmap';

export class Tile {
Expand Down
2 changes: 1 addition & 1 deletion apps/server/src/terrain/mapdata/tilemanager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { fastFlatten } from '../processing/generic/helper';
import { ElevationGrid } from '../fileformat/elevationgrid';
import { ElevationGrid } from '../types';
import { Worldmap } from './worldmap';
import { TerrainMap } from '../fileformat/terrainmap';

Expand Down
17 changes: 2 additions & 15 deletions apps/server/src/terrain/mapdata/worldmap.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,10 @@
import { PositionData } from '../communication/types';
import { ElevationGrid } from '../fileformat/elevationgrid';
import { PositionData, GridDefinition } from '../types';
import { ElevationGrid } from '../types/elevationgrid';
import { TerrainMap } from '../fileformat/terrainmap';
import { Tile } from '../fileformat/tile';
import { projectWgs84 } from '../processing/gpu/helper';
import { TileManager } from './tilemanager';

export interface GridDefinition {
rows: number,
columns: number,
latitudeStep: number,
longitudeStep: number,
}

export interface TileData {
row: number,
column: number,
grid: ElevationGrid,
}

export class Worldmap {
public GridData: GridDefinition = {
rows: 0,
Expand Down
26 changes: 26 additions & 0 deletions apps/server/src/terrain/processing/generic/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// execution parameters
export const GpuProcessingActive = true;

// mathematical conversion constants
export const FeetPerNauticalMile = 6076.12;
export const ThreeNauticalMilesInFeet = 18228.3;
export const NauticalMilesToMetres = 1852;
export const RenderingColorChannelCount = 4;

// map grid creation
export const InvalidElevation = 32767;
export const UnknownElevation = 32766;
export const WaterElevation = -1;
export const DefaultTileSize = 300;

// navigation display parameters
export const NavigationDisplayMapStartOffsetY = 128;
export const NavigationDisplayMaxPixelWidth = 768;
export const NavigationDisplayArcModePixelHeight = 492;
export const NavigationDisplayRoseModePixelHeight = 250;
export const NavigationDisplayMaxPixelHeight = Math.max(NavigationDisplayArcModePixelHeight, NavigationDisplayRoseModePixelHeight);

// rendering parameters
export const RenderingMapTransitionDeltaTime = 40;
export const RenderingMapTransitionDuration = 1000;
export const RenderingMapUpdateTimeout = 1500;
59 changes: 18 additions & 41 deletions apps/server/src/terrain/processing/gpu/elevationmap.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { normalizeHeading, projectWgs84 } from './helper';
import { normalizeHeading, projectWgs84, wgs84toPixelCoordinate } from './helper';
import { rad2deg } from '../generic/helper';
import { LocalElevationMapParameters } from './interfaces';

Expand Down Expand Up @@ -42,46 +42,23 @@ export function createLocalElevationMap(
bearing = normalizeHeading(bearing + heading);

const projected = projectWgs84(latitude, longitude, bearing, distance);
let latStep = 0.0;
if (worldMapSouthwestLat >= latitude) {
// we are at the south pole
latStep = worldMapSouthwestLat + worldMapNortheastLat + 180.0;
} else if (worldMapNortheastLat <= latitude) {
// we are at the north pole
latStep = 180.0 - worldMapSouthwestLat - worldMapNortheastLat;
} else {
latStep = worldMapNortheastLat - worldMapSouthwestLat;
}
latStep /= worldMapHeight;

// get the longitudinal step and check for 180 deg wrap arounds
let longStep = 0.0;
if (worldMapNortheastLong < worldMapSouthwestLong) {
longStep = 180.0 - worldMapSouthwestLong + Math.abs(worldMapNortheastLong + 180.0);
} else {
longStep = worldMapNortheastLong - worldMapSouthwestLong;
}
longStep /= worldMapWidth;

// calculate the pixel movement out of the current position
const latPixelDelta = (groundTruthLatitude - projected[0]) / latStep;

// calculate the pixel delta and check for wrap around situation at 180 deg
let longPixelDelta = 0.0;
if (Math.abs(projected[1] - groundTruthLongitude) >= 180.0) {
if (projected[1] > groundTruthLatitude) {
longPixelDelta = 180.0 - projected[1] + Math.abs(groundTruthLongitude) - 180.0;
} else {
longPixelDelta = 180.0 - groundTruthLongitude + Math.abs(projected[1]) - 180.0;
}
} else {
longPixelDelta = projected[1] - groundTruthLongitude;
}
longPixelDelta /= longStep;
const pixel = wgs84toPixelCoordinate(
latitude,
projected[0],
projected[1],
groundTruthLatitude,
groundTruthLongitude,
worldMapSouthwestLat,
worldMapSouthwestLong,
worldMapNortheastLat,
worldMapNortheastLong,
worldMapWidth,
worldMapHeight,
currentWorldGridX,
currentWorldGridY,
);

const y = currentWorldGridY + latPixelDelta;
const x = currentWorldGridX + longPixelDelta;
if (y < 0 || y > worldMapHeight || x < 0 || x > worldMapWidth) return this.constants.unknownElevation;
if (pixel[1] < 0 || pixel[1] >= worldMapHeight || pixel[0] < 0 || pixel[0] >= worldMapWidth) return this.constants.unknownElevation;

return worldMap[y][x];
return worldMap[pixel[1]][pixel[0]];
}
137 changes: 137 additions & 0 deletions apps/server/src/terrain/processing/gpu/elevationprofile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// disable the destructuring because of GPU.JS
/* eslint-disable prefer-destructuring */
import { bearingWgs84, projectWgs84, wgs84toPixelCoordinate } from './helper';
import { distanceWgs84 } from '../generic/helper';
import { ElevationProfileParameters } from './interfaces';

export function createElevationProfile(
this: ElevationProfileParameters,
latitude: number,
longitude: number,
groundTruthLatitude: number,
groundTruthLongitude: number,
currentWorldGridX: number,
currentWorldGridY: number,
worldMap: number[][],
worldMapWidth: number,
worldMapHeight: number,
worldMapSouthwestLat: number,
worldMapSouthwestLong: number,
worldMapNortheastLat: number,
worldMapNortheastLong: number,
pathOffset: number,
waypointsLatitudes: number[],
waypointsLongitudes: number[],
waypointsPointCount: number,
distancePerPixel: number,
): number {
const distanceForPixel = distancePerPixel * this.thread.x;
let routeSegmentIndex = waypointsPointCount;
let routeStartPointDistance = 0.0;
let startLatitude = latitude;
let startLongitude = longitude;

// find the correct starting point
for (let i = 0; i < waypointsPointCount; ++i) {
const currentDistance = distanceWgs84(startLatitude, startLongitude, waypointsLatitudes[i], waypointsLongitudes[i]);
if (routeStartPointDistance + currentDistance >= distanceForPixel) {
routeSegmentIndex = i;
break;
}

routeStartPointDistance += currentDistance;
startLatitude = waypointsLatitudes[i];
startLongitude = waypointsLongitudes[i];
}

// check if we exceeded the points
if (routeSegmentIndex >= waypointsPointCount) {
return this.constants.invalidElevation;
}

// get the required projection of latitude, longitude
const remainingDistance = (distanceForPixel - routeStartPointDistance) * 1852.0;
const bearing = bearingWgs84(startLatitude, startLongitude, waypointsLatitudes[routeSegmentIndex], waypointsLongitudes[routeSegmentIndex]);
const centerPosition = projectWgs84(startLatitude, startLongitude, bearing, remainingDistance);

/*
* The VD uses a hose along the fly-path or route and uses the maximum elevation inside the hose for the visualization.
* Therefore are orthogonal points at the centerPosition required (left and right of track) to define a sampling line
* to find the maximum elevation. The startPixel and endPixel define the endpoints of the line
*/
let bearingStart = bearing - 90.0;
if (bearingStart < 0.0) bearingStart += 360.0;
let bearingEnd = bearing + 90.0;
if (bearingEnd >= 360.0) bearingEnd -= 360.0;
const offsetMeters = pathOffset * 1852.0;

const startProjected = projectWgs84(centerPosition[0], centerPosition[1], bearingStart, offsetMeters);
const startPixel = wgs84toPixelCoordinate(
latitude,
startProjected[0],
startProjected[1],
groundTruthLatitude,
groundTruthLongitude,
worldMapSouthwestLat,
worldMapSouthwestLong,
worldMapNortheastLat,
worldMapNortheastLong,
worldMapWidth,
worldMapHeight,
currentWorldGridX,
currentWorldGridY,
);
const endProjected = projectWgs84(centerPosition[0], centerPosition[1], bearingEnd, offsetMeters);
const endPixel = wgs84toPixelCoordinate(
latitude,
endProjected[0],
endProjected[1],
groundTruthLatitude,
groundTruthLongitude,
worldMapSouthwestLat,
worldMapSouthwestLong,
worldMapNortheastLat,
worldMapNortheastLong,
worldMapWidth,
worldMapHeight,
currentWorldGridX,
currentWorldGridY,
);

/*
* Use a modified Bresenham line algorithm to sample the line along the world map pixels
*/
const deltaX = Math.abs(endPixel[0] - startPixel[0]);
const stepX = startPixel[0] < endPixel[0] ? 1 : -1;
const deltaY = -1.0 * Math.abs(endPixel[1] - startPixel[1]);
const stepY = startPixel[1] < endPixel[1] ? 1 : -1;
let error = deltaX + deltaY;
let maxElevation = -1000;
let x = startPixel[0];
let y = startPixel[1];

while (true) {
if (y >= 0 && y < worldMapHeight && x >= 0 && x < worldMapWidth) {
const elevation = worldMap[y][x];
if (elevation !== this.constants.invalidElevation && elevation !== this.constants.unknownElevation && elevation > maxElevation) {
maxElevation = elevation;
}
}

if (x === endPixel[0] && y === endPixel[1]) break;

const errorDouble = 2.0 * error;
if (errorDouble >= deltaY) {
if (x === endPixel[0]) break;
error += deltaY;
x += stepX;
}
if (errorDouble <= deltaX) {
if (y === endPixel[1]) break;
error += deltaX;
y += stepY;
}
}

return maxElevation;
}
Loading