diff --git a/app/src/main/java/com/kylecorry/trail_sense/tools/augmented_reality/ARPathLayer.kt b/app/src/main/java/com/kylecorry/trail_sense/tools/augmented_reality/ARPathLayer.kt new file mode 100644 index 000000000..b7a7ea7f3 --- /dev/null +++ b/app/src/main/java/com/kylecorry/trail_sense/tools/augmented_reality/ARPathLayer.kt @@ -0,0 +1,100 @@ +package com.kylecorry.trail_sense.tools.augmented_reality + +import com.kylecorry.andromeda.canvas.ICanvasDrawer +import com.kylecorry.andromeda.core.units.PixelCoordinate +import com.kylecorry.sol.units.Coordinate +import com.kylecorry.sol.units.Distance +import com.kylecorry.trail_sense.tools.augmented_reality.position.GeographicARPoint +import com.kylecorry.trail_sense.tools.navigation.ui.IMappableLocation +import com.kylecorry.trail_sense.tools.navigation.ui.IMappablePath +import com.kylecorry.trail_sense.tools.navigation.ui.MappablePath +import com.kylecorry.trail_sense.tools.paths.ui.IPathLayer + +class ARPathLayer(private val viewDistance: Distance) : ARLayer, IPathLayer { + + private val lineLayer = ARLineLayer() + private val pointLayer = ARMarkerLayer(0.1f, 6f) + private var lastLocation = Coordinate.zero + private val viewDistanceMeters = viewDistance.meters().distance + + override fun draw(drawer: ICanvasDrawer, view: AugmentedRealityView) { + lastLocation = view.location + lineLayer.draw(drawer, view) + pointLayer.draw(drawer, view) + } + + override fun invalidate() { + lineLayer.invalidate() + pointLayer.invalidate() + } + + override fun onClick( + drawer: ICanvasDrawer, + view: AugmentedRealityView, + pixel: PixelCoordinate + ): Boolean { + return false + } + + override fun onFocus(drawer: ICanvasDrawer, view: AugmentedRealityView): Boolean { + return false + } + + override fun setPaths(paths: List) { + + val location = lastLocation + + val splitPaths = paths.flatMap { path -> + clipPath(path, location, viewDistanceMeters) + } + + lineLayer.setLines(splitPaths.map { path -> + ARLine( + path.points.map { + GeographicARPoint(it.coordinate, it.elevation) + }, + path.color, + 1f, + ARLine.ThicknessUnits.Dp + ) + }) + + // Only render the closest 20 points + val nearby = splitPaths.flatMap { path -> + path.points.map { + it to path.color + } + }.sortedBy { + it.first.coordinate.distanceTo(location) + }.take(20).map { + ARMarker( + GeographicARPoint(it.first.coordinate, it.first.elevation), + CanvasCircle(it.second) + ) + } + pointLayer.setMarkers(nearby) + } + + private fun clipPath(path: IMappablePath, location: Coordinate, distance: Float): List { + val clipped = mutableListOf() + val currentPoints = mutableListOf() + + for (point in path.points) { + if (point.coordinate.distanceTo(location) < distance) { + currentPoints.add(point) + } else { + // TODO: Clip instead of remove + if (currentPoints.isNotEmpty()) { + clipped.add(MappablePath(path.id, currentPoints.toList(), path.color, path.style)) + currentPoints.clear() + } + } + } + + if (currentPoints.isNotEmpty()) { + clipped.add(MappablePath(path.id, currentPoints.toList(), path.color, path.style)) + } + + return clipped + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kylecorry/trail_sense/tools/augmented_reality/AugmentedRealityFragment.kt b/app/src/main/java/com/kylecorry/trail_sense/tools/augmented_reality/AugmentedRealityFragment.kt index bd19b08ff..03dfd153c 100644 --- a/app/src/main/java/com/kylecorry/trail_sense/tools/augmented_reality/AugmentedRealityFragment.kt +++ b/app/src/main/java/com/kylecorry/trail_sense/tools/augmented_reality/AugmentedRealityFragment.kt @@ -11,11 +11,14 @@ import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.navigation.NavController import com.kylecorry.andromeda.core.system.Resources +import com.kylecorry.andromeda.core.time.CoroutineTimer import com.kylecorry.andromeda.core.ui.Colors.withAlpha import com.kylecorry.andromeda.fragments.BoundFragment import com.kylecorry.andromeda.fragments.observeFlow import com.kylecorry.andromeda.sense.Sensors import com.kylecorry.sol.science.astronomy.moon.MoonPhase +import com.kylecorry.sol.science.geology.CoordinateBounds +import com.kylecorry.sol.science.geology.Geofence import com.kylecorry.sol.units.Distance import com.kylecorry.trail_sense.R import com.kylecorry.trail_sense.databinding.FragmentAugmentedRealityBinding @@ -33,6 +36,7 @@ import com.kylecorry.trail_sense.shared.withId import com.kylecorry.trail_sense.tools.augmented_reality.guide.ARGuide import com.kylecorry.trail_sense.tools.augmented_reality.guide.AstronomyARGuide import com.kylecorry.trail_sense.tools.augmented_reality.guide.NavigationARGuide +import com.kylecorry.trail_sense.tools.maps.infrastructure.layers.PathLayerManager import java.time.ZonedDateTime import kotlin.math.hypot @@ -85,9 +89,19 @@ class AugmentedRealityFragment : BoundFragment( ) } + private val pathsLayer by lazy { + ARPathLayer(Distance.meters(userPrefs.augmentedReality.viewDistance)) + } + private var pathLayerManager: PathLayerManager? = null + private var isCameraEnabled = true - // TODO: Draw an indicator around the focused marker + private val layerManagementUpdater = CoroutineTimer { + if (!isBound) return@CoroutineTimer + val viewDistance = Distance.meters(userPrefs.augmentedReality.viewDistance) + pathLayerManager?.onBoundsChanged(CoordinateBounds.from(Geofence(binding.arView.location, viewDistance))) + pathLayerManager?.onLocationChanged(binding.arView.location, null) + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -130,6 +144,10 @@ class AugmentedRealityFragment : BoundFragment( override fun onResume() { super.onResume() + pathLayerManager = PathLayerManager(requireContext(), pathsLayer) + pathLayerManager?.start() + layerManagementUpdater.interval(1000) + binding.arView.start() if (isCameraEnabled) { startCamera() @@ -174,6 +192,8 @@ class AugmentedRealityFragment : BoundFragment( binding.camera.stop() binding.arView.stop() guide?.stop(binding.arView, binding.guidancePanel) + pathLayerManager?.stop() + layerManagementUpdater.stop() } private fun onSunFocused(time: ZonedDateTime): Boolean { @@ -236,7 +256,7 @@ class AugmentedRealityFragment : BoundFragment( this.mode = mode when (mode) { ARMode.Normal -> { - binding.arView.setLayers(listOf(gridLayer, astronomyLayer, beaconLayer)) + binding.arView.setLayers(listOf(gridLayer, astronomyLayer, pathsLayer, beaconLayer)) changeGuide(NavigationARGuide(navigator)) } diff --git a/app/src/main/java/com/kylecorry/trail_sense/tools/beacons/domain/Beacon.kt b/app/src/main/java/com/kylecorry/trail_sense/tools/beacons/domain/Beacon.kt index b751e79f7..c34b60b14 100644 --- a/app/src/main/java/com/kylecorry/trail_sense/tools/beacons/domain/Beacon.kt +++ b/app/src/main/java/com/kylecorry/trail_sense/tools/beacons/domain/Beacon.kt @@ -13,7 +13,7 @@ data class Beacon( val visible: Boolean = true, val comment: String? = null, override val parentId: Long? = null, - val elevation: Float? = null, + override val elevation: Float? = null, val temporary: Boolean = false, val owner: BeaconOwner = BeaconOwner.User, @ColorInt override val color: Int = Color.BLACK, diff --git a/app/src/main/java/com/kylecorry/trail_sense/tools/maps/infrastructure/layers/PathLayerManager.kt b/app/src/main/java/com/kylecorry/trail_sense/tools/maps/infrastructure/layers/PathLayerManager.kt index 390a3aae6..157a6a29e 100644 --- a/app/src/main/java/com/kylecorry/trail_sense/tools/maps/infrastructure/layers/PathLayerManager.kt +++ b/app/src/main/java/com/kylecorry/trail_sense/tools/maps/infrastructure/layers/PathLayerManager.kt @@ -11,12 +11,13 @@ import com.kylecorry.trail_sense.tools.paths.infrastructure.persistence.PathServ import com.kylecorry.trail_sense.tools.paths.ui.asMappable import com.kylecorry.trail_sense.tools.navigation.ui.layers.PathLayer import com.kylecorry.andromeda.core.coroutines.onDefault +import com.kylecorry.trail_sense.tools.paths.ui.IPathLayer import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel import kotlinx.coroutines.launch -class PathLayerManager(private val context: Context, private val layer: PathLayer) : +class PathLayerManager(private val context: Context, private val layer: IPathLayer) : BaseLayerManager() { private val pathService = PathService.getInstance(context) diff --git a/app/src/main/java/com/kylecorry/trail_sense/tools/navigation/ui/Mappables.kt b/app/src/main/java/com/kylecorry/trail_sense/tools/navigation/ui/Mappables.kt index 085da8931..dfef57e38 100644 --- a/app/src/main/java/com/kylecorry/trail_sense/tools/navigation/ui/Mappables.kt +++ b/app/src/main/java/com/kylecorry/trail_sense/tools/navigation/ui/Mappables.kt @@ -34,13 +34,15 @@ interface IMappableLocation : Identifiable { val coordinate: Coordinate val color: Int val icon: BeaconIcon? + val elevation: Float? } data class MappableLocation( override val id: Long, override val coordinate: Coordinate, override val color: Int, - override val icon: BeaconIcon? + override val icon: BeaconIcon?, + override val elevation: Float? = null ) : IMappableLocation interface IMappablePath : Identifiable { diff --git a/app/src/main/java/com/kylecorry/trail_sense/tools/navigation/ui/layers/PathLayer.kt b/app/src/main/java/com/kylecorry/trail_sense/tools/navigation/ui/layers/PathLayer.kt index cbf81ad31..c9b717a1f 100644 --- a/app/src/main/java/com/kylecorry/trail_sense/tools/navigation/ui/layers/PathLayer.kt +++ b/app/src/main/java/com/kylecorry/trail_sense/tools/navigation/ui/layers/PathLayer.kt @@ -19,11 +19,12 @@ import com.kylecorry.trail_sense.tools.paths.ui.drawing.RenderedPath import com.kylecorry.trail_sense.tools.navigation.ui.IMappablePath import com.kylecorry.trail_sense.shared.extensions.drawLines import com.kylecorry.trail_sense.shared.getBounds +import com.kylecorry.trail_sense.tools.paths.ui.IPathLayer import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -class PathLayer : ILayer { +class PathLayer : ILayer, IPathLayer { private var pathsRendered = false private var renderInProgress = false @@ -47,7 +48,7 @@ class PathLayer : ILayer { invalidate() } - fun setPaths(paths: List) { + override fun setPaths(paths: List) { synchronized(lock) { _paths.clear() _paths.addAll(paths) diff --git a/app/src/main/java/com/kylecorry/trail_sense/tools/paths/ui/IPathLayer.kt b/app/src/main/java/com/kylecorry/trail_sense/tools/paths/ui/IPathLayer.kt new file mode 100644 index 000000000..889d6d178 --- /dev/null +++ b/app/src/main/java/com/kylecorry/trail_sense/tools/paths/ui/IPathLayer.kt @@ -0,0 +1,7 @@ +package com.kylecorry.trail_sense.tools.paths.ui + +import com.kylecorry.trail_sense.tools.navigation.ui.IMappablePath + +interface IPathLayer { + fun setPaths(paths: List) +} \ No newline at end of file diff --git a/app/src/main/java/com/kylecorry/trail_sense/tools/paths/ui/PathExtensions.kt b/app/src/main/java/com/kylecorry/trail_sense/tools/paths/ui/PathExtensions.kt index 58a3fd8d2..f6232d472 100644 --- a/app/src/main/java/com/kylecorry/trail_sense/tools/paths/ui/PathExtensions.kt +++ b/app/src/main/java/com/kylecorry/trail_sense/tools/paths/ui/PathExtensions.kt @@ -33,7 +33,8 @@ fun List.toMappableLocations( point.id, point.coordinate, strategy.getColor(point) ?: Color.TRANSPARENT, - null + null, + point.elevation ) } } @@ -60,6 +61,7 @@ fun List.asBeacons( point.id, "", point.coordinate, + elevation = point.elevation, color = if (point.id == selected && color == Color.TRANSPARENT) { Color.WHITE } else { diff --git a/app/src/main/java/com/kylecorry/trail_sense/tools/paths/ui/PathOverviewFragment.kt b/app/src/main/java/com/kylecorry/trail_sense/tools/paths/ui/PathOverviewFragment.kt index 33099e50a..7f9ac07b5 100644 --- a/app/src/main/java/com/kylecorry/trail_sense/tools/paths/ui/PathOverviewFragment.kt +++ b/app/src/main/java/com/kylecorry/trail_sense/tools/paths/ui/PathOverviewFragment.kt @@ -492,7 +492,8 @@ class PathOverviewFragment : BoundFragment() { it.id, it.coordinate, path.style.color, - null + null, + it.elevation ) }, path.style.color,