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

Need some 3D math help

Discussion in 'Scripting' started by Sabo, Feb 22, 2017.

  1. Sabo

    Sabo

    Joined:
    Nov 7, 2009
    Posts:
    151
    Describing things in 3D can be hard, but I will do my best.

    We have a ground (square) and a ball (sphere) in a 3D world. We are using Unity's axis alignment, where forward is positive z, right is positive x and up is positive y.

    The ball can be controlled by the player, which can give input in the x and z axes (y position being adjusted as needed while traversing the ground). At the start of the game the ball is located at (0,0,0).

    Here is how I would like the ball to move depending on the input and the orientation of the ground. The input vector is (x,z). For the sake of simplicity we do not take any external forces into account when moving the ball.

    I believe that the simplest way to describe what I want is the following.
    First scenario: The ground has no rotation and the input is (1,1). The ball is moved to (1,0,1).
    Second scenario: The position of the ball after it has been moved, relative the ground, in this scenario should be the same as in the first scenrio. The ground is rotated 89 degrees around the z axis and the input is (1,1). The ball will be trying to move through the ground, but I want it to travel in a direction somewhat similar to (0.02,0.98,1).

    If we would have one screenshot for each scenario, after the ball has been moved, they would look identical if the camera had been rotated along with the ground.

    I assume this can be solved by first translating the ball then do some rotation magic, but that probably is not an option since that assumes the ground does not change throughout the movement of the ball. I need to figure out the direction vector based on the orientation of the ground immediately below the ball and the input so that I can do some sort of (ray)casting along it, moving the ball in steps if the ground is uneven.

    I have been able to move the ball along the surface of the ground, but the "direction vector along the surface" is only correct when the ground is not rotated or is rotated 90 degrees. Anything inbetween and it is off, so my math is wrong. Anyone?
     
  2. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,744
    Let's start with an assumption: the ball is always "attached" to the ground at a particular point. In other words, at no point will the ball ever be jumping or falling (or if it is, this movement logic won't apply).

    A second assumption will make the algorithm a lot easier: The ground is close to flat. Basically as long as you're moving in small increments and don't have any right angles on your ground (chamfer the edges of your ground mesh if need be), this simple case should work.

    1. Determine the mesh normal at your current point (you can use a raycast, with the most recent normal as a "base"). In your raycast, determine just how much leeway you need based on how smoothly rotated the ground will be, and use that as the distance. You don't want this to be too high, or you could easily jump instantaneously to a wrong point "below" the existing surface if you hit an edge. Essentially, there must be not point at which your player can go "off the edge" of the mesh in one movement "frame". (In order to protect against low framerates, you may consider putting your movement code in FixedUpdate)

    NOTE: If your raycast doesn't hit, you should probably revert to the most recent known good position - your ball has hit an edge it doesn't know how to traverse.

    2. Get the player's desired movement vector (probably by modifying the input based on camera space - easiest thing here is to have a transform that's a child of the camera, and use transform.TransformPoint(xInput, 0, yInput) ) This movement vector can be any direction, and doesn't need to be normalized (that'll happen later).

    3. "flatten" the movement vector into a plane to the surface normal. I don't remember the math for this offhand, but if you need help working it out I can try to work it out. Maybe try playing with OrthoNormalize?

    4. normalize the movement vector, then multiply by speed and deltaTime, and move the ball.

    5. (optional, but recommended) Raycast again. Place the ball at the raycast's hit point. If the surface has curved along the course of the move, this will help keep your ball glued to the ground surface. (if perfect visual fidelity isn't necessary, you can merge this step into the following frame's step #1. You could also cache the raycast results for efficiency.)

    If you can't guarantee those conditions for your ground mesh, then it's gonna get complicated, and you'll probably have to dig into the ground mesh in order to be able to traverse sharp edges.
     
    Last edited: Feb 22, 2017
  3. Sabo

    Sabo

    Joined:
    Nov 7, 2009
    Posts:
    151
    #3 is the problem. I have no idea how to accomplish that.
     
  4. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    I'm assuming you can get the surface normal of the point your ball is at.

    Some quick notes.
    To map a vector u onto Vector v

    DotProduct(u*v)/Magnitude(v)^2 * v
    I believe Unity has built in function for this:
    Code (CSharp):
    1. Vector3 projection = Vector3.Project(u,v);  // u onto v
    Now to project a vector (like your movement vector) onto a plane, we just need to subtract out the portion of our vector that is orthagonal to that plane (the normal of that plane). If you think of a normal vector3 with an x,y,z component. and we want to project it onto the x,z plane we just have to subtract out the Y. the normal of the x,z plane is the Y axis. This works for any rotated plane.

    So the code for your flattened movement vector is:
    Code (CSharp):
    1. Vector3 surfaceNormal = // get your surface normal for this point
    2. Vector3 movmentVector = // get your movement vector from input
    3.  
    4. Vector3 actualMovment = movementVector - Vector3.Project(movementVector,surfaceNormal);
     
  5. Sabo

    Sabo

    Joined:
    Nov 7, 2009
    Posts:
    151
    Yeah, nice example and I learned something from it. However, although I want the direction to be along the surface of the plane, projection is not what I am looking for.

    I believe that the solution I am looking for (need some additional testing) is
    Code (CSharp):
    1. var surfaceAlignedDirection = hitInfo.transform.rotation * direction;
    As I said, I need to test it, but drawing rays using Debug.DrawRay() seems to indicate that it is the right solution. If so, the problem shifts (I suspect) to figuring out the rotation of the ground below the sphere when I am not using a single plane / cube object from Unity but a level designed in a 3D program such as Maya. Unless there is some easy way to calculate the "rotation" using the normal of the surface below the spehere?
     
  6. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    I believe this would give you the proper rotation:

    Code (CSharp):
    1. // This will give us a rotation from absolute world up/forward to the normal
    2. Quaternion rotation = Quaternion.FromToRotation(Vector3.up,surfaceNormal);
    3.  
    4. // This will give us a rotation from our object's current rotation
    5. // to the rotation of the ground below us.
    6. // You would want to lerp from current rot to this rot to make is smooth
    7. Quaternion.FromToRotation(transform.up,surfaceNormal);
    There is a ton of Maths behind the FromToRotation.. but thats the point of a Engine. They did it already :)
     
  7. lordconstant

    lordconstant

    Joined:
    Jul 4, 2013
    Posts:
    389
    I cant currently test this but as long as the ball is a child of the ground if you move it in local space you dont need to do any extra work. Just use transform.localPosition and you can use the forward and right vectors.
     
    takatok likes this.
  8. Sabo

    Sabo

    Joined:
    Nov 7, 2009
    Posts:
    151
    That code did the trick, but only as long as the surface below only is rotated around one axis. If the surface is rotated around multiple axes Quaternion.FromToRotation(Vector3.up, surfaceNormal) differs from hitInfo.transform.rotation.
     
  9. Sabo

    Sabo

    Joined:
    Nov 7, 2009
    Posts:
    151
    Even if that would work it is not a solution I would like to use. It does not make sense for every character etc to be a child of ground.
     
  10. lordconstant

    lordconstant

    Joined:
    Jul 4, 2013
    Posts:
    389
    Fair enough.

    If you get a reference to the floor you can use its forward and right vectors to move correctly.