Search Unity

Calculating/Predicting a way.

Discussion in 'Scripting' started by McGravity, Feb 23, 2014.

  1. McGravity

    McGravity

    Joined:
    Nov 2, 2013
    Posts:
    60
    Hi, I'm just starting with physics, so I'm not always sure about what I'm doing. It's a 2D project but I'm using 3D physical objects like SphereCollider etc..

    What I have:

    Objects floating in space and affecting each other through gravity:

    Code (csharp):
    1.     protected virtual IEnumerator OnTriggerStay(Collider other) {
    2.         yield return new WaitForFixedUpdate();
    3.  
    4.         if(other.attachedRigidbody) {
    5.             Vector3 offsetVector = this.transform.position - other.transform.position;
    6.             float distance = offsetVector.magnitude;
    7.             float gravityForce = (other.rigidbody.mass * mass) / Mathf.Pow(distance, 2);
    8.             // Clamp gravity.
    9.             if(gravityForce > 1.0F) {
    10.                 gravityForce = 1.0F;
    11.             }
    12.             other.attachedRigidbody.constantForce.force = offsetVector.normalized * gravityForce;
    13.         }
    14.     }
    There are controllable objects on which the player can click and drag a line away from the object in order to give it a force (shoot) in the opposite direction. While aiming the time is slowed down:

    Code (csharp):
    1.     Time.timeScale = 0.01F;
    2.     Time.fixedDeltaTime = 0.02F * Time.timeScale;
    What I want to achieve:

    The player should see a rough prediction of the way while aiming. That means that the way-prediction needs to take in account the current velocity, the force which would be applied when the player release the mouse button and the gravity of the surrounding objects.

    What I have tried so far:

    For testing purposes I just save the computed/predicted positions in an array and draw those positions in OnDrawGizmos().

    I wrote a method which returns the gravity influence for a certain position called computeGravityForPosition(Vector3 position).

    And thats how I try to calculate the positions:

    Code (csharp):
    1.     private void drawWayPrediction() {
    2.         Vector3 pos = this.transform.position;
    3.         // The offsetVector for the shooting action.
    4.         Vector3 forceVector = pos - Camera.main.ScreenToWorldPoint(Input.mousePosition);
    5.         forceVector.z = 0.0F;
    6.  
    7.         // The predicted momentum scaled up to increase the strength.
    8.         Vector3 force = (forceVector.normalized * forceVector.magnitude) * 10;
    9.  
    10.         // 1. I guess that this is wrong, but don't know how to do it properly.
    11.         momentum = this.rigidbody.velocity + force;
    12.  
    13.         for(int i = 0; i < predictionPoints.Length; i++) {
    14.             float t = i * Time.fixedDeltaTime;
    15.             momentum += computeGravityForPosition(pos);
    16.             pos += momentum * t * t;
    17.             predictionPoints[i] = pos;
    18.         }
    19.     }
    At the beginning, when the objects just slowly approaching each other it looks okay. After the first shot, the prediction is completely wrong. I guess it is because of 1. in the code. Just adding the force to the velocity is probably horrible wrong.

    Please don't just lead me to some wiki page or basic physics explanations. I think I need some more specific help here and I would be pleased to get some explanation added on top. Trying to teach myself what is going wrong here led to some serious wiki page hopping of speed, velocity, acceleration and even more confusion.

    Thank you very much for your time.
     
  2. AlwaysSunny

    AlwaysSunny

    Joined:
    Sep 15, 2011
    Posts:
    260
    I don't have the time at the moment, but your question deserves a bump. If no one else beats me to it, I'll have a go at helping out when I can. Don't get too excited, I got a B in physics, and that was a while back. ;) I want to see this project, assuming there's something to see. Sounds cool!
     
  3. exiguous

    exiguous

    Joined:
    Nov 21, 2010
    Posts:
    1,749
  4. AlwaysSunny

    AlwaysSunny

    Joined:
    Sep 15, 2011
    Posts:
    260
    In a simpler scenario, you could just run this kinematic equation with increasing time_delta values to get your projected displacement at successive intervals:

    displacement = ( velocity_initial * time_delta ) + (acceleration * time_delta^2 * 0.5f);

    In your case though, it's no good since you don't have a constant acceleration. Instead you'll have to take multiple samples, where the results of the previous samples affect the current one.

    I guess you know the further out in time the projection looks the less accurate it will be, simply by virtue of having multiple bodies acting on each other. Depending on how many bodies are acting and what granularity you want, it's unlikely everyone will have the CPU horsepower to get it approximated in a timely fashion. Therefor accuracy is guaranteed to suffer with distance-projected, based on the too-expensive-to-predict nature of your bodies.

    If you'd be satisfied with this approach, you could do a loop like you're doing now, but carry over results from the previous iteration to plug back into the simple kinematic equation. I'm going to refer to things as what they'd represent in this recursive loop, but keep the variable names the same as in the equation above.

    displacement
    gives projected object's position delta this recursive-projection-loop step
    each result is added to the previous result, thus becoming the next position on your curve
    a visible sprite or curve-node belongs at each position
    velocity_initial
    projected object's instantaneous velocity this step
    based on the last step's position delta
    units must be m/s or in unity terms, units per second!

    time_delta
    time in seconds each recursive-projection-step looks ahead.
    fixed number, does not
    accumulate or relate to loop iterator or anything in unity's Time class
    acceleration
    computeGravityForPosition() at the object's projected position this step
    projected position this step is previous step's displaced position
    units must be m/s^2 or in unity terms, units per second squared!


    Hmm.. And the first step, your acceleration includes the force you're going to give the body upon exiting "aim mode." How many times you run it determines how many nodes you get, and time_delta changes the relative distance between them. Maybe my math makes sense. I'm tired, I appologize if my logic or clarifications aren't up to par. I wanna see this game of yours, I mean it!
     
    Last edited: Feb 25, 2014
  5. McGravity

    McGravity

    Joined:
    Nov 2, 2013
    Posts:
    60
    Thank you very much AlwaysSunny,

    as I have to learn for the exams at the moment, I will see what I can do with your answer at the weekend.

    It's gonne be a small game-app in the scale of Angry Birds and co.. I'll keep you in mind as a beta tester if you like.
     
  6. McGravity

    McGravity

    Joined:
    Nov 2, 2013
    Posts:
    60
    It almost looks good, but at this point I still have my problems.

    For the "aiming mode" I have this Vector = pos - Camera.main.ScreenToWorldPoint(Input.mousePosition);

    I can get the direction from normalizing it and get the length from the magnitude. How do I get a force from this though, without having any acceleration? And the current movement (e.g. for the second shot) has to be taken into account. For this I only have the current velocity from the rigidbody, don't I? How am I mixing up the force (of which I'm also still in need) and the current velocity?

    For the rest your explanation is pretty good! Thank you very much again, the solution seems to be near :)
     
  7. AlwaysSunny

    AlwaysSunny

    Joined:
    Sep 15, 2011
    Posts:
    260
    I'm really happy you've found this helpful, I enjoy this kind of work despite being somewhat inept. ;)

    Might be you simply chose your words incautiously, but whatever body you're doing future projections for does have an acceleration every frame - which is exactly what you should be getting from computeGravityForPosition() - since any change in velocity is called acceleration.

    And your rigidbody.velocity is your velocity in units per second, so that's correct too, but you won't have that for your projections beyond step one. You'll have to have a clever way to compute each step's would-be velocity based on the projected position deltas of your previous steps. I'm fuzzy here, but I'm relatively confident it'd be

    projectedVelocity = (previousStep'sProjectedPosition - theStepBeforeThatOne'sProjectedPosition) / time_delta


    The force you'll give upon release is going to be based on the mouseDragVector as you said, but the scalar you multiply by will be determined experimentally and be based on relative masses. You should intuitively choose a maxDragLength float which your mouseDragVector's magnitude cannot exceed, then compare the mouseDragVector's length to it with

    percent = mouseDragVector.magnitude / maxDragLength

    You then multiply that 0..1 result with your experimentally chosen maxForceToApply float, like so

    forceToApplyOnRelease = mouseDragVector.normalized * percent * maxForceToApply

    The only reason to do this differently would be if you wanted to know ahead of time how many newtons of force to apply - for instance, if you were only choosing the direction to apply N force in.

    So in the first step of the recursive-projection-loop, I think your acceleration should be

    computeGravityForPosition() + forceToApplyOnRelease

    but in subsequent steps it should only be computeGravity, since you're giving a one-shot impulse force.
     
    Last edited: Feb 25, 2014
  8. McGravity

    McGravity

    Joined:
    Nov 2, 2013
    Posts:
    60
    Ah okay, so if i got you right, the shooting force is okay.

    The problem for the initial acceleration is, that the current acceleration of the object is not taken into account. computeGravityForPosition() really only gives the gravity force for a certain point. It's completely independent of the current movement of the object. So if I shoot the object, then preparing a second shot, the prediction is not correct, because it is just using the force vector from the mousepositon (aiming) and the gravity of the sorrounding objects. It is acting as if the shootable object is standing still.
     
  9. AlwaysSunny

    AlwaysSunny

    Joined:
    Sep 15, 2011
    Posts:
    260
    computeGravityForPosition() should give the acceleration due to gravity at a supplied point, and doesn't need to consider the current movement, that all sounds fine. We're on the same page that this is and will be a one-size-fits-all solution, which doesn't care whether the object to be affected is already in motion or not.

    As long as the variables supplied to the kinematic equation are in the right form, it's going to predict the future displacement of the object at time time_delta, just like it suggests it will:

    displacement = ( velocity_initial * time_delta ) + (acceleration * time_delta^2 * 0.5f);

    meters = (meters/second * seconds) + (meters/second/second * (seconds^2) * 0.5);
     
    Last edited: Feb 25, 2014
  10. McGravity

    McGravity

    Joined:
    Nov 2, 2013
    Posts:
    60
    Hmm, but with computeGravityForPosition() I have the same problem. At the moment it is returning a vector representing the gravity force for a position. For the shootable object, I can get the current velocity and the mass from the attached rigidbody. I just don't know how to introduce those parameters to the current equations. They are not taken into account. Wether it be in computeGravityForPosition() or the initial acceleration for the way prediction.

    I will provide an updated version of the code...

    Code (csharp):
    1.  
    2.  
    3. Vector3 forceVector = pos - Camera.main.ScreenToWorldPoint(Input.mousePosition);
    4.         forceVector.z = 0.0F;
    5.  
    6.         // The predicted momentum.
    7.         Vector3 force = forceVector.normalized * mass * forceVector.magnitude;
    8.  
    9. Vector3 pos = this.transform.position;
    10.         Vector3 acceleration = computeGravityForPosition(pos) + force;
    11.  
    12.         // displacement = ( velocity_initial * time_delta ) + (acceleration * time_delta^2 * 0.5f);
    13.         float timeDelta = 0.2F;
    14.         predictionPoints[0] = pos;
    15.  
    16.         for(int i = 1; i < predictionPoints.Length; i++) {
    17.             acceleration += computeGravityForPosition(pos);
    18.             pos += (((pos - predictionPoints[i - 1]) / timeDelta) * timeDelta) + (acceleration* Mathf.Pow(timeDelta, 2) * 0.5F);
    19.             predictionPoints[i] = pos;
    20.         }
    21.  
    And the gravity calculation (the list holds objects in a certain range and is maintained by OnTriggerEnter and OnTriggerExit):

    Code (csharp):
    1.     private Vector3 computeGravityForPosition(Vector3 position) {
    2.         Vector3 gravityVector = Vector3.zero;
    3.  
    4.         foreach(KeyValuePair<string, Rigidbody> entry in gravityInfluencer) {
    5.             Vector3 offsetVector = position - entry.Value.position;
    6.             float distance = offsetVector.magnitude /*- (this.transform.localScale.x / 2) Verkleinerung der Distanz, so dass Oberfläche für Anziehungskraft entscheidend ist. */;
    7.             float gravityForce = (entry.Value.mass * mass) / Mathf.Pow(distance, 2);
    8.             if(gravityForce > 1.0F) {
    9.                 gravityForce = 1.0F;
    10.             }
    11.             gravityVector -= offsetVector.normalized * gravityForce;
    12.         }
    13.  
    14.         return gravityVector;
    15.     }
     
    Last edited: Feb 25, 2014
  11. AlwaysSunny

    AlwaysSunny

    Joined:
    Sep 15, 2011
    Posts:
    260
    Definitely proofread my logic - this is untested notepad coding. ;)

    Code (csharp):
    1. // experiment with this
    2. public float forceFactor = 1;  
    3. // currently requires these be class scope
    4. private const float projectionStepTimeDelta = 0.2f;
    5. private Vector3[] predictionPoints;
    6.  
    7.  
    8. // start calling when you enter aimMode, where origin = your transform.position when aimMode was entered
    9. void CalculateProjection( Vector3 origin ) {  
    10.  
    11.     // get force vector from input  
    12.     Vector3 forceVector = origin - Camera.main.ScreenToWorldPoint(Input.mousePosition);
    13.     forceVector.z = 0.0F;    
    14.    
    15.     // The predicted acceleration due to user input
    16.     // was Vector3 force = forceVector.normalized * mass * forceVector.magnitude; but this is equivalent to...
    17.     Vector3 force = forceVector * mass * forceFactor;  // introduced forceFactor for extra control
    18.  
    19.     // it is now officially necessary to set element zero to our origin here, as you already did
    20.     predictionPoints[0] = origin;        
    21.  
    22.     // sum the force with your computed gravity as before
    23.     Vector3 acceleration = computeGravityForPosition( origin ) + force;
    24.        
    25.     // your magic function.  1 order of magnitude more expensive with the length of predictionPoints array
    26.     RecursiveProjection( 1, rigidbody.velocity, acceleration );
    27.  
    28.     // now use freshly populated predictionPoints[] to draw curve or whatever you want    
    29.     DrawProjectedPoints();
    30. }
    31.  
    32.  
    33. void RecursiveProjection( int iterator, Vector3 velocity, Vector3 acceleration ) {
    34.  
    35.     // displacement = ( velocity_initial * time_delta ) + (acceleration * time_delta^2 * 0.5f);    
    36.     Vector3 projectedDisplacement = ( velocity * projectionStepTimeDelta ) + ( acceleration * (projectionStepTimeDelta^2) * 0.5f );
    37.    
    38.     // add projected displacement to the previous point to carry the projected displacement into world space
    39.     // conveniently, array element zero is already in world space, so this line should suffice
    40.     predictionPoints[ iterator ] = predictionPoints[iterator-1] + projectedDisplacement;    
    41.      
    42.     // call recursively until array is populated
    43.     iterator++;    
    44.     if (iterator <= predictionPoints.Length-1) {
    45.  
    46.         // our best guess of the velocity experienced during the next step - another kinematic equation
    47.         Vector3 projectedVelocity = velocity + (acceleration * projectionStepTimeDelta);
    48.  
    49.         // next step's acceleration, based on current accel due to gravity at our projected point
    50.         Vector3 projectedAcceleration = computeGravityForPosition( predictionPoints[ iterator ] );    
    51.  
    52.         // call method again with our best guess values
    53.         RecursiveProjection( iterator, projectedVelocity, projectedAcceleration );            
    54.     }
    55. }
    By the way, I didn't really proof your computeGravity function carefully, but I was unclear on why gravityForce is clamped to be <=1 Just a heads up that could cause trouble unless you know what you're doing. I'd love to hear how this works out, don't leave us hanging! ;)
     
    Last edited: Feb 26, 2014
  12. McGravity

    McGravity

    Joined:
    Nov 2, 2013
    Posts:
    60
    Still not working :/

    I think I will refrain from taking the current motion in account and just shoot the object as if it is standing still. Maybe this is more fun for a player anyways.

    Thank you very much for your effort!
     
  13. AlwaysSunny

    AlwaysSunny

    Joined:
    Sep 15, 2011
    Posts:
    260
    Just to clarify, whether the object is already in motion makes no difference whatsoever using my approach, since the current velocity and force to apply are both considered in step one of the loop. You'll still need to solve the projection same way, except it will no longer be nearly as accurate. What exactly isn't working? Maybe we can figure it out.
     
    Last edited: Feb 26, 2014
  14. McGravity

    McGravity

    Joined:
    Nov 2, 2013
    Posts:
    60
    What isn't or wasn't working all the time, was that the prediction was way to heavy. You're right, I mistaken something, with your approach the current motion is taken in account. But the prediciton was still going to outer space :)

    This is my current version. It looks pretty fine and the remaining error coud come from what you already said, that the prediciton obviously gets impresice over time.

    Code (csharp):
    1.     private void drawWayPrediction() {
    2.         Vector3 pos = this.transform.position;
    3.         // The force which will be applied to the planet when player shoots.
    4.         Vector3 forceVector = pos - Camera.main.ScreenToWorldPoint(Input.mousePosition);
    5.         forceVector.z = 0.0F;
    6.  
    7.         float timeDelta = 0.2F;
    8.  
    9.         Vector3 force = forceVector * mass * FORCE_FACTOR;
    10.  
    11.         Vector3 acceleration = (this.constantForce.force + (force / this.rigidbody.mass)) * timeDelta;
    12.         Vector3 velocity = this.rigidbody.velocity;
    13.  
    14.         predictionPoints[0] = pos;
    15.  
    16.         for(int i = 1; i < predictionPoints.Length; i++) {
    17.             pos += (velocity * timeDelta) + (acceleration * Mathf.Pow(timeDelta, 2) * 0.5F);
    18.             predictionPoints[i] = pos;
    19.  
    20.             velocity = (pos - predictionPoints[i - 1]) / timeDelta;
    21.             acceleration = computeGravityForPosition(pos) / timeDelta;
    22.         }
    23.     }
     
  15. AlwaysSunny

    AlwaysSunny

    Joined:
    Sep 15, 2011
    Posts:
    260
    Only thing I see right away is that the acceleration you calculate in the loop shouldn't be divided by the timeDelta - that doesn't make any sense. Nor does multiplying by timeDelta when you first set acceleration before the loop. Getting the velocity your way ought to be okay. Great job keeping it tidy, I went overboard trying to make it obvious what I was doing.

    If you're happy with the results you're getting then congratulations! I love a good tricky math problem - especially when it's someone else's problem. ;)
     
    Last edited: Feb 26, 2014