Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice
  4. Dismiss Notice

Question How to make a gear rotate another gear based on their rotation axis and relative position

Discussion in 'Scripting' started by thetrapman, Aug 5, 2023.

  1. thetrapman

    thetrapman

    Joined:
    Mar 9, 2019
    Posts:
    6
    Let's say the left gear is rotating and the right gear is connected to him. How can i calculate the correct rotation for the right gear by using their positions in space, the rotational axis of them (which can be parallel or perpendicular on each other) and applied rotation on the powered(left) one?
    I feel like i have to use sinus and cosinus for this in a way similar with iteration in points located on a circle circumference but i cannot think at a suitable solution.
    I can use a lot of raycasts which start from the left gear center and extend around his theets; in this way i can find contact location of these two gears and based on direction teeth move/rotate in that location (the red arrows) i somehow can translate this direction to the other gear and rotate it but i want to avoid raycast route because it will mean i have to do like > 16 raycasts for each rotated gear and cpu will cry when there are a lot of gears in action
    Also i don't want to use physics for the same reason

    upload_2023-8-5_17-59-22.png
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,780
    Good. Physics would be insanely hard to get working on something like this.

    Gear ratios are simple:

    Technically it is the ratio of the circumference at the contact point of the gears.

    Because circumference is related to radius, the radius can serve as a proxy.

    Code (csharp):
    1. float outputAngle = (inputAngle / inputGearRadius) * outputGearRadius;
    That's it. That's how all non-frictional transmissions work.
     
    thetrapman likes this.
  3. thetrapman

    thetrapman

    Joined:
    Mar 9, 2019
    Posts:
    6
    Yes, that is correct, but is not enough for gears to work correctly in different conditions but (funny) it was enough for me to get motivated and just start work and think on implementation myself :D.
    Just applying that outputAngle to rotate other gear around his axis created multiple problems e.g. if rotation axis vector of two parallel gears were pointing in opposite direction (like vector.up and vector.down), gears got rotated in same way. On perpendicular gears problems were even worse and the gears would rotate correctly only in few particular relative positions.
    After a lot of thinking i came with a more than enough working solution:

    Code (CSharp):
    1. public SimpleGear connectedGear;
    2.     public float radius;
    3.  
    4.     public bool isMotor;
    5.     public float angle;
    6.  
    7.  
    8.     private void Update()
    9.     {
    10.      
    11.         if(isMotor)
    12.         {
    13.             Vector3 inFwd = transform.forward; // transform.forward represent gear's rotational axis
    14.             Vector3 inPos = transform.position;
    15.  
    16.             Vector3 outFwd = connectedGear.transform.forward;
    17.             Vector3 outPos = connectedGear.transform.position;
    18.  
    19.             Vector3 inContactDir = Vector3.ProjectOnPlane((outPos - inPos).normalized, inFwd).normalized;
    20.             Vector3 outContactDir = Vector3.ProjectOnPlane((inPos - outPos).normalized, outFwd).normalized;
    21.  
    22.             Vector3 goodInFwd = inFwd;
    23.             Vector3 goodOutFwd = outFwd;
    24.  
    25.             if (Vector3.Angle(inFwd, outFwd) > 10 && Vector3.Angle(inFwd, outFwd) < 170)
    26.             {
    27.                 goodInFwd = Vector3.ProjectOnPlane((outPos - inPos).normalized, inContactDir).normalized;
    28.                 goodOutFwd = Vector3.ProjectOnPlane((inPos - outPos).normalized, outContactDir).normalized;
    29.             }
    30.             else
    31.             {
    32.                 if(Vector3.Angle(goodInFwd,goodOutFwd) >= 170)
    33.                 {
    34.                     goodInFwd = -goodInFwd;
    35.                 }
    36.             }
    37.            float outputAngle = -(angle / radius) * connectedGear.radius * ((goodOutFwd.normalized == outFwd.normalized) ? 1:-1) * ((goodInFwd.normalized == inFwd.normalized) ? 1 : -1);
    38.  
    39.             transform.Rotate(transform.forward * angle * Time.deltaTime, Space.World);
    40.        
    41.             connectedGear.transform.Rotate(outFwd * outputAngle * Time.deltaTime, Space.World);
    42.  
    43.             Debug.DrawLine(inPos, inPos +  inContactDir.normalized, Color.red);
    44.             Debug.DrawLine(outPos, outPos + outContactDir.normalized, Color.green);
    45.  
    46.             Debug.DrawLine(connectedGear.transform.position, connectedGear.transform.position + goodOutFwd*100);
    47.  
    48.            }
    49.     }
    The code is working but i feel like i overcomplicated it and it could be done fewer lines of code(for e.g. inContactDir and outContactDir variable might be replaced with gears transform.right or .up )
     
  4. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,917
    If physics isn't required, then gears only need to rotate themselves, and tell other connected gears what speed they were rotated by and what ratio they are. Then all other connected gears can do the same.

    Then it's just a matter of rotating said gear on their axis with something like
    Quaternion.AngleAxis
    .

    When I did something like this I found that you often want the relationship to be two-way, as in you should be able to rotate either gear (or any gear within a series) and they rotate each-other, so they need a reference to one another. This means you also need to add a few safe-guards, such as when a gear is rotated, it doesn't try to rotate the gear that's driving it, otherwise you end up with a stack-overflow.
     
    Last edited: Aug 6, 2023
    Kurt-Dekker likes this.
  5. wideeyenow_unity

    wideeyenow_unity

    Joined:
    Oct 7, 2020
    Posts:
    728
    Not sure if you were just looking for a simple example, but I came up with:
    Code (CSharp):
    1. public class RotateTest : MonoBehaviour
    2. {
    3.     public Transform otherTrans;
    4.  
    5.     void Update()
    6.     {
    7.         float speed = 0.1f;
    8.         transform.Rotate(0, speed, 0);
    9.         float rotOffset = 5.0f; // teeth align value
    10.         otherTrans.rotation = Quaternion.Euler(
    11.             transform.rotation.eulerAngles.y + rotOffset, 0, 0);
    12.     }
    13. }
    I used 2 cubes with the same orientation, so otherTrans.rotation is on the X value, so if your other gear is just rotated 90, just set that for Y instead of X. Not sure if another way is more performant or not, but I assumed you just wanted something simple. :)
     
  6. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,917
    Just want to emphasise my point with an example. Again, gears don't need to rotate other gears, they just need to tell other gears that they need to rotate, and they can rotate themselves.

    For example:
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3.  
    4. public class MechanicalGear : MonoBehaviour
    5. {
    6.     #region Inspector Fields
    7.  
    8.     [SerializeField]
    9.     private List<MechanicalGear> _connectedGears = new List<MechanicalGear>();
    10.  
    11.     [SerializeField, Min(2)]
    12.     private int _ratio = 10; // ergo, the number of gears
    13.  
    14.     [SerializeField]
    15.     private Vector3 _axis = Vector3.forward; // blue axis
    16.  
    17.     #endregion
    18.  
    19.     #region Properties
    20.  
    21.     public int Ratio => _ratio;
    22.  
    23.     #endregion
    24.  
    25.     #region Driving Methods
    26.  
    27.     public void DriveGear(float speed) // for non-gear sources to drive gears
    28.     {
    29.         RotateGear(speed);
    30.  
    31.         foreach (var gear in _connectedGears)
    32.         {
    33.             gear.DriveGear(this, speed);
    34.         }
    35.     }
    36.  
    37.     public void DriveGear(MechanicalGear driver, float speed)
    38.     {
    39.         float ratio = (float)_ratio / (float)driver.Ratio;
    40.         speed = -1 * (speed / ratio); // we multiply by negative 1 to reverse direction
    41.  
    42.         RotateGear(speed);
    43.  
    44.         foreach (var gear in _connectedGears)
    45.         {
    46.             // we don't want to drive the gear driving this one, otherwise we go infinite
    47.             if (gear != driver)
    48.             {
    49.                 gear.DriveGear(this, speed);
    50.             }
    51.         }
    52.     }
    53.  
    54.     private void RotateGear(float speed)
    55.     {
    56.         Quaternion rotation = Quaternion.AngleAxis(speed, _axis);
    57.         transform.rotation = rotation * transform.rotation;
    58.     }
    59.  
    60.     #endregion
    61. }
    And of course, gears don't drive themselves, so we just use another script to run them:
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class GearDriver : MonoBehaviour
    4. {
    5.     #region Inspector Fields
    6.  
    7.     [SerializeField]
    8.     private float _driverSpeed = 10f;
    9.  
    10.     [SerializeField]
    11.     private MechanicalGear _gear;
    12.  
    13.     #endregion
    14.  
    15.     #region Unity Callbacks
    16.  
    17.     private void Awake()
    18.     {
    19.         if (!_gear)
    20.         {
    21.             this.enabled = false;
    22.         }
    23.     }
    24.  
    25.     private void Update()
    26.     {
    27.         float speed = _driverSpeed * Time.deltaTime;
    28.         _gear.DriveGear(speed);
    29.     }
    30.  
    31.     #endregion
    32. }
    And it works pretty well: https://i.gyazo.com/d5e89206bf41a26d72c13a50c52b872d.mp4

    Notably this means you do not at all need to care about each gear's orientation to one another.
     
    Chubzdoomer likes this.
  7. thetrapman

    thetrapman

    Joined:
    Mar 9, 2019
    Posts:
    6
    Indeed, your solution is good and working. But it is applicable only if the gears position or rotation is not updated during runtime(because this happens: https://gyazo.com/608694473693a6983d5a72eaf131653b ). For use cases where we create the gears in unity editor and we don't want to update positon or rotation at runtime, it is more than enough.

    In my case, I want the gears to have some kind of "fake" physics such way dynamically changes at runtime will not broke gears logic and also creating custom gears at runtime (like a crafting system) will not involve manually setting up their rotation axis.
    My solution work but is far from beeing finished: https://gyazo.com/b61716d69fa2229f6635a7a7c52c2106

    Also, you are right, gears should not rotate other gears, and there should be clear separation of logic between what a gear should do and what driver/motor should do. I think, gears and motor should be in different scripts and their scripts alone should not do much on their own, instead, a single gear system/controller script which contains a list of gears and motors should be responsible for updating their rotations based on connected motors. In such way different cases can be handled easier , e.g. if motor1 send to gear3 rotation 50, and motor2 sent to gear3 rotation -25, gear rotation should be 25
     
  8. wideeyenow_unity

    wideeyenow_unity

    Joined:
    Oct 7, 2020
    Posts:
    728
    I personally would make gears rotate other gears, as if you're simulating the physics. And can also get more in depth if you were making a real-style gearing system, as far as scale or teeth in ratio.

    But if your not simulating physics, then true, just make parent rotate all of them appropriately to "look" right.
     
  9. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,917
    That's kind of a separate problem, and a level higher than the actual gear mechanics.

    Whatever system you/the player uses to manipulate and place gears should be seperate - and on top of - the actual system that drives these gears. The lower level system should just have an appropriate API for establishing and breaking these connections.

    It'll be a lot more manageable than actually trying to simulate gears with any sort of fidelity.