Search Unity

How To Apply Torque Based on Normal

Discussion in 'Scripting' started by John-B, Aug 19, 2014.

  1. John-B

    John-B

    Joined:
    Nov 14, 2009
    Posts:
    1,262
    I have an object that consists of two paddles connected to a base with a hinge joint. The two paddles rotate horizontally around a center vertical pole.
    Sample.jpg
    This works correctly when an object hits one of the paddles. They spin around and a spring brings them back to their original position.

    I also want the user to be able to tap one of the paddles and have the object respond by rotating. The problem is, I can't consistently get the object to rotate in the right direction, away from the camera. The direction it rotates when tapped depends on both the object's rotation and the relative camera position. Sometimes it rotates away from the camera, as it should, but sometimes it rotates towards the camera.

    I've tried AddForceAtPosition, but unless the tap is near the edge of the paddle, not much happens. I've tried AddTorque. I've tried using the hit.normal to determine the direction of force, but that doesn't work consistently either. I've tried using the hit.point minus the camera position to determine the direction of force. But the object still rotates the wrong direction sometimes.

    I also have another type of object that works essentially the same, but pivots vertically. It always pivots in the right direction when tapped:

    Code (JavaScript):
    1.  tVec = hit.point - tCamera.transform.position;
    2.  tForce = 50.4;
    3.  tTrans.rigidbody.AddForceAtPosition(tVec.normalized * tForce, hit.point);
    But this doesn't work with the horizontal pivot. How can I get the correct direction of the force to apply to make my horizontal pivot to rotate in the correct direction when tapped?
     
  2. RockoDyne

    RockoDyne

    Joined:
    Apr 10, 2014
    Posts:
    2,234
    If you are trying to rotate something on an axis, you would be better off locking the axis of rotation and modifying the magnitude.
    Code (CSharp):
    1. rigidbody.AddTorque(vector3.up*addedTorque)
    addedTorque would then be the only variable you need to control.
     
  3. John-B

    John-B

    Joined:
    Nov 14, 2009
    Posts:
    1,262
    My problem isn't the magnitude of the force or the axis of rotation, but the direction. The axis of rotation is correct (it's controlled by the hinge joint). I want to apply a force perpendicular to the surface of the paddle AWAY from the direction of the hit. Like if you push something, it should move away from you, not towards you.
     
  4. RockoDyne

    RockoDyne

    Joined:
    Apr 10, 2014
    Posts:
    2,234
    Yeah, you are going to have a hell of a time flipping a bunch of vectors. Flipping the normal is probably the easiest by making tforce negative.

    Part of the reason I like add torque is because it is just the up vector and a float that isn't any harder to figure out.
     
  5. John-B

    John-B

    Joined:
    Nov 14, 2009
    Posts:
    1,262
    As I tried to explain, that doesn't work. If I tap from one direction, it works, but from the opposite direction it rotates the wrong way. In other words, it would appear there's no way to tell direction from the normal, which makes no sense.

    Maybe I'm not explaining this very well. If you look at the diagram, the side view on the left: If I tap the right paddle in that view, it should rotate counter clockwise, and if I tap the left paddle, it should rotate clockwise. It should do that regardless of the object's rotation or the camera position. For a particular rotation of the object, it does work that way. But rotate the object 180 deg, and it rotates in the wrong direction.

    Here's one of the many code variations that DOES NOT work (does not consistently rotate the right direction):

    Code (JavaScript):
    1. tForce = 10.4;
    2. if (hit.collider.name == "1 Paddle")
    3.   tTrans.rigidbody.AddTorque(Vector3(0.0, hit.normal.z * tForce, 0.0), ForceMode.Impulse);
    4. if (hit.collider.name == "2 Paddle")
    5.   tTrans.rigidbody.AddTorque(Vector3(0.0, -hit.normal.z * tForce, 0.0), ForceMode.Impulse);
    This works, but because the paddles are so short, you have to tap them at the very edge, and still can't get much force:
    Code (JavaScript):
    1. tVec = hit.point - tCamera.transform.position;
    2. if (hit.collider.name == "1 Paddle ")
    3.     tTrans.rigidbody.AddForceAtPosition(tVec.normalized * tForce, hit.point);
    4. if (hit.collider.name == "2 Paddle")
    5.     tTrans.rigidbody.AddForceAtPosition(tVec.normalized * tForce, hit.point);
    6.  
    This works with the vertical version of the object because the arms that support the paddles are much longer.
     
  6. RockoDyne

    RockoDyne

    Joined:
    Apr 10, 2014
    Posts:
    2,234
    I suppose the issue then is the hit point needs to be offset by the center of the axis. It's not in the right local space or the mesh/sprite itself isn't balanced at origin.

    Okay, so now that I've pulled my head back a bit, the easiest way to do this is have colliders (maybe not exactly colliders as these are supposed to move) on the left and right of the axle that billboard the camera. Right collider spins it CCW and left collider spins it CW. Simple as that.

    Otherwise, you will have to find the scalar magnitude of the camera's forward vector combined with the wheel's forward vector, subtract it by one to get a positive/negative value in relation to the wheel's facing, then just multiply that by hit.point in the wheel's local space. No trig involved, just horrible matrix math.
     
  7. John-B

    John-B

    Joined:
    Nov 14, 2009
    Posts:
    1,262
    Thanks for the help. Turns out that since this object spins horizontally around its Y axis, depending on the object's rotation, the force (from the tap) could either be along the X or Z axis, and I was only checking one of those axes. For example, I was using the Z normal, but when the object was rotated parallel to that axis, the Z normal was close to zero. That's why it wasn't working at certain object rotations. I had to check to see which normal, X or Z, was greater, and use that to calculate the force. I guess that means normals are in world coordinates. It was not a problem with the vertically-rotating object because it doesn't rotate around its Y axis.
     
    Last edited: Aug 20, 2014
  8. Fraconte

    Fraconte

    Joined:
    Dec 6, 2013
    Posts:
    327
    I think this code have to works always. No matter if rotation is horizontal or vertical. The only thing that can make this not working could be if the hit.point change position from the left side of the axis to the right side, in the time from where you "click" to when you apply the force. Something goes wrong perhaps because of too much rotation speed, or some delay in your code. Are you doing all of this in FixedUpdate() right?
     
  9. Fraconte

    Fraconte

    Joined:
    Dec 6, 2013
    Posts:
    327
    I did some tests: the only problems I had was trying to use hit.normal. Testing the mouse continuously with GetMouseButton(), make it always stop when the mouse hit the edge of the collider. I corrected this but only on the edge parallel to the rotation axis.

    Best fix I have found is to make the thickness of the collider = 0.
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class Push : MonoBehaviour
    5. {
    6.     public float tForce = 50.4f;
    7.     Camera tCamera;
    8.  
    9.     // Use this for initialization
    10.     void Start ()
    11.     {
    12.         tCamera = Camera.main;
    13.     }
    14.  
    15.     // Update is called once per frame
    16.     void Update ()
    17.     {
    18.         if (!Input.GetMouseButton (0))
    19.             return;
    20.      
    21.         RaycastHit hit;
    22.         if (!Physics.Raycast(tCamera.ScreenPointToRay(Input.mousePosition), out hit, 100))
    23.             return;
    24.  
    25.         if (!hit.rigidbody || hit.rigidbody.isKinematic)
    26.             return;
    27.  
    28.         Vector3 tVec = hit.point - tCamera.transform.position;
    29.  
    30.         // Check that the direction of the hit.normal is away from camera
    31.         int dir = Vector3.Dot (hit.normal, tVec) >= 0f ? 1 : -1;
    32.  
    33.         // Use this line to check that the hit.normal is not directed from the edge to the center.
    34.         // This only works on the edge parallel to the rotation axis.
    35.         // You dont need it if collider thickness is set = 0.
    36.         //if (Mathf.Abs(Vector3.Dot (hit.point - hit.transform.position, hit.normal)) < .9f)
    37.             hit.rigidbody.AddForceAtPosition(hit.normal * dir * tForce, hit.point);
    38.  
    39.         // This always works, but the intensity of the force depends from the camera position
    40.         //hit.rigidbody.AddForceAtPosition(tVec * tForce, hit.point);
    41.     }
    42. }
    43.  
    I am not sure if hit.normal is already normalized or not...
     
    Last edited: Aug 21, 2014