Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
230 changes: 165 additions & 65 deletions src/components/widgets/CompassHUD.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div class="main">
<canvas ref="canvasRef" :width="canvasSize.width" :height="canvasSize.height" />
<!-- POI icons container -->
<div v-if="widget.options.poi?.showPoiOnHUD" ref="poiIconsContainer">
<div v-if="widget.options.poi?.showPoiOnHUD || widget.options.showHomeOnHUD" ref="poiIconsContainer">
<div
v-for="poiMarker in poiMarkers"
:key="poiMarker.poiId"
Expand All @@ -29,7 +29,9 @@
<!-- POI distance box for onHudSide mode -->
<div
v-if="
widget.options.poi?.showPoiOnHUD && widget.options.poi?.showDistances === 'onHudSide' && highlightedPoiMarker
(widget.options.poi?.showPoiOnHUD || widget.options.showHomeOnHUD) &&
widget.options.poi?.showDistances === 'onHudSide' &&
highlightedPoiMarker
"
ref="poiSideDistanceBox"
class="poi-side-distance-box"
Expand Down Expand Up @@ -73,6 +75,16 @@
/>
</div>
<v-divider class="mb-3 opacity-5" />
<div class="flex w-full justify-between">
<v-switch
class="ma-1 w-[220px]"
label="Show home on HUD"
:color="widget.options.showHomeOnHUD ? 'white' : undefined"
:model-value="widget.options.showHomeOnHUD"
hide-details
@change="widget.options.showHomeOnHUD = !widget.options.showHomeOnHUD"
/>
</div>
<div class="flex w-full justify-between">
<v-switch
class="ma-1 w-[220px]"
Expand All @@ -85,7 +97,7 @@
"
/>
<v-select
v-if="widget.options.poi?.showPoiOnHUD"
v-if="widget.options.poi?.showPoiOnHUD || widget.options.showHomeOnHUD"
v-model="widget.options.poi.showDistances"
:items="[
{ title: 'All markers', value: 'all' },
Expand Down Expand Up @@ -207,6 +219,7 @@ onBeforeMount(() => {
showYawValue: true,
hudColor: colorSwatches.value[0][0],
useNegativeRange: false,
showHomeOnHUD: true,
poi: {
showPoiOnHUD: true,
showDistances: 'onHudSide',
Expand Down Expand Up @@ -293,6 +306,9 @@ const poiData = computed(() => {
})
})

const homeCoordinates = computed(() => missionStore.homeMarkerPosition)
const homeMarkerId = '__home__'

const canvasRef = ref<HTMLCanvasElement | undefined>()
const canvasContext = ref()
const poiMarkers = ref<PoiMarker[]>([])
Expand Down Expand Up @@ -429,7 +445,11 @@ const renderCanvas = (): void => {
}

const updatePoiMarkers = (): void => {
if (!widget.value.options.poi?.showPoiOnHUD || !store.coordinates.latitude || !store.coordinates.longitude) {
if (
(!widget.value.options.poi?.showPoiOnHUD && !widget.value.options.showHomeOnHUD) ||
!store.coordinates.latitude ||
!store.coordinates.longitude
) {
poiMarkers.value = []
return
}
Expand All @@ -440,69 +460,139 @@ const updatePoiMarkers = (): void => {
const markers: PoiMarker[] = []
const onHeading = new Set<string>()

poiData.value.forEach(({ poi, distance, bearing }) => {
if (widget.value.options.poi?.showPoiOnHUD) {
poiData.value.forEach(({ poi, distance, bearing }) => {
let relativeBearing = bearing - yaw.value
if (relativeBearing < -180) relativeBearing += 360
if (relativeBearing > 180) relativeBearing -= 360

if (Math.abs(relativeBearing) > 90) return

if (isMarkerOnHeading(bearing, yaw.value)) {
onHeading.add(poi.id)
}

const x = halfWidth + ((2 * halfWidth) / Math.PI) * Math.sin(radians(relativeBearing))
const isReachedNow = distance <= 1
// How long the POI will stay marked as reached (blinking animation)
if (isReachedNow) {
reachedMarkers.value.set(poi.id, {
poiId: poi.id,
reachedAt: now,
expiresAt: now + 20000,
})
}

const reachedData = reachedMarkers.value.get(poi.id)
const isReached = isReachedNow || (reachedData !== undefined && now < reachedData.expiresAt)
// How far from a POI to mark as reached (2 meters)
const distanceText = isReached
? 'Reached'
: distance >= 2000
? `${(distance / 1000).toFixed(1)}km`
: `${Math.round(distance)}m`

const mode = widget.value.options.poi?.showDistances
const isHighlighted = highlightedMarkers.value.has(poi.id)
const isOnHeadingNow = onHeading.has(poi.id)
let distanceLabelOpacity: string | undefined
let distanceLabelZIndex: string | undefined
let zIndex: string | undefined

if (mode === 'highlightedMarker') {
const angularDist = Math.abs(relativeBearing)
const opacity = angularDist <= 60 ? (1.0 - (angularDist / 60) * 0.6).toFixed(2) : '0.4'
distanceLabelOpacity = isHighlighted || isOnHeadingNow ? '1.0' : opacity
}

markers.push({
poiId: poi.id,
name: poi.name,
icon: poi.icon,
color: poi.color || '#FF0000',
size: 10,
distanceText,
distanceFontSize: 9,
...(distanceLabelOpacity && { distanceLabelOpacity }),
...(distanceLabelZIndex && { distanceLabelZIndex }),
...(isReached && { isReached: true }),
style: {
left: `${x}px`,
top: '38%',
transform: 'translate(-50%, -50%)',
...(zIndex && { zIndex }),
},
})
})
}

if (widget.value.options.showHomeOnHUD && homeCoordinates.value) {
const distance = calculateHaversineDistance(
[store.coordinates.latitude!, store.coordinates.longitude!],
homeCoordinates.value
)
const bearing = calculateBearing(
store.coordinates.latitude!,
store.coordinates.longitude!,
homeCoordinates.value[0],
homeCoordinates.value[1]
)
let relativeBearing = bearing - yaw.value
if (relativeBearing < -180) relativeBearing += 360
if (relativeBearing > 180) relativeBearing -= 360

if (Math.abs(relativeBearing) > 90) return
if (Math.abs(relativeBearing) <= 90) {
if (isMarkerOnHeading(bearing, yaw.value)) {
onHeading.add(homeMarkerId)
}

if (isMarkerOnHeading(bearing, yaw.value)) {
onHeading.add(poi.id)
}
const x = halfWidth + ((2 * halfWidth) / Math.PI) * Math.sin(radians(relativeBearing))
const isReachedNow = distance <= 1
if (isReachedNow) {
reachedMarkers.value.set(homeMarkerId, {
poiId: homeMarkerId,
reachedAt: now,
expiresAt: now + 20000,
})
}

const x = halfWidth + ((2 * halfWidth) / Math.PI) * Math.sin(radians(relativeBearing))
const isReachedNow = distance <= 1
// How long the POI will stay marked as reached (blinking animation)
if (isReachedNow) {
reachedMarkers.value.set(poi.id, {
poiId: poi.id,
reachedAt: now,
expiresAt: now + 20000,
})
}
const reachedData = reachedMarkers.value.get(homeMarkerId)
const isReached = isReachedNow || (reachedData !== undefined && now < reachedData.expiresAt)
const distanceText = isReached
? 'Reached'
: distance >= 2000
? `${(distance / 1000).toFixed(1)}km`
: `${Math.round(distance)}m`

const mode = widget.value.options.poi?.showDistances
const isHighlighted = highlightedMarkers.value.has(homeMarkerId)
const isOnHeadingNow = onHeading.has(homeMarkerId)
let distanceLabelOpacity: string | undefined

if (mode === 'highlightedMarker') {
const angularDist = Math.abs(relativeBearing)
const opacity = angularDist <= 60 ? (1.0 - (angularDist / 60) * 0.6).toFixed(2) : '0.4'
distanceLabelOpacity = isHighlighted || isOnHeadingNow ? '1.0' : opacity
}

const reachedData = reachedMarkers.value.get(poi.id)
const isReached = isReachedNow || (reachedData !== undefined && now < reachedData.expiresAt)
// How far from a POI to mark as reached (2 meters)
const distanceText = isReached
? 'Reached'
: distance >= 2000
? `${(distance / 1000).toFixed(1)}km`
: `${Math.round(distance)}m`

const mode = widget.value.options.poi?.showDistances
const isHighlighted = highlightedMarkers.value.has(poi.id)
const isOnHeadingNow = onHeading.has(poi.id)
let distanceLabelOpacity: string | undefined
let distanceLabelZIndex: string | undefined
let zIndex: string | undefined

if (mode === 'highlightedMarker') {
const angularDist = Math.abs(relativeBearing)
const opacity = angularDist <= 60 ? (1.0 - (angularDist / 60) * 0.6).toFixed(2) : '0.4'
distanceLabelOpacity = isHighlighted || isOnHeadingNow ? '1.0' : opacity
markers.push({
poiId: homeMarkerId,
name: 'Home',
icon: 'mdi-home',
color: '#1E88E5',
size: 12,
distanceText,
distanceFontSize: 9,
...(distanceLabelOpacity && { distanceLabelOpacity }),
...(isReached && { isReached: true }),
style: {
left: `${x}px`,
top: '38%',
transform: 'translate(-50%, -50%)',
},
})
}

markers.push({
poiId: poi.id,
name: poi.name,
icon: poi.icon,
color: poi.color || '#FF0000',
size: 10,
distanceText,
distanceFontSize: 9,
...(distanceLabelOpacity && { distanceLabelOpacity }),
...(distanceLabelZIndex && { distanceLabelZIndex }),
...(isReached && { isReached: true }),
style: {
left: `${x}px`,
top: '38%',
transform: 'translate(-50%, -50%)',
...(zIndex && { zIndex }),
},
})
})
}

markersOnHeading.value = onHeading
cleanupExpired()
Expand Down Expand Up @@ -545,15 +635,25 @@ const updatePoiMarkers = (): void => {
}
lastCardShownAt = now
} else {
const poi = missionStore.pointsOfInterest.find((p) => p.id === selectedId)
if (poi && store.coordinates.latitude && store.coordinates.longitude) {
let fallbackName: string | undefined
let fallbackCoords: [number, number] | undefined

if (selectedId === homeMarkerId) {
fallbackName = 'Home'
fallbackCoords = homeCoordinates.value
} else {
const poi = missionStore.pointsOfInterest.find((p) => p.id === selectedId)
fallbackName = poi?.name
fallbackCoords = poi?.coordinates
}

if (fallbackName && fallbackCoords && store.coordinates.latitude && store.coordinates.longitude) {
const distance = calculateHaversineDistance(
[store.coordinates.latitude, store.coordinates.longitude],
poi.coordinates
fallbackCoords
)
// How far from a POI to mark as reached
highlightedPoiMarker.value = {
name: poi.name,
name: fallbackName,
distanceText:
distance <= 1
? 'Reached'
Expand Down Expand Up @@ -622,7 +722,7 @@ const stopAnimationLoop = (): void => {
}

const debouncedUpdatePoiMarkers = useDebounceFn(updatePoiMarkers, 16)
watch([poiData, store.coordinates, canvasSize, yaw], debouncedUpdatePoiMarkers)
watch([poiData, store.coordinates, canvasSize, yaw, homeCoordinates], debouncedUpdatePoiMarkers)

// Start both canvas and POI markers animation loop when widget becomes visible or data changes
watch([renderVars, canvasSize, widget.value.options], () => {
Expand Down
8 changes: 7 additions & 1 deletion src/components/widgets/Map.vue
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,6 @@ const router = useRouter()
const map = shallowRef<Map | undefined>()
const zoom = ref(missionStore.userLastMapZoom ?? missionStore.defaultMapZoom)
const mapCenter = ref<WaypointCoordinates>(missionStore.userLastMapCenter ?? missionStore.defaultMapCenter)
const home = ref()
const mapId = computed(() => `map-${widget.value.hash}`)
const showButtons = computed(() => isMouseOver.value || downloadMenuOpen.value)
const mapReady = ref(false)
Expand All @@ -263,6 +262,13 @@ const downloadMenuOpen = ref(false)
const missionItemsInVehicle = ref<Waypoint[]>([])
const missionSeqToMarkerSeq = shallowRef<Record<number, number>>({})

const home = computed({
get: () => missionStore.homeMarkerPosition,
set: (value: WaypointCoordinates | undefined) => {
missionStore.homeMarkerPosition = value
},
})

const glassMenuCssVars = computed(() => ({
'--glass-background': interfaceStore.globalGlassMenuStyles.backgroundColor,
'--glass-filter': interfaceStore.globalGlassMenuStyles.backdropFilter,
Expand Down
5 changes: 4 additions & 1 deletion src/stores/mainVehicle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,9 @@ export const useMainVehicleStore = defineStore('main-vehicle', () => {
if (mainVehicle.value.firmware() !== Vehicle.Firmware.ArduPilot) {
throw new Error('Home waypoint retrieval is only supported for ArduPilot vehicles.')
}
return await mainVehicle.value.fetchHomeWaypoint()
const homeWaypoint = await mainVehicle.value.fetchHomeWaypoint()
missionStore.homeMarkerPosition = homeWaypoint.coordinates
return homeWaypoint
}

/**
Expand All @@ -478,6 +480,7 @@ export const useMainVehicleStore = defineStore('main-vehicle', () => {
throw new Error('No vehicle available to set home waypoint.')
}
await mainVehicle.value.setHomeWaypoint(coordinate, height)
missionStore.homeMarkerPosition = coordinate
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/stores/mission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export const useMissionStore = defineStore('mission', () => {
)
const mapDownloadMissionFromVehicle = ref<(() => Promise<void>) | null>(null)
const mapClearMapDrawing = ref<(() => void) | null>(null)
const homeMarkerPosition = ref<WaypointCoordinates | undefined>(undefined)

const { showDialog } = useInteractionDialog()

Expand Down Expand Up @@ -469,5 +470,6 @@ export const useMissionStore = defineStore('mission', () => {
vehiclePositionHistory,
isVehiclePositionHistoryPersistent,
clearVehicleHistory,
homeMarkerPosition,
}
})
8 changes: 7 additions & 1 deletion src/views/MissionPlanningView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -735,7 +735,6 @@ const downloadMissionFromVehicle = async (): Promise<void> => {

const planningMap = shallowRef<Map | undefined>()
const mapCenter = ref<WaypointCoordinates>(missionStore.userLastMapCenter ?? missionStore.defaultMapCenter)
const home = ref<WaypointCoordinates | undefined>(undefined)
const zoom = ref(missionStore.userLastMapZoom ?? missionStore.defaultMapZoom)
const followerTarget = ref<WhoToFollow | undefined>(undefined)
const currentWaypointAltitude = ref(0)
Expand Down Expand Up @@ -790,6 +789,13 @@ let measureTextEl: HTMLDivElement | null = null
const surveyAreaMarkers = shallowRef<Record<string, L.Marker>>({})
const liveSurveyAreaMarker = shallowRef<L.Marker | null>(null)

const home = computed({
get: () => missionStore.homeMarkerPosition,
set: (value: WaypointCoordinates | undefined) => {
missionStore.homeMarkerPosition = value
},
})

const glassMenuCssVars = computed(() => ({
'--glass-background': interfaceStore.globalGlassMenuStyles.backgroundColor,
'--glass-filter': interfaceStore.globalGlassMenuStyles.backdropFilter,
Expand Down
Loading