Calculating the resting position of a rigidbody.

What is the easiest way to calculate the resting transformation of an object with a rigidbody attached to it?

Currently, I have to position the object just over the terrain, then when the scene starts, you see it drop into place.

I’d like to not see it drop when the scene is initialized.

My current idea is to wait for all objects to drop before showing the scene, but I’d prefer something less hacky.

Okay, I’ve found a solution to this from Orient vehicle to ground normal (terrain hugging) - Questions & Answers - Unity Discussions

It isn’t the most straightforward solution, but I can use it for more than just initialization. Currently, I am using it in my character controller.

I don’t actually need the rigidbody with this solution, so I’ve disabled it for now. Also, this could probably use a few optimizations, but it works for now.

Vector3 GetUpVector()
{
    // quad is an invisible rectangle denoting the four corners of the base
    MeshCollider collider = quad.GetComponent<MeshCollider>();

    // ensure we are above the terrain so the hit tests work
    transform.position += Vector3.up * 5;
    Vector3 backLeft = quad.transform.TransformPoint(collider.sharedMesh.vertices[3]);
    Vector3 backRight = quad.transform.TransformPoint(collider.sharedMesh.vertices[0]);
    Vector3 frontLeft = quad.transform.TransformPoint(collider.sharedMesh.vertices[1]);
    Vector3 frontRight = quad.transform.TransformPoint(collider.sharedMesh.vertices[2]);

    RaycastHit lr, rr, lf, rf;
    Vector3 upDir = Vector3.up;
    int layerMask = LayerMask.GetMask(new[] { "Terrain" });

    bool overTerrain = Physics.Raycast(backLeft + Vector3.up,
                                       Vector3.down, out lr, layerMask)
        && Physics.Raycast(backRight + Vector3.up, Vector3.down, out rr, layerMask)
        && Physics.Raycast(frontLeft + Vector3.up, Vector3.down, out lf, layerMask)
        && Physics.Raycast(frontRight + Vector3.up, Vector3.down, out rf, layerMask);

    if (overTerrain)
    {
        upDir = (Vector3.Cross(rr.point - Vector3.up, lr.point - Vector3.up) +
                 Vector3.Cross(lr.point - Vector3.up, lf.point - Vector3.up) +
                 Vector3.Cross(lf.point - Vector3.up, rf.point - Vector3.up) +
                 Vector3.Cross(rf.point - Vector3.up, rr.point - Vector3.up)
        ).normalized;
    }

    // Move back to the starting position
    transform.position += Vector3.down * 5;

    return upDir;
}

Quaternion GetPredictedForwardQuaternion()
{
    // temporarily orient the object and calculate the up vector
    Quaternion origRot = transform.rotation;
    transform.rotation = Quaternion.LookRotation(camera.forward, Vector3.up);
    Vector3 up = GetUpVector();
    transform.rotation = origRot;

    // get the forward vector and create a new quaternion
    Vector3 forward = Vector3.ProjectOnPlane(camera.forward, up).normalized;

    return Quaternion.LookRotation(forward, up);
}

void DropToTerrain()
{
    MeshCollider collider = quad.GetComponent<MeshCollider>();

    // Ensure we are above the terrain.
    transform.position += Vector3.up * 5;
    Vector3 backLeft = quad.transform.TransformPoint(collider.sharedMesh.vertices[3]);
    Vector3 backRight = quad.transform.TransformPoint(collider.sharedMesh.vertices[0]);
    int layerMask = LayerMask.GetMask(new[] { "Terrain" });

    RaycastHit lr, rr;

    // One of these corners will be one of the 3 that are touching the ground
    if (Physics.Raycast(backLeft, Vector3.down, out lr, layerMask)
        && Physics.Raycast(backRight, Vector3.down, out rr, layerMask))
    {
        transform.position += Vector3.down * Mathf.Min(lr.distance, rr.distance);
    }
}