// Check eccentricity and inclination
    public void RVtoCOEtoRV()
    {
        GameObject star = TestSetupUtils.CreateNBody(10, new Vector3(0, 0, 0));

        TestSetupUtils.SetupGravityEngine(star, null);
        NBody starBody = star.GetComponent <NBody>();

        RVpair[] rvp =
        {
            new RVpair(10,   0, 0,  0,   1, 0),
            new RVpair(10,   0, 0,  0,  10, 0),
            new RVpair(10,   0, 0,  0,  -1, 0),
            new RVpair(10,   0, 0,  0, -10, 0),
            new RVpair(-10, 10, 0, -4,   3, 0),
            new RVpair(-10, 10, 0,  4,  -3, 0)
        };

        for (int i = 0; i < rvp.Length; i++)
        {
            OrbitUtils.OrbitElements oe = OrbitUtils.RVtoCOE(rvp[i].r, rvp[i].v, starBody, false);
            Vector3d r1 = new Vector3d();
            Vector3d v1 = new Vector3d();
            OrbitUtils.COEtoRV(oe, starBody, ref r1, ref v1, false);

            Debug.LogFormat("i={0} r_in={1} r_out={2}\n v_in={3} v_out={4}\n oe: {5}",
                            i, rvp[i].r, r1, rvp[i].v, v1, oe);
            Assert.IsTrue(GEUnit.Vec3dEqual(rvp[i].r, r1, small));
            Assert.IsTrue(GEUnit.Vec3dEqual(rvp[i].v, v1, small));
        }
    }
    private void RVtoCOEWrapper(NBody aroundNBody, Vector3 r, Vector3 v)
    {
        OrbitUtils.OrbitElements oe = OrbitUtils.RVtoCOE(r, v, aroundNBody, false);
        ecc = (float)oe.ecc;
        a   = (float)oe.a;
        // awkward, but hyperbola expects -a (TODO clean this all out!)
        if (ecc > 1.0f)
        {
            a = -a;
        }
        perihelion  = a * (ecc - 1);
        inclination = Mathf.Rad2Deg * (float)oe.incl;
        omega_uc    = 0;
        omega_lc    = 0;
        if (oe.IsInclined())
        {
            if (oe.IsCircular())
            {
                omega_uc = Mathf.Rad2Deg * (float)oe.raan;
            }
            else
            {
                omega_uc = Mathf.Rad2Deg * (float)oe.raan;
                omega_lc = Mathf.Rad2Deg * (float)oe.argp;
            }
        }
        else
        {
            if (!oe.IsCircular())
            {
                omega_lc = Mathf.Rad2Deg * (float)oe.lonper;
            }
            // if not inclined and circular, no omega_uc or omega_lc, just phase.
        }
        phase   = (float)OrbitUtils.GetPhaseFromOE(oe) * Mathf.Rad2Deg;
        ecc_vec = oe.ecc_vec;

        if (ecc < 1.0f)
        {
            CalcPeriod();
            CalcTau(r, v);
        }
    }
    /// <summary>
    /// Computes the transfer and updates all the ghost bodies.
    /// </summary>
    /// <returns></returns>
    private void ComputeTransfer()
    {
        double timeNow = ge.GetPhysicalTimeDouble();

        // First using the transfer time, move the ghost Moon to position at SOI arrival.
        // Call evolve via LockAtTime on the ghostMoon to move it. Set position based on this.
        double t_flight  = tflightFactor * timeHohmann;
        double timeatSoi = timeNow + t_flight;

        ghostMoonOrbit[MOON_SOI_ENTER].LockAtTime(timeatSoi);
        // Determine the moon phase angle
        double moonPhase = ghostMoonSoiEnterOrbitPredictor.GetOrbitUniversal().phase;

        // Place the TLI ship at the user-requested angle wrt planet-moon line
        // Put ghost ship in same orbit geometry as the moon, assuming it is circular. Then
        // can use same phase value.
        // (Ship needs to reach this departure point, it may not even be on the ship orbit
        //  in general).
        ghostShipOrbit[TLI].phase       = shipTLIAngleDeg + (moonPhase + 180f);
        ghostShipOrbit[TLI].inclination = ghostMoonOrbit[MOON_SOI_ENTER].inclination;
        ghostShipOrbit[TLI].omega_lc    = ghostMoonOrbit[MOON_SOI_ENTER].omega_lc;
        ghostShipOrbit[TLI].omega_uc    = ghostMoonOrbit[MOON_SOI_ENTER].omega_uc;
        ghostShipOrbit[TLI].p_inspector = shipOrbit.p;
        ghostShipOrbit[TLI].Init();
        ghostShipOrbit[TLI].LockAtTime(0);

        // Place the SOI enter ship at the user-requested angle in an SOI orbit. Lock at time 0 so the phase
        // is held per the user input.
        ghostShipOrbit[ENTER_SOI].phase       = soiAngleDeg + moonPhase;
        ghostShipOrbit[ENTER_SOI].inclination = soiInclination + shipOrbit.inclination;
        ghostShipOrbit[ENTER_SOI].omega_lc    = ghostMoonOrbit[MOON_SOI_ENTER].omega_lc;
        ghostShipOrbit[ENTER_SOI].omega_uc    = ghostMoonOrbit[MOON_SOI_ENTER].omega_uc;
        ghostShipOrbit[ENTER_SOI].Init();
        ghostShipOrbit[ENTER_SOI].LockAtTime(0);

        // Find the line to the ENTER_SOI position. Ship departs from that line continued through planet
        // at the shipRadius distance (assumes circular ship orbit)
        // TODO: Handle planet not at (0,0,0)
        Vector3d soiEntryPos    = ge.GetPositionDoubleV3(ghostShip[ENTER_SOI]);
        Vector3d planetPos      = ge.GetPositionDoubleV3(planet);
        Vector3d departurePoint = ge.GetPositionDoubleV3(ghostShip[TLI]);

        // Use Lambert to find the departure velocity to get from departure to soiEntry
        // Since we need 180 degrees from departure to arrival, use LambertBattin
        lambertB = new LambertBattin(ghostShip[TO_MOON], planet, departurePoint, soiEntryPos, shipOrbit.GetAxis());

        // apply any time of flight change
        bool reverse = !shortPath;

        const bool df    = false;
        const int  nrev  = 0;
        int        error = lambertB.ComputeXfer(reverse, df, nrev, t_flight);

        if (error != 0)
        {
            Debug.LogWarning("Lambert failed to find solution. error=" + error);
            return;
        }
        // Check Lambert is going in the correct direction
        //Vector3 shipOrbitAxis = Vector3.Cross(ge.GetPhysicsPosition(spaceship), ge.GetVelocity(spaceship) ).normalized;
        //Vector3 tliOrbitAxis = Vector3.Cross(departurePoint.ToVector3(), lambertB.GetTransferVelocity().ToVector3()).normalized;
        Vector3 shipOrbitAxis = Vector3.Cross(ge.GetVelocity(spaceship), ge.GetPhysicsPosition(spaceship)).normalized;
        Vector3 tliOrbitAxis  = Vector3.Cross(lambertB.GetTransferVelocity().ToVector3(), departurePoint.ToVector3()).normalized;

        if (Vector3.Dot(shipOrbitAxis, tliOrbitAxis) < 0)
        {
            error = lambertB.ComputeXfer(!reverse, df, nrev, t_flight);
            if (error != 0)
            {
                Debug.LogWarning("Lambert failed to find solution for reverse path. error=" + error);
                return;
            }
        }
        Debug.LogFormat("tli_vel={0}", lambertB.GetTransferVelocity());

        ghostShipOrbit[TO_MOON].InitFromRVT(departurePoint, lambertB.GetTransferVelocity(), timeNow, planet, false);

        // Set velocity for orbit around moon. Will be updated every frame
        ghostShipOrbit[SOI_HYPER].InitFromRVT(soiEntryPos, lambertB.GetFinalVelocity(), timeNow, ghostMoon[MOON_SOI_ENTER], false);

        // Find the exit point of the hyperbola in the SOI
        OrbitUtils.OrbitElements oe = OrbitUtils.RVtoCOE(soiEntryPos, lambertB.GetFinalVelocity(), ghostMoon[MOON_SOI_ENTER], false);
        Vector3d soiExitR           = new Vector3d();
        Vector3d soiExitV           = new Vector3d();

        OrbitUtils.COEtoRVMirror(oe, ghostMoon[MOON_SOI_ENTER], ref soiExitR, ref soiExitV, false);

        // Find time to go around the moon. TOF requires relative positions!!
        Vector3d ghostSoiEnterPos   = ge.GetPositionDoubleV3(ghostMoon[MOON_SOI_ENTER]);
        Vector3d soiEnterRelative   = soiEntryPos - ghostSoiEnterPos;
        Vector3d soiExitRelative    = soiExitR - ghostSoiEnterPos;
        Vector3d soiExitVelRelative = soiExitV - ge.GetVelocityDoubleV3(ghostMoon[MOON_SOI_ENTER]);
        double   hyperTOF           = ghostShipOrbit[SOI_HYPER].TimeOfFlight(soiEnterRelative, soiExitRelative);

        // Position the ghost moon for SOI exit (timeAtSoi includes timeNow)
        t_soiExit = timeatSoi + hyperTOF;
        ghostMoonOrbit[MOON_SOI_EXIT].LockAtTime(t_soiExit);

        // Set position and vel for exit ship, so exit orbit predictor can run.
        Vector3d ghostMoonSoiExitPos = ge.GetPositionDoubleV3(ghostMoon[MOON_SOI_EXIT]);
        Vector3d ghostMoonSoiExitVel = ge.GetVelocityDoubleV3(ghostMoon[MOON_SOI_EXIT]);

        ghostShipOrbit[EXIT_SOI].InitFromRVT(soiExitRelative + ghostMoonSoiExitPos,
                                             soiExitVelRelative + ghostMoonSoiExitVel,
                                             timeNow, planet, false);
    }
    /// <summary>
    /// Set up a KeplerSeqeunce to do the three phases of the transfer as Kepler mode conics.
    ///
    /// Leave the existing ship orbit as the first
    /// </summary>
    /// <param name="transferTime"></param>
    private void TransferOnRails(double transferTime, Vector3 shipPos, Vector3 shipVel, float moonOmega)
    {
        // the ship needs to have a KeplerSequence
        KeplerSequence kseq = spaceship.GetComponent <KeplerSequence>();

        if (kseq == null)
        {
            Debug.LogError("Could not find a KeplerSequence on " + spaceship.name);
            return;
        }
        float      moonPhaseDeg = moonOmega * (float)transferTime * Mathf.Rad2Deg;
        Quaternion moonPhaseRot = Quaternion.AngleAxis(moonPhaseDeg, Vector3.forward);

        // Ellipse 1: shipPos/shipvel already phased by the caller.
        double t = ge.GetPhysicalTime();

        KeplerSequence.ElementStarted noCallback = null;
        kseq.AppendElementRVT(new Vector3d(shipPos), new Vector3d(shipVel), t, false, spaceship, planet, noCallback);

        // Hyperbola: start at t + transferTime
        // have targetPoint and final velocity from LambertTransfer. Need to make these wrt moon at this time
        // targetPoint is w.r.t current moon position, but need to rotate around SOI by amount moon will shift
        // as ship transits to moon
        Vector3 targetPos    = targetPoint.ToVector3();
        Vector3 moonPosAtSoi = moonPhaseRot * ge.GetPhysicsPosition(moonBody);
        Vector3 moonVelAtSoi = moonPhaseRot * ge.GetVelocity(moonBody);
        // get the relative positions (i.e. as if moon at the origin with v=0)
        Vector3 adjustedTarget = moonPhaseRot * targetPos - moonPosAtSoi;
        Vector3 adjustedVel    = moonPhaseRot * lambertU.GetFinalVelocity() - moonVelAtSoi;

        // Create moon hyperbola at the moon position after flight to moon. This means the init cannot make reference
        // to the CURRENT moon position.
        Vector3d       soiEnterR  = new Vector3d(adjustedTarget);
        Vector3d       soiEnterV  = new Vector3d(adjustedVel);
        OrbitUniversal hyperOrbit = kseq.AppendElementRVT(soiEnterR,
                                                          soiEnterV,
                                                          t + transferTime,
                                                          true,
                                                          spaceship,
                                                          moonBody,
                                                          EnterMoonSoi);

        // Find the hyperbola exit SOI position/vel
        OrbitUtils.OrbitElements oe = OrbitUtils.RVtoCOE(soiEnterR, soiEnterV, moonBody, true);
        Vector3d soiExitR           = new Vector3d();
        Vector3d soiExitV           = new Vector3d();

        Debug.Log("oe=" + oe);
        // Gives position and velocity in relative position
        OrbitUtils.COEtoRVMirror(oe, moonBody, ref soiExitR, ref soiExitV, true);

        // Determine hyperbola transit time to the soiExit position
        double hyperTOF = hyperOrbit.TimeOfFlight(soiEnterR, soiExitR);

        //Debug.LogFormat("Hyper TOF={0} r0={1} r1={2} p={3}", hyperTOF, adjustedTarget, soiExitR,
        //    hyperOrbit.p);

        // Ellipse 2:
        // Adjust phase to allow for moon travel during hyperbola transit
        // Need to set position and vel relative to the planet using position relative to moon at 0
        moonPhaseDeg = moonOmega * (float)hyperTOF * Mathf.Rad2Deg;
        Quaternion moonHyperRot     = Quaternion.AngleAxis(moonPhaseDeg, Vector3.forward);
        Vector3    moonAtExit       = moonHyperRot * moonPosAtSoi;
        Vector3    moonVelAtExit    = moonHyperRot * moonVelAtSoi;
        Vector3    soiExitwrtPlanet = soiExitR.ToVector3() + moonAtExit;
        // soiexitV is relative to moon at (0,0,0) BUT frame of hyperbola does not rotate
        Vector3 soiExitVelwrtPlanet = moonHyperRot * soiExitV.ToVector3() + moonVelAtExit;

        Debug.LogFormat("Ellipse2: soiExitV={0} moonV={1} net={2}", soiExitV, moonVelAtExit, soiExitVelwrtPlanet);

        kseq.AppendElementRVT(new Vector3d(soiExitwrtPlanet),
                              new Vector3d(soiExitVelwrtPlanet),
                              t + transferTime + hyperTOF,
                              true,
                              spaceship,
                              planet,
                              ExitMoonSoi);
        running = true;
    }
    /// <summary>
    /// Computes the transfer with the moon on the +X axis without accounting for the moon motion during
    /// transit. (That is accounted for in the ExecuteTransfer routine).
    ///
    /// This allows a co-rotating visualization of the orbit.
    /// </summary>
    /// <returns></returns>
    private Vector3 ComputeTransfer()
    {
        OrbitData shipOrbit = new OrbitData();

        shipOrbit.SetOrbitForVelocity(spaceship, planet);

        // compute the min energy path (this will be in the short path direction)
        lambertU = new LambertUniversal(shipOrbit, startPoint, targetPoint, shortPath);

        // apply any time of flight change
        double t_flight = tflightFactor * lambertU.GetTMin();
        bool   reverse  = !shortPath;

        const bool df    = false;
        const int  nrev  = 0;
        int        error = lambertU.ComputeXfer(reverse, df, nrev, t_flight);

        if (error != 0)
        {
            Debug.LogWarning("Lambert failed to find solution.");
            aroundMoonSegment.gameObject.SetActive(false);
            return(Vector3.zero);
        }
        // Check Lambert is going in the correct direction
        Vector3 shipOrbitAxis = Vector3.Cross(ge.GetVelocity(spaceship), ge.GetPhysicsPosition(spaceship)).normalized;
        Vector3 tliOrbitAxis  = Vector3.Cross(lambertU.GetTransferVelocity(), startPoint.ToVector3());

        if (Vector3.Dot(shipOrbitAxis, tliOrbitAxis) < 0)
        {
            error = lambertU.ComputeXfer(!reverse, df, nrev, t_flight);
            if (error != 0)
            {
                Debug.LogWarning("Lambert failed to find solution for reverse path. error=" + error);
                return(Vector3.zero);
            }
        }

        Vector3 tliVelocity = lambertU.GetTransferVelocity();

        toMoonOrbit.SetVelocity(tliVelocity);
        toMoonSegment.SetVelocity(tliVelocity);
        aroundMoonSegment.gameObject.SetActive(true);

        // Set velocity for orbit around moon
        Vector3 soiEnterVel = lambertU.GetFinalVelocity();

        aroundMoonSegment.SetVelocity(soiEnterVel);

        // update shipEnterSOI object
        ge.UpdatePositionAndVelocity(shipEnterSOI, targetPoint.ToVector3(), soiEnterVel);

        // Find the orbit around the moon. By using the mirror position we're assuming it's
        // a hyperbola (since there is no course correction at SOI this is true).
        // (Moon is in correct position for these calcs so can use world positions, relativePos=false)
        Vector3d soiEnterV = new Vector3d(lambertU.GetFinalVelocity());

        OrbitUtils.OrbitElements oe = OrbitUtils.RVtoCOE(targetPoint, soiEnterV, moonBody, false);
        Vector3d soiExitR           = new Vector3d();
        Vector3d soiExitV           = new Vector3d();

        OrbitUtils.COEtoRVMirror(oe, moonBody, ref soiExitR, ref soiExitV, false);
        // Set position and vel for exit ship, so exit orbit predictor can run. Moon offset/vel already added.
        ge.SetPositionDoubleV3(shipExitSOI, soiExitR);
        ge.SetVelocityDoubleV3(shipExitSOI, soiExitV);
        aroundMoonSegment.UpdateOrbit();
        return(tliVelocity);
    }