Skip to content

Commit

Permalink
Make AR mode configurable
Browse files Browse the repository at this point in the history
  • Loading branch information
kylecorry31 committed Dec 31, 2023
1 parent dbbc736 commit f6d352f
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.kylecorry.trail_sense.tools.augmented_reality

import com.kylecorry.trail_sense.shared.database.Identifiable

enum class ARMode(override val id: Long) : Identifiable {
Normal(1),
Astronomy(2),
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.camera.view.PreviewView
import androidx.core.os.bundleOf
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.ui.Colors.withAlpha
import com.kylecorry.andromeda.fragments.BoundFragment
import com.kylecorry.andromeda.fragments.observeFlow
import com.kylecorry.sol.science.astronomy.Astronomy
import com.kylecorry.sol.science.astronomy.moon.MoonPhase
import com.kylecorry.sol.units.Distance
import com.kylecorry.trail_sense.R
Expand All @@ -25,23 +26,29 @@ import com.kylecorry.trail_sense.shared.DistanceUtils.toRelativeDistance
import com.kylecorry.trail_sense.shared.FormatService
import com.kylecorry.trail_sense.shared.Units
import com.kylecorry.trail_sense.shared.UserPreferences
import com.kylecorry.trail_sense.shared.colors.AppColor
import com.kylecorry.trail_sense.shared.permissions.alertNoCameraPermission
import com.kylecorry.trail_sense.shared.permissions.requestCamera
import com.kylecorry.trail_sense.tools.augmented_reality.position.GeographicARPoint
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 java.time.ZonedDateTime
import kotlin.math.hypot

// TODO: Support arguments for default layer visibility (ex. coming from astronomy, enable only sun/moon)
class AugmentedRealityFragment : BoundFragment<FragmentAugmentedRealityBinding>() {

private var mode = ARMode.Normal

private val userPrefs by lazy { UserPreferences(requireContext()) }
private val beaconRepo by lazy {
BeaconRepo.getInstance(requireContext())
}

private val formatter by lazy { FormatService.getInstance(requireContext()) }

private var guide: ARGuide? = null

private val beaconLayer by lazy {
ARBeaconLayer(
Distance.meters(userPrefs.navigation.maxBeaconDistance),
Expand Down Expand Up @@ -85,18 +92,11 @@ class AugmentedRealityFragment : BoundFragment<FragmentAugmentedRealityBinding>(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

// Beacon layer setup (TODO: Move this to a layer manager)
observeFlow(beaconRepo.getBeacons()) {
beaconLayer.setBeacons(it)
}

observeFlow(navigator.destination) {
if (it == null) {
binding.arView.clearGuide()
} else {
binding.arView.guideTo(GeographicARPoint(it.coordinate, it.elevation)) {
// Do nothing when reached
}
}
beaconLayer.destination = it
}

Expand All @@ -105,7 +105,10 @@ class AugmentedRealityFragment : BoundFragment<FragmentAugmentedRealityBinding>(

binding.arView.bind(binding.camera)

binding.arView.setLayers(listOf(gridLayer, astronomyLayer, beaconLayer))
val modeId = requireArguments().getLong("mode", ARMode.Normal.id)
val desiredMode = ARMode.entries.withId(modeId) ?: ARMode.Normal

setMode(desiredMode)

binding.cameraToggle.setOnClickListener {
if (isCameraEnabled) {
Expand All @@ -125,6 +128,8 @@ class AugmentedRealityFragment : BoundFragment<FragmentAugmentedRealityBinding>(
} else {
stopCamera()
}

guide?.start(binding.arView)
}

// TODO: Move this to the AR view
Expand Down Expand Up @@ -160,6 +165,7 @@ class AugmentedRealityFragment : BoundFragment<FragmentAugmentedRealityBinding>(
super.onPause()
binding.camera.stop()
binding.arView.stop()
guide?.stop(binding.arView)
}

private fun onSunFocused(time: ZonedDateTime): Boolean {
Expand All @@ -176,9 +182,11 @@ class AugmentedRealityFragment : BoundFragment<FragmentAugmentedRealityBinding>(
getString(R.string.moon) + "\n" + formatter.formatRelativeDateTime(
time,
includeSeconds = false
) + "\n${formatter.formatMoonPhase(phase.phase)} (${formatter.formatPercentage(
phase.illumination
)})"
) + "\n${formatter.formatMoonPhase(phase.phase)} (${
formatter.formatPercentage(
phase.illumination
)
})"
return true
}

Expand All @@ -203,4 +211,34 @@ class AugmentedRealityFragment : BoundFragment<FragmentAugmentedRealityBinding>(
): FragmentAugmentedRealityBinding {
return FragmentAugmentedRealityBinding.inflate(layoutInflater, container, false)
}

private fun setMode(mode: ARMode) {
this.mode = mode
when (mode) {
ARMode.Normal -> {
binding.arView.setLayers(listOf(gridLayer, astronomyLayer, beaconLayer))
changeGuide(NavigationARGuide(navigator))
}

ARMode.Astronomy -> {
binding.arView.setLayers(listOf(gridLayer, astronomyLayer))
changeGuide(AstronomyARGuide())
}
}
}

private fun changeGuide(guide: ARGuide?) {
this.guide?.stop(binding.arView)
this.guide = guide
this.guide?.start(binding.arView)
}

companion object {
fun open(navController: NavController, mode: ARMode = ARMode.Normal) {
navController.navigate(R.id.augmentedRealityFragment, bundleOf(
"mode" to mode.id
))
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.findViewTreeLifecycleOwner
import com.kylecorry.andromeda.camera.ar.CalibratedCameraAnglePixelMapper
import com.kylecorry.andromeda.camera.ar.CameraAnglePixelMapper
import com.kylecorry.andromeda.camera.ar.LinearCameraAnglePixelMapper
import com.kylecorry.andromeda.canvas.CanvasView
import com.kylecorry.andromeda.canvas.TextAlign
import com.kylecorry.andromeda.canvas.TextMode
Expand Down Expand Up @@ -114,6 +113,8 @@ class AugmentedRealityView : CanvasView {
var showReticle: Boolean = true
var showPosition: Boolean = true

private var reticleColor = Color.WHITE.withAlpha(127)

/**
* The diameter of the reticle in pixels
*/
Expand All @@ -124,7 +125,7 @@ class AugmentedRealityView : CanvasView {
private val layerLock = Any()

// Guidance
private var guideStrategy: ARPoint? = null
private var guidePoint: ARPoint? = null
private var guideThreshold: Float? = null
private var onGuideReached: (() -> Unit)? = null

Expand Down Expand Up @@ -190,17 +191,17 @@ class AugmentedRealityView : CanvasView {
}

fun guideTo(
guideStrategy: ARPoint,
guidePoint: ARPoint,
thresholdDegrees: Float? = null,
onReached: () -> Unit = { clearGuide() }
) {
this.guideStrategy = guideStrategy
this.guidePoint = guidePoint
guideThreshold = thresholdDegrees
onGuideReached = onReached
}

fun clearGuide() {
guideStrategy = null
guidePoint = null
guideThreshold = null
onGuideReached = null
}
Expand Down Expand Up @@ -236,8 +237,8 @@ class AugmentedRealityView : CanvasView {
}

if (showReticle) {
drawReticle()
drawGuidance()
drawReticle()
drawFocusText()
}

Expand All @@ -259,7 +260,8 @@ class AugmentedRealityView : CanvasView {

private fun drawGuidance() {
// Draw an arrow around the reticle that points to the desired location
val coordinate = guideStrategy?.getHorizonCoordinate(this) ?: return
reticleColor = Color.WHITE.withAlpha(127)
val coordinate = guidePoint?.getHorizonCoordinate(this) ?: return
val threshold = guideThreshold
val point = toPixel(coordinate)
val center = PixelCoordinate(width / 2f, height / 2f)
Expand All @@ -270,6 +272,7 @@ class AugmentedRealityView : CanvasView {
)

if (circle.contains(center)) {
reticleColor = Color.WHITE
onGuideReached?.invoke()
}

Expand Down Expand Up @@ -354,7 +357,7 @@ class AugmentedRealityView : CanvasView {
}

private fun drawReticle() {
stroke(Color.WHITE.withAlpha(127))
stroke(reticleColor)
strokeWeight(dp(2f))
noFill()
circle(width / 2f, height / 2f, reticleDiameter)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.kylecorry.trail_sense.tools.augmented_reality.guide

import com.kylecorry.trail_sense.tools.augmented_reality.AugmentedRealityView

interface ARGuide {
fun start(arView: AugmentedRealityView)
fun stop(arView: AugmentedRealityView)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.kylecorry.trail_sense.tools.augmented_reality.guide

import com.kylecorry.andromeda.core.time.CoroutineTimer
import com.kylecorry.trail_sense.astronomy.domain.AstronomyService
import com.kylecorry.trail_sense.tools.augmented_reality.AugmentedRealityView
import com.kylecorry.trail_sense.tools.augmented_reality.position.SphericalARPoint
import java.time.ZonedDateTime

class AstronomyARGuide : ARGuide {

private var objectToTrack = AstronomyObject.Sun
private val astro = AstronomyService()
private var arView: AugmentedRealityView? = null
private val timer = CoroutineTimer {
val arView = arView ?: return@CoroutineTimer
// TODO: Make time configurable
val time = ZonedDateTime.now()

val destination = when (objectToTrack) {
AstronomyObject.Sun -> {
val azimuth = astro.getSunAzimuth(arView.location, time).value
val altitude = astro.getSunAltitude(arView.location, time)
SphericalARPoint(azimuth, altitude, angularDiameter = 2f)
}
AstronomyObject.Moon -> {
val azimuth = astro.getMoonAzimuth(arView.location, time).value
val altitude = astro.getMoonAltitude(arView.location, time)
SphericalARPoint(azimuth, altitude, angularDiameter = 2f)
}
}

arView.guideTo(destination) {
// Do nothing when reached
}

}

override fun start(arView: AugmentedRealityView) {
this.arView = arView
timer.interval(1000)
}

override fun stop(arView: AugmentedRealityView) {
this.arView = null
timer.stop()
arView.clearGuide()
}


private enum class AstronomyObject {
Sun,
Moon
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.kylecorry.trail_sense.tools.augmented_reality.guide

import com.kylecorry.trail_sense.navigation.infrastructure.Navigator
import com.kylecorry.trail_sense.tools.augmented_reality.AugmentedRealityView
import com.kylecorry.trail_sense.tools.augmented_reality.position.GeographicARPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch

class NavigationARGuide(private val navigator: Navigator) : ARGuide {

private val scope = CoroutineScope(Dispatchers.Default)
private var job: Job? = null

override fun start(arView: AugmentedRealityView) {
job?.cancel()
job = scope.launch {
navigator.destination.collect {
if (it == null) {
arView.clearGuide()
} else {
arView.guideTo(GeographicARPoint(it.coordinate, it.elevation)) {
// Do nothing when reached
}
}
}
}
}

override fun stop(arView: AugmentedRealityView) {
job?.cancel()
arView.clearGuide()
}
}
9 changes: 7 additions & 2 deletions app/src/main/res/navigation/nav_graph.xml
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@
<fragment
android:id="@+id/experimentationFragment"
android:name="com.kylecorry.trail_sense.experimentation.ExperimentationFragment"
android:label="ExperimentationFragment"/>
android:label="ExperimentationFragment" />
<fragment
android:id="@+id/beacon_list"
android:name="com.kylecorry.trail_sense.navigation.beacons.ui.list.BeaconListFragment"
Expand Down Expand Up @@ -952,7 +952,12 @@
<fragment
android:id="@+id/augmentedRealityFragment"
android:name="com.kylecorry.trail_sense.tools.augmented_reality.AugmentedRealityFragment"
android:label="AugmentedRealityFragment" />
android:label="AugmentedRealityFragment">
<argument
android:name="mode"
android:defaultValue="1L"
app:argType="long" />
</fragment>
<fragment
android:id="@+id/fragmentStrideLengthEstimation"
android:name="com.kylecorry.trail_sense.tools.pedometer.ui.FragmentStrideLengthEstimation"
Expand Down

0 comments on commit f6d352f

Please sign in to comment.