1 /* <lambda>null2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.egg.landroid 18 19 import kotlin.math.min 20 import kotlin.math.sign 21 22 class Autopilot(val ship: Spacecraft, val universe: Universe) : Entity { 23 val BRAKING_TIME = 5f 24 val SIGHTSEEING_TIME = 15f 25 val LAUNCH_THRUST_TIME = 5f 26 val STRATEGY_MIN_TIME = 0.5f 27 28 var enabled = false 29 30 var target: Planet? = null 31 32 var landingAltitude = 0f 33 34 var nextStrategyTime = 0f 35 36 var brakingDistance = 0f 37 38 // used by rendering 39 var leadingPos = Vec2.Zero 40 var leadingVector = Vec2.Zero 41 42 val telemetry: String 43 get() = 44 listOf( 45 "---- AUTOPILOT ENGAGED ----", 46 "TGT: " + (target?.name?.toUpperCase() ?: "SELECTING..."), 47 "EXE: $strategy" + if (debug.isNotEmpty()) " ($debug)" else "", 48 ) 49 .joinToString("\n") 50 51 private var strategy: String = "NONE" 52 private var debug: String = "" 53 54 override fun update(sim: Simulator, dt: Float) { 55 if (!enabled) return 56 57 if (sim.now < nextStrategyTime) { 58 return 59 } 60 61 val currentStrategy = strategy 62 63 if (ship.landing != null) { 64 if (target != null) { 65 strategy = "LANDED" 66 debug = "" 67 // we just got here. see the sights. 68 target = null 69 landingAltitude = 0f 70 nextStrategyTime = sim.now + SIGHTSEEING_TIME 71 } else { 72 // full power until we blast off 73 ship.thrust = Vec2.makeWithAngleMag(ship.angle, 1f) 74 75 strategy = "LAUNCHING" 76 debug = "" 77 nextStrategyTime = sim.now + LAUNCH_THRUST_TIME 78 } 79 } else { 80 // select new target 81 82 if (target == null) { 83 // testing: target the first planet 84 // target = universe.planets[0] 85 86 // target the nearest unexplored planet 87 target = 88 universe.planets 89 .sortedBy { (it.pos - ship.pos).mag() } 90 .firstOrNull { !it.explored } 91 brakingDistance = 0f 92 93 // if we've explored them all, pick one at random 94 if (target == null) target = universe.planets.random() 95 } 96 97 target?.let { target -> // should be nonnull 98 val shipV = ship.velocity 99 val targetV = target.velocity 100 val targetVector = (target.pos - ship.pos) 101 val altitude = targetVector.mag() - target.radius 102 103 landingAltitude = min(target.radius, 100f) 104 105 // the following is in the moving reference frame of the target 106 val relativeV: Vec2 = shipV - targetV 107 val projection = relativeV.dot(targetVector / targetVector.mag()) 108 val relativeSpeed = relativeV.mag() * projection.sign 109 val timeToTarget = if (relativeSpeed != 0f) altitude / relativeSpeed else 1_000f 110 111 val newBrakingDistance = 112 BRAKING_TIME * if (relativeSpeed > 0) relativeSpeed else MAIN_ENGINE_ACCEL 113 brakingDistance = 114 expSmooth(brakingDistance, newBrakingDistance, dt = sim.dt, speed = 5f) 115 116 // We're going to aim at where the target will be, but we want to make sure to 117 // compute 118 leadingPos = 119 target.pos + 120 Vec2.makeWithAngleMag( 121 target.velocity.angle(), 122 min(altitude / 2, target.velocity.mag()) 123 ) 124 leadingVector = leadingPos - ship.pos 125 126 if (altitude < landingAltitude) { 127 strategy = "LANDING" 128 // Strategy: zero thrust, face away, prepare for landing 129 130 ship.angle = (ship.pos - target.pos).angle() // point away from ground 131 ship.thrust = Vec2.Zero 132 } else { 133 if (relativeSpeed < 0 || altitude > brakingDistance) { 134 strategy = "CHASING" 135 // Strategy: Make tracks. We are either a long way away, or falling behind. 136 ship.angle = leadingVector.angle() 137 138 ship.thrust = Vec2.makeWithAngleMag(ship.angle, 1.0f) 139 } else { 140 strategy = "APPROACHING" 141 // Strategy: Just slow down. If we get caught in the gravity well, it will 142 // gradually start pulling us more in the direction of the planet, which 143 // will create a graceful deceleration 144 ship.angle = (-ship.velocity).angle() 145 146 // We want to bleed off velocity over time. Specifically, relativeSpeed px/s 147 // over timeToTarget seconds. 148 val decel = relativeSpeed / timeToTarget 149 val decelThrust = 150 decel / MAIN_ENGINE_ACCEL * 0.9f // not quite slowing down enough 151 ship.thrust = Vec2.makeWithAngleMag(ship.angle, decelThrust) 152 } 153 } 154 debug = ("DV=%.0f D=%.0f T%+.1f").format(relativeSpeed, altitude, timeToTarget) 155 } 156 if (strategy != currentStrategy) { 157 nextStrategyTime = sim.now + STRATEGY_MIN_TIME 158 } 159 } 160 } 161 162 override fun postUpdate(sim: Simulator, dt: Float) { 163 if (!enabled) return 164 } 165 } 166