Skip to content

Commit

Permalink
Render paths in AR
Browse files Browse the repository at this point in the history
Closes #2023
  • Loading branch information
kylecorry31 committed Jan 31, 2024
1 parent 6c83e86 commit 5446bd6
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -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<IMappablePath>) {

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<IMappablePath> {
val clipped = mutableListOf<IMappablePath>()
val currentPoints = mutableListOf<IMappableLocation>()

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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -85,9 +89,19 @@ class AugmentedRealityFragment : BoundFragment<FragmentAugmentedRealityBinding>(
)
}

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)
Expand Down Expand Up @@ -130,6 +144,10 @@ class AugmentedRealityFragment : BoundFragment<FragmentAugmentedRealityBinding>(
override fun onResume() {
super.onResume()

pathLayerManager = PathLayerManager(requireContext(), pathsLayer)
pathLayerManager?.start()
layerManagementUpdater.interval(1000)

binding.arView.start()
if (isCameraEnabled) {
startCamera()
Expand Down Expand Up @@ -174,6 +192,8 @@ class AugmentedRealityFragment : BoundFragment<FragmentAugmentedRealityBinding>(
binding.camera.stop()
binding.arView.stop()
guide?.stop(binding.arView, binding.guidancePanel)
pathLayerManager?.stop()
layerManagementUpdater.stop()
}

private fun onSunFocused(time: ZonedDateTime): Boolean {
Expand Down Expand Up @@ -236,7 +256,7 @@ class AugmentedRealityFragment : BoundFragment<FragmentAugmentedRealityBinding>(
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))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -47,7 +48,7 @@ class PathLayer : ILayer {
invalidate()
}

fun setPaths(paths: List<IMappablePath>) {
override fun setPaths(paths: List<IMappablePath>) {
synchronized(lock) {
_paths.clear()
_paths.addAll(paths)
Expand Down
Original file line number Diff line number Diff line change
@@ -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<IMappablePath>)
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ fun List<PathPoint>.toMappableLocations(
point.id,
point.coordinate,
strategy.getColor(point) ?: Color.TRANSPARENT,
null
null,
point.elevation
)
}
}
Expand All @@ -60,6 +61,7 @@ fun List<PathPoint>.asBeacons(
point.id,
"",
point.coordinate,
elevation = point.elevation,
color = if (point.id == selected && color == Color.TRANSPARENT) {
Color.WHITE
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,8 @@ class PathOverviewFragment : BoundFragment<FragmentPathOverviewBinding>() {
it.id,
it.coordinate,
path.style.color,
null
null,
it.elevation
)
},
path.style.color,
Expand Down

0 comments on commit 5446bd6

Please sign in to comment.