camera – How to rotate a 3D object based on stick Input and the normal of the ground’s surface? (Unity3D)

I am making a game where I want to view an object from any angle and use an analog stick to rotate it according to this input and the surface the object is currently resting on. Here’s a visualization of what I am trying to accomplish to outline my question:

(this is from a camera perspective, user’s view. Also that’s “transform.forward”, woops)
In the top left, diagram its the plane of the surface normal (in magenta). In the bottom left, its the input stick direction (purple) based on the view from the camera. On the right would be the output direction in which the object would travel (orange).

At the moment I go about some of the initial steps like this with a short cylinder (think a hockey puck):

  1. Take the average normal below the puck using 8 raycasts around the rim towards the ground
  2. Use this average normal to have the puck mimic the surface’s average tilt
  3. Use the camera’s rotation to as the reference for determining where the stick is pointing from the player’s POV
  4. In terms of the diagram, this is the step where I’d hope to calculate the orange arrow (the output direction of the object’s forward direction), but so far I cannot figure that part out.

To specify, I want to apply force based on the transform’s forward direction relative to the ground so simply applying force on an XY plane is not what I am trying to accomplish. Below is my best attempt so far. It fails in that it doesn’t turn on the ground according to its movement on the plane.

// All the objects I am using to capture/store the data
GameObject input;
GameObject camera_Axis;

GameObject tank_Manager;
bool isGrounded;

GameObject base_Model; // The "puck's" model
GameObject base_Collider; // The "puck's" collider
GameObject ground_Checker; // Object which casts the 8 rays directly down from puck rim
float groundMatchSpeed; // Speed the objects mimics the ground surface
float turnSpeed; // Speed at which the object would turn left and right relative to the surface

float lastAngleLSFromOrigin;
Vector3 lastPosition;

PlayerControls moveControl;
Vector2 move;

void Awake()
    moveControl = new PlayerControls();
void OnEnable()
void OnDisable()

void Update()
    Quaternion rotation = base_Collider.transform.rotation;

    move = moveControl.Gameplay.LeftStick.ReadValue<Vector2>();
    float LSx = move[0];
    float LSy = -move[1];
    Vector2 LS = new Vector2(LSx, LSy); // Stick inputs

    float angleLSFromOrigin = Vector2.SignedAngle(new Vector2(0f, -1f), LS);
    if ((LSx == 0) & (LSy == 0))
        angleLSFromOrigin = lastAngleLSFromOrigin; // If there is any input, update the angle

    Quaternion relative = camera_Axis.transform.rotation;

    if (LS.magnitude >= 1)
        lastAngleLSFromOrigin = angleLSFromOrigin; // If stick is beyond threshold, update angle    

    if (rotation == camera_Axis.transform.rotation)
        relative = Quaternion.Inverse(rotation) * yRotation(camera_Axis.transform.rotation);
        // I am going to be honest, I don't remember why this works and how, but it is essential. Fixes a problem where the object and camera align perfectly

    // Determine ground tilt from hover test points
    Vector3[] hover_Test_Normals = ground_Checker.GetComponent<check_Grounded>().hover_Test_Normals;
    Vector3 averagedNormal = new Vector3();

    for (int i = 0; i < hover_Test_Normals.Length; i++ ) 
        averagedNormal[0] += hover_Test_Normals[i][0];
        averagedNormal[1] += hover_Test_Normals[i][1];
        averagedNormal[2] += hover_Test_Normals[i][2];

    averagedNormal[0] = averagedNormal[0] / hover_Test_Normals.Length;
    averagedNormal[1] = averagedNormal[1] / hover_Test_Normals.Length;
    averagedNormal[2] = averagedNormal[2] / hover_Test_Normals.Length;

    Quaternion rotateTo = base_Collider.transform.rotation;
    rotateTo = Quaternion.FromToRotation(base_Collider.transform.up, averagedNormal) * rotateTo;

    // Find vector of displacement from last frame
    Vector3 displacmentSinceLastFrame = new Vector3(Mathf.Abs(base_Collider.transform.position.x - lastPosition.x), 0f, Mathf.Abs(base_Collider.transform.position.z - lastPosition.z));
    Vector3 displacementDirection = displacmentSinceLastFrame.normalized;

    var colliderStep = Time.deltaTime * groundMatchSpeed;
    var modelStep = Time.deltaTime * turnSpeed;

    // If grounded, mimic tilt of surface
    if (tank_Manager.GetComponent<tank_State>().isGrounded == true)
        transform.rotation = Quaternion.Lerp(rotation, rotateTo, colliderStep);
        base_Model.transform.rotation = Quaternion.Slerp(rotation, rotateTo, colliderStep);

    lastPosition = base_Collider.transform.position;

private Quaternion yRotation(Quaternion q)
    float theta = Mathf.Atan2(q.y, q.w);

    // quaternion representing rotation about the y axis
    return new Quaternion(0, Mathf.Sin(theta), 0, Mathf.Cos(theta));

I would also like to voluntarily acknowledge I don’t entirely understand Quaternions and may be doing something stupid as a result. If so, please let me know. Any other suggestions?

Source link

More To Explore

UX/UI Links of September 2021

Our favorite UX and UI links from September 2021, including articles about empty states, UX maturity, custom device design, and more. Source link

Share on facebook
Share on twitter
Share on linkedin
Share on email