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)
At the moment I go about some of the initial steps like this with a short cylinder (think a hockey puck):
- Take the average normal below the puck using 8 raycasts around the rim towards the ground
- Use this average normal to have the puck mimic the surface's average tilt
- Use the camera's rotation to as the reference for determining where the stick is pointing from the player's POV
- 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()
{
moveControl.Gameplay.Enable();
}
void OnDisable()
{
moveControl.Gameplay.Disable();
}
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?