How to get Quaternion.FromToRotation and hit.normal to allow the Y-axis to not be set to 0

I implemented a terrain follow script that looks like this:

	var fwd:Vector3 = transform.TransformDirection(Vector3.forward);
	var up:Vector3 = transform.TransformDirection(Vector3.up);

	transform.localPosition += fwd/speedModifier;

	var hit:RaycastHit;
	Debug.DrawRay(transform.localPosition, -up*20, Color.cyan);
	if (Physics.Raycast(transform.localPosition, -up, hit, 10)){

		if ( hit.distance > 1 ){
			transform.localPosition.y -= hit.distance-1;
		} else if ( hit.distance < 1 ){
			transform.localPosition.y += 1-hit.distance;
		}

		var rot:Quaternion = Quaternion.FromToRotation(Vector3.up, hit.normal);
		transform.rotation = rot;

	}

The issue I am running into is when setting the rotation of the transform it is always snapping the GO on the Y-axis to 0 instead of using the Y-axis rotation already applied (for example 180). I have tried a bunch of different things which either gave undesirable results or setting the Y-axis to 0.

Essentially, how can I get the hit.normal and apply it to the GO transform via Quaternion.FromToRotation and it not snap the Y of the transform?

Thanks in advance!

3 Answers

3

Let's try it again, now read what you want, function below should do the trick. What it does is calculating a projection of the GO's forward vector on plane defined by normal vector of hit, then generate a rotation that looks in direction of the projection vector and has the normal vector as up vector.

var speed = 0.5;

function Update () {
    var fwd:Vector3 = transform.forward;

    transform.position += fwd * speed * Time.deltaTime;

    var hit:RaycastHit;
    // instead of -Vector3.up you could use -transform.up but as hit point will jump
    // when slope changes it will give jitter. That's solvable as well by working from
    // a pivot point in bottom centre of object instead of centre (and to make sure
    // your raycast won't be too low move start pos back by a bit using again
    // transform.up as direction.
    if (Physics.Raycast(transform.position, -Vector3.up, hit, 10)){

    	if ( hit.distance > 1 ){
    			transform.localPosition.y -= hit.distance-1;
    	} else if ( hit.distance < 1 ){
    			transform.localPosition.y += 1-hit.distance;
    	}

    	var proj : Vector3 = fwd - (Vector3.Dot(fwd, hit.normal)) * hit.normal;
    	transform.rotation = Quaternion.LookRotation(proj, hit.normal);
    }
}

Jaap, Thank you for the reply it worked very well!

How do you move the pivot point?

I've been searching ALL NIGHT for the solution to this extremely common problem. Finally found it over here, thank you very much Jaap!

Saved my evening too! Every other solution I found wasn't working for me but this one finally did the trick for setting proper direction when moving around on my planetoid.

Adding to Jaap’s Answer. I had the same problem with the y-rotation snapping when i tried to align something with the terrain.

        public static void AlignTransform(Transform transform)
        {
            Vector3 sample = SampleNormal(transform.position);
           
            Vector3 proj = transform.forward - (Vector3.Dot(transform.forward, sample)) * sample;
            transform.rotation = Quaternion.LookRotation(proj, sample);
        }

        public static Vector3 SampleNormal(Vector3 position)
        {
            Terrain terrain = Terrain.activeTerrain;
            var terrainLocalPos = position - terrain.transform.position;
            var normalizedPos = new Vector2(
                Mathf.InverseLerp(0f, terrain.terrainData.size.x, terrainLocalPos.x),
                Mathf.InverseLerp(0f, terrain.terrainData.size.z, terrainLocalPos.z)
            );
            var terrainNormal = terrain.terrainData.GetInterpolatedNormal(normalizedPos.x, normalizedPos.y);

            return terrainNormal;
        }

This does not use a raycast to get the normal. and instead gets the normal from the terrain data.

Thank you so much @Weblyan. I spent hours looking for a way to do that.