|
|
@@ -57,6 +57,8 @@ const AUTO_ROTATE_SNAP_DEG = 0.1
|
|
|
const AUTO_ROTATE_DEADZONE_DEG = 4
|
|
|
const AUTO_ROTATE_MAX_STEP_DEG = 0.75
|
|
|
const AUTO_ROTATE_HEADING_SMOOTHING = 0.46
|
|
|
+const COMPASS_NEEDLE_FRAME_MS = 16
|
|
|
+const COMPASS_NEEDLE_SNAP_DEG = 0.08
|
|
|
const COMPASS_TUNING_PRESETS: Record<CompassTuningProfile, {
|
|
|
needleMinSmoothing: number
|
|
|
needleMaxSmoothing: number
|
|
|
@@ -827,6 +829,7 @@ export class MapEngine {
|
|
|
previewResetTimer: number
|
|
|
viewSyncTimer: number
|
|
|
autoRotateTimer: number
|
|
|
+ compassNeedleTimer: number
|
|
|
pendingViewPatch: Partial<MapEngineViewState>
|
|
|
mounted: boolean
|
|
|
diagnosticUiEnabled: boolean
|
|
|
@@ -834,6 +837,7 @@ export class MapEngine {
|
|
|
sensorHeadingDeg: number | null
|
|
|
smoothedSensorHeadingDeg: number | null
|
|
|
compassDisplayHeadingDeg: number | null
|
|
|
+ targetCompassDisplayHeadingDeg: number | null
|
|
|
compassSource: 'compass' | 'motion' | null
|
|
|
compassTuningProfile: CompassTuningProfile
|
|
|
smoothedMovementHeadingDeg: number | null
|
|
|
@@ -1277,6 +1281,7 @@ export class MapEngine {
|
|
|
this.previewResetTimer = 0
|
|
|
this.viewSyncTimer = 0
|
|
|
this.autoRotateTimer = 0
|
|
|
+ this.compassNeedleTimer = 0
|
|
|
this.pendingViewPatch = {}
|
|
|
this.mounted = false
|
|
|
this.diagnosticUiEnabled = false
|
|
|
@@ -1284,6 +1289,7 @@ export class MapEngine {
|
|
|
this.sensorHeadingDeg = null
|
|
|
this.smoothedSensorHeadingDeg = null
|
|
|
this.compassDisplayHeadingDeg = null
|
|
|
+ this.targetCompassDisplayHeadingDeg = null
|
|
|
this.compassSource = null
|
|
|
this.compassTuningProfile = 'balanced'
|
|
|
this.smoothedMovementHeadingDeg = null
|
|
|
@@ -1399,6 +1405,7 @@ export class MapEngine {
|
|
|
this.clearPreviewResetTimer()
|
|
|
this.clearViewSyncTimer()
|
|
|
this.clearAutoRotateTimer()
|
|
|
+ this.clearCompassNeedleTimer()
|
|
|
this.clearPunchFeedbackTimer()
|
|
|
this.clearContentCardTimer()
|
|
|
this.clearMapPulseTimer()
|
|
|
@@ -2940,27 +2947,19 @@ export class MapEngine {
|
|
|
const compassHeadingDeg = getCompassReferenceHeadingDeg(this.northReferenceMode, this.smoothedSensorHeadingDeg)
|
|
|
if (this.compassDisplayHeadingDeg === null) {
|
|
|
this.compassDisplayHeadingDeg = compassHeadingDeg
|
|
|
+ this.targetCompassDisplayHeadingDeg = compassHeadingDeg
|
|
|
+ this.syncCompassDisplayState()
|
|
|
} else {
|
|
|
+ this.targetCompassDisplayHeadingDeg = compassHeadingDeg
|
|
|
const displayDeltaDeg = Math.abs(normalizeAngleDeltaDeg(compassHeadingDeg - this.compassDisplayHeadingDeg))
|
|
|
if (displayDeltaDeg >= COMPASS_TUNING_PRESETS[this.compassTuningProfile].displayDeadzoneDeg) {
|
|
|
- this.compassDisplayHeadingDeg = interpolateAngleDeg(
|
|
|
- this.compassDisplayHeadingDeg,
|
|
|
- compassHeadingDeg,
|
|
|
- getCompassNeedleSmoothingFactor(
|
|
|
- this.compassDisplayHeadingDeg,
|
|
|
- compassHeadingDeg,
|
|
|
- this.compassTuningProfile,
|
|
|
- ),
|
|
|
- )
|
|
|
+ this.scheduleCompassNeedleFollow()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
this.autoRotateHeadingDeg = this.resolveAutoRotateInputHeadingDeg()
|
|
|
-
|
|
|
this.setState({
|
|
|
- compassNeedleDeg: formatCompassNeedleDegForMode(this.northReferenceMode, this.compassDisplayHeadingDeg),
|
|
|
- sensorHeadingText: formatHeadingText(this.compassDisplayHeadingDeg),
|
|
|
- compassDeclinationText: formatCompassDeclinationText(this.northReferenceMode),
|
|
|
+ compassSourceText: formatCompassSourceText(this.compassSource),
|
|
|
...(this.diagnosticUiEnabled
|
|
|
? {
|
|
|
...this.getTelemetrySensorViewPatch(),
|
|
|
@@ -2986,9 +2985,11 @@ export class MapEngine {
|
|
|
|
|
|
handleCompassError(message: string): void {
|
|
|
this.clearAutoRotateTimer()
|
|
|
+ this.clearCompassNeedleTimer()
|
|
|
this.targetAutoRotationDeg = null
|
|
|
this.autoRotateCalibrationPending = false
|
|
|
this.compassSource = null
|
|
|
+ this.targetCompassDisplayHeadingDeg = null
|
|
|
this.setState({
|
|
|
compassSourceText: formatCompassSourceText(null),
|
|
|
autoRotateCalibrationText: formatAutoRotateCalibrationText(false, this.autoRotateCalibrationOffsetDeg),
|
|
|
@@ -3013,6 +3014,7 @@ export class MapEngine {
|
|
|
this.northReferenceMode = nextMode
|
|
|
this.autoRotateCalibrationOffsetDeg = nextMapNorthOffsetDeg
|
|
|
this.compassDisplayHeadingDeg = compassHeadingDeg
|
|
|
+ this.targetCompassDisplayHeadingDeg = compassHeadingDeg
|
|
|
|
|
|
if (this.state.orientationMode === 'north-up') {
|
|
|
const exactCenter = this.getExactCenterFromTranslate(this.state.tileTranslateX, this.state.tileTranslateY)
|
|
|
@@ -3056,6 +3058,10 @@ export class MapEngine {
|
|
|
if (this.state.orientationMode === 'heading-up' && this.refreshAutoRotateTarget()) {
|
|
|
this.scheduleAutoRotate()
|
|
|
}
|
|
|
+
|
|
|
+ if (this.compassDisplayHeadingDeg !== null) {
|
|
|
+ this.syncCompassDisplayState()
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
setCourseHeading(headingDeg: number | null): void {
|
|
|
@@ -3570,6 +3576,78 @@ export class MapEngine {
|
|
|
this.autoRotateTimer = 0
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ clearCompassNeedleTimer(): void {
|
|
|
+ if (this.compassNeedleTimer) {
|
|
|
+ clearTimeout(this.compassNeedleTimer)
|
|
|
+ this.compassNeedleTimer = 0
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ syncCompassDisplayState(): void {
|
|
|
+ this.setState({
|
|
|
+ compassNeedleDeg: formatCompassNeedleDegForMode(this.northReferenceMode, this.compassDisplayHeadingDeg),
|
|
|
+ sensorHeadingText: formatHeadingText(this.compassDisplayHeadingDeg),
|
|
|
+ compassDeclinationText: formatCompassDeclinationText(this.northReferenceMode),
|
|
|
+ ...(this.diagnosticUiEnabled
|
|
|
+ ? {
|
|
|
+ ...this.getTelemetrySensorViewPatch(),
|
|
|
+ northReferenceButtonText: formatNorthReferenceButtonText(this.northReferenceMode),
|
|
|
+ autoRotateSourceText: this.getAutoRotateSourceText(),
|
|
|
+ northReferenceText: formatNorthReferenceText(this.northReferenceMode),
|
|
|
+ }
|
|
|
+ : {}),
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ scheduleCompassNeedleFollow(): void {
|
|
|
+ if (
|
|
|
+ this.compassNeedleTimer
|
|
|
+ || this.targetCompassDisplayHeadingDeg === null
|
|
|
+ || this.compassDisplayHeadingDeg === null
|
|
|
+ ) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const step = () => {
|
|
|
+ this.compassNeedleTimer = 0
|
|
|
+
|
|
|
+ if (
|
|
|
+ this.targetCompassDisplayHeadingDeg === null
|
|
|
+ || this.compassDisplayHeadingDeg === null
|
|
|
+ ) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const deltaDeg = normalizeAngleDeltaDeg(
|
|
|
+ this.targetCompassDisplayHeadingDeg - this.compassDisplayHeadingDeg,
|
|
|
+ )
|
|
|
+ const absDeltaDeg = Math.abs(deltaDeg)
|
|
|
+
|
|
|
+ if (absDeltaDeg <= COMPASS_NEEDLE_SNAP_DEG) {
|
|
|
+ if (absDeltaDeg > 0.001) {
|
|
|
+ this.compassDisplayHeadingDeg = this.targetCompassDisplayHeadingDeg
|
|
|
+ this.syncCompassDisplayState()
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ this.compassDisplayHeadingDeg = interpolateAngleDeg(
|
|
|
+ this.compassDisplayHeadingDeg,
|
|
|
+ this.targetCompassDisplayHeadingDeg,
|
|
|
+ getCompassNeedleSmoothingFactor(
|
|
|
+ this.compassDisplayHeadingDeg,
|
|
|
+ this.targetCompassDisplayHeadingDeg,
|
|
|
+ this.compassTuningProfile,
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ this.syncCompassDisplayState()
|
|
|
+ this.scheduleCompassNeedleFollow()
|
|
|
+ }
|
|
|
+
|
|
|
+ this.compassNeedleTimer = setTimeout(step, COMPASS_NEEDLE_FRAME_MS) as unknown as number
|
|
|
+ }
|
|
|
+
|
|
|
pickViewPatch(patch: Partial<MapEngineViewState>): Partial<MapEngineViewState> {
|
|
|
const viewPatch = {} as Partial<MapEngineViewState>
|
|
|
for (const key of VIEW_SYNC_KEYS) {
|