public static void SetupGravityEngine(GameObject centerBody, GameObject orbitingBody)
    {
        GravityEngine ge = GravityEngine.Instance();

        if (ge == null)
        {
            Debug.LogError("No GE in scene");
        }
        if (ge.evolveAtStart)
        {
            Debug.LogError("Evolve at start set. Are you in the TestRunner scene?");
        }
        else if (ge.detectNbodies)
        {
            Debug.LogError("Detect NBodies at start set. Are you in the TestRunner scene?");
        }
        ge.UnitTestAwake();
        ge.Clear();
        ge.AddBody(centerBody);
        if (orbitingBody != null)
        {
            ge.AddBody(orbitingBody);
        }
        ge.Setup();
        ge.LogDump();
    }
    private void TestRV(OrbitData od, GameObject planet, NBody starNbody, float orbitRadius)
    {
        GameObject     testPlanet = TestSetupUtils.CreatePlanetInOrbitUniversal(starNbody, 1f, orbitRadius);
        OrbitUniversal orbitU     = testPlanet.GetComponent <OrbitUniversal>();

        // Run init explicitly to update transform details
        orbitU.InitFromOrbitData(od, 0);

        // Awkward but previously could not add a new object to GE when it is stopped, so re-add all three
        // Leave as is, since it works!
        GravityEngine ge = GravityEngine.Instance();

        ge.Clear();
        ge.AddBody(starNbody.gameObject);
        ge.AddBody(planet);
        ge.AddBody(testPlanet);
        ge.Setup();
        ge.LogDump();
        Vector3 r_od = ge.GetPhysicsPosition(testPlanet.GetComponent <NBody>());
        Vector3 v_od = ge.GetVelocity(testPlanet);
        Vector3 r_i  = ge.GetPhysicsPosition(planet.GetComponent <NBody>());
        Vector3 v_i  = ge.GetVelocity(planet);

        Debug.Log(" r_i=" + r_i + " r_od=" + r_od + " delta=" + Vector3.Distance(r_i, r_od));
        Debug.Log(" v_i=" + v_i + " v_od=" + v_od + " delta=" + Vector3.Distance(v_i, v_od));
        Assert.IsTrue(GEUnit.FloatEqual(Vector3.Distance(r_i, r_od), 0f, 1E-2));
        Assert.IsTrue(GEUnit.FloatEqual(Vector3.Distance(v_i, v_od), 0f, 1E-2));
    }
    private void TestRV(OrbitData od, GameObject planet, GameObject star)
    {
        GameObject testPlanet = TestSetupUtils.CreatePlanetInHyper(star, 1f);

        testPlanet.name = "TestPlanet";
        OrbitHyper testHyper = testPlanet.GetComponent <OrbitHyper>();

        testHyper.InitFromOrbitData(od);

        planet.name = "Planet";

        // Awkward but cannot add a new object to GE when it is stopped, so re-add all three
        GravityEngine ge = GravityEngine.Instance();

        ge.Clear();
        ge.AddBody(star);
        ge.AddBody(planet);
        ge.AddBody(testPlanet);
        ge.Setup();
        ge.LogDump();
        Vector3 r_od = ge.GetPhysicsPosition(testPlanet.GetComponent <NBody>());
        Vector3 v_od = ge.GetVelocity(testPlanet);
        Vector3 r_i  = ge.GetPhysicsPosition(planet.GetComponent <NBody>());
        Vector3 v_i  = ge.GetVelocity(planet);

        Debug.Log(" r_i=" + r_i + " r_od=" + r_od + " delta=" + Vector3.Distance(r_i, r_od));
        Debug.Log(" v_i=" + v_i + " v_od=" + v_od + " delta=" + Vector3.Distance(v_i, v_od));
        Assert.IsTrue(FloatEqual(Vector3.Distance(r_i, r_od), 0f, 5E-2));
        Assert.IsTrue(FloatEqual(Vector3.Distance(v_i, v_od), 0f, 5E-2));
    }
    private void TestRV(OrbitData od, GameObject planet, GameObject star, float orbitRadius)
    {
        GameObject   testPlanet  = TestSetupUtils.CreatePlanetInOrbit(star, 1f, orbitRadius);
        OrbitEllipse testEllipse = testPlanet.GetComponent <OrbitEllipse>();

        // Run init explicitly to update transform details
        testEllipse.InitFromOrbitData(od);

        // Awkward but cannot add a new object to GE when it is stopped, so re-add all three
        GravityEngine ge = GravityEngine.Instance();

        ge.Clear();
        ge.AddBody(star);
        ge.AddBody(planet);
        ge.AddBody(testPlanet);
        ge.Setup();
        ge.LogDump();
        Vector3 r_od = ge.GetPhysicsPosition(testPlanet.GetComponent <NBody>());
        Vector3 v_od = ge.GetVelocity(testPlanet);
        Vector3 r_i  = ge.GetPhysicsPosition(planet.GetComponent <NBody>());
        Vector3 v_i  = ge.GetVelocity(planet);

        Debug.Log(" r_i=" + r_i + " r_od=" + r_od + " delta=" + Vector3.Distance(r_i, r_od));
        Debug.Log(" v_i=" + v_i + " v_od=" + v_od + " delta=" + Vector3.Distance(v_i, v_od));
        Assert.IsTrue(FloatEqual(Vector3.Distance(r_i, r_od), 0f, 1E-2));
        Assert.IsTrue(FloatEqual(Vector3.Distance(v_i, v_od), 0f, 1E-2));
    }
    private void AddGhostBodies()
    {
        // Create a ghost moon and put soiEnter/Exit ships into orbit around it. Add all to GE
        // (ghost moon does not have an OrbitPredictor)
        ghostMoon      = new NBody[2];
        ghostMoonOrbit = new OrbitUniversal[2];
        for (int i = 0; i < 2; i++)
        {
            GameObject ghostMoonGO = Instantiate(moonBody.gameObject);
            ghostMoon[i]      = ghostMoonGO.GetComponent <NBody>();
            ghostMoonOrbit[i] = ghostMoonGO.GetComponent <OrbitUniversal>();
        }
        ghostMoon[MOON_SOI_ENTER].gameObject.name = "GhostMoon (SOI enter)";
        ghostMoon[MOON_SOI_EXIT].gameObject.name  = "GhostMoon (SOI exit)";
        ghostMoonSoiEnterOrbitPredictor           = ghostMoon[MOON_SOI_ENTER].GetComponentInChildren <OrbitPredictor>();

        ghostMoon[MOON_SOI_ENTER].GetComponentInChildren <LineRenderer>().material = toMoonMaterial;
        ghostMoon[MOON_SOI_EXIT].GetComponentInChildren <LineRenderer>().material  = fromMoonMaterial;

        ghostShip               = new NBody[NUM_GHOST_SHIPS];
        ghostShipOrbit          = new OrbitUniversal[NUM_GHOST_SHIPS];
        ghostShipOrbitPredictor = new OrbitPredictor[NUM_GHOST_SHIPS];
        GameObject ghostShipGO;

        for (int i = 0; i < NUM_GHOST_SHIPS; i++)
        {
            ghostShipGO                           = Instantiate(shipSOIPrefab);
            ghostShip[i]                          = ghostShipGO.GetComponent <NBody>();
            ghostShipOrbit[i]                     = ghostShipGO.GetComponent <OrbitUniversal>();
            ghostShipOrbit[i].p_inspector         = soiRadius;
            ghostShipOrbit[i].centerNbody         = planet;
            ghostShipOrbitPredictor[i]            = ghostShipGO.GetComponentInChildren <OrbitPredictor>();
            ghostShipOrbitPredictor[i].body       = ghostShipGO;
            ghostShipOrbitPredictor[i].centerBody = planet.gameObject;
            LineRenderer lineR = ghostShipOrbitPredictor[i].GetComponent <LineRenderer>();
            lineR.startWidth = lineWidth;
            lineR.endWidth   = lineWidth;
            ghostShipGO.transform.SetParent(planet.gameObject.transform);
        }

        // check prefab has orbitU in Kepler mode
        if (ghostShipOrbit[0].evolveMode != OrbitUniversal.EvolveMode.KEPLERS_EQN)
        {
            Debug.LogError("ShipSoi prefab must have an on-rails OrbitU");
            return;
        }

        ghostShip[TLI].gameObject.name  = "Ghost TLI";
        ghostShipOrbit[TLI].p_inspector = shipRadius;
        ghostShipOrbitPredictor[TLI].GetComponent <LineRenderer>().material = toMoonMaterial;

        // customize ghost ships as necessary
        // ENTER_SOI
        ghostShip[TO_MOON].gameObject.name  = "Ghost To Moon";
        ghostShipOrbit[TO_MOON].p_inspector = shipRadius;
        ghostShipOrbitPredictor[TO_MOON].GetComponent <LineRenderer>().material = toMoonMaterial;

        // SOI Enter
        ghostShip[ENTER_SOI].gameObject.name = "Ghost SOI Enter";
        ghostShip[ENTER_SOI].gameObject.transform.SetParent(ghostMoon[MOON_SOI_ENTER].gameObject.transform);
        Destroy(ghostShipOrbitPredictor[ENTER_SOI]);
        // Use OrbitPredictor to show SOI at entry
        //ghostShipOrbitPredictor[ENTER_SOI].centerBody = ghostMoon[MOON_SOI_ENTER].gameObject;
        //ghostShipOrbitPredictor[ENTER_SOI].GetComponent<LineRenderer>().material = aroundMoonMaterial;
        ghostShipOrbit[ENTER_SOI].centerNbody = ghostMoon[MOON_SOI_ENTER];

        // HYPER_SOI
        ghostShip[SOI_HYPER].gameObject.name = "Ghost SOI Hyper";
        ghostShip[SOI_HYPER].gameObject.transform.SetParent(ghostMoon[MOON_SOI_ENTER].gameObject.transform);
        ghostShipOrbitPredictor[SOI_HYPER].centerBody         = ghostMoon[MOON_SOI_ENTER].gameObject;
        ghostShipOrbitPredictor[SOI_HYPER].hyperDisplayRadius = soiRadius;
        ghostShipOrbit[SOI_HYPER].centerNbody = ghostMoon[MOON_SOI_ENTER];
        ghostShipOrbitPredictor[SOI_HYPER].GetComponent <LineRenderer>().material = aroundMoonMaterial;

        // EXIT_SOI
        ghostShip[EXIT_SOI].gameObject.name = "Ghost SOI Exit";
        ghostShipOrbitPredictor[EXIT_SOI].GetComponent <LineRenderer>().material = fromMoonMaterial;

        // Tell GE about everything
        ge.AddBody(ghostMoon[MOON_SOI_ENTER].gameObject); // also adds ENTER_SOI
        ge.AddBody(ghostShip[TLI].gameObject);
        ge.AddBody(ghostShip[TO_MOON].gameObject);
        ge.AddBody(ghostShip[EXIT_SOI].gameObject);
        ge.AddBody(ghostMoon[MOON_SOI_EXIT].gameObject);

        // set TLI ship to inactive and control pos/vel in ComputeTransfer
        ge.InactivateBody(ghostShip[TO_MOON].gameObject);
    }
    /// <summary>
    /// Take the Nbody objects in the nbodies list set them inactive and make them children of a new
    /// NBody object moving as the CM of the nbodies. This allows RigidBody mechanics during close
    /// encounters.
    /// </summary>
    private void Activate()
    {
        if (nbodies.Length < 2)
        {
            Debug.LogError("Need two or more nbodies");
            return;
        }

        GravityEngine ge = GravityEngine.Instance();

        // Step 1: calculate CM position and velocity
        Vector3d cmPos = new Vector3d(0, 0, 0);
        Vector3d cmVel = new Vector3d(0, 0, 0);

        float mass = 0f;

        rigidBodies = new Rigidbody[nbodies.Length];

        // RigidBody is assumed to be attached to one of the children (to keep model scale independent)
        int i = 0;

        foreach (NBody nbody in nbodies)
        {
            rigidBodies[i] = nbody.GetComponentInChildren <Rigidbody>();
            //rigidBodies[i] = nbody.GetComponent<Rigidbody>();
            if (rigidBodies[i] == null)
            {
                Debug.LogError("Abort - No rigidbody detected on " + nbody.gameObject.name);
                return;
            }
            mass  += rigidBodies[i].mass;
            cmPos += rigidBodies[i].mass * ge.GetPositionDoubleV3(nbody);
            cmVel += rigidBodies[i].mass * ge.GetVelocityDoubleV3(nbody);
            i++;
        }
        cmPos /= mass;
        cmVel /= mass;
        Debug.LogFormat("CM p={0} v={1} mass={2}", cmPos.ToVector3(), cmVel.ToVector3(), mass);

        // Step2: Inactivate the NBodies and make children of a new NBody object
        priorParents = new Transform[nbodies.Length];
        cmObject     = new GameObject("DockingGroupCM");
        cmNbody      = cmObject.AddComponent <NBody>();

        // Set cm pos/vel
        // NBody InitPosition will use transform or initialPos base on units. Set both.
        cmNbody.initialPos         = cmPos.ToVector3() * ge.physToWorldFactor;
        cmNbody.transform.position = cmNbody.initialPos;
        ge.AddBody(cmObject);
        ge.SetVelocity(cmNbody, cmVel.ToVector3());
        Debug.LogFormat("set pos={0} actual={1}", cmPos.ToVector3(), ge.GetPhysicsPosition(cmNbody));
        i = 0;
        foreach (NBody nbody in nbodies)
        {
            Vector3d pos = ge.GetPositionDoubleV3(nbody);
            Vector3d vel = ge.GetVelocityDoubleV3(nbody);
            priorParents[i] = nbody.gameObject.transform.parent;
            ge.InactivateBody(nbody.gameObject);
            nbody.gameObject.transform.parent = cmObject.transform;
            // position wrt to CM. Need to convert to Unity scene units from GE Internal
            pos = (pos - cmPos) * ge.physToWorldFactor;
            vel = GravityScaler.ScaleVelPhysToScene(vel - cmVel);
            nbody.transform.localPosition = pos.ToVector3();
            rigidBodies[i].velocity       = vel.ToVector3();
            // rigidBodies[i].isKinematic = false;
            i++;
            Debug.LogFormat("body {0} p={1} v={2}", nbody.gameObject.name, pos.ToVector3(), vel.ToVector3());
        }
        // activate any RCS elements
        foreach (ReactionControlSystem r in rcs)
        {
            if (r != null)
            {
                r.SetRigidBodyEnabled(true);
            }
        }
    }