I making movement process on Unity.
I would like to make a process that moves the object to a specified position, but as the title suggests, I want the object not only to move, but also to reach a predetermined distance with its velocity decaying.
If the acceleration is negative, I can not to process it well.
Specifically, I want to reach a position without turning back when the initial velocity is 10 as shown in the gif.
I used "s=v0t+1/2at^2" from the constant acceleration movement formula to find the acceleration "a", but that does not seem to be enough.
I would appreciate it if you could help me.
public class Test : MonoBehaviour
{
public float t;
public float initSpd;
public Transform t1, t2;
IEnumerator Start()
{
var p = t1.position;
while (true) {
t1.position = p;
var v0t = initSpd * t;
var distance = Vector2.Distance(t1.position, t2.position);
var direction = (t2.position - t1.position).normalized;
var a = (2 * (distance - v0t)) / (t * t);
var v = initSpd;
// update
yield return Utils.Coroutine.WhileForSeconds(t, () =>
{
t1.Translate(direction * v * Time.deltaTime);
v += a * Time.deltaTime;
});
}
}
}
span=3, initial velocity=0
span=3, initial velocity=3
span=3, initial velocity=10
Your math to compute a is correct, but you have posed the problem in a way that cannot be solved to your satisfaction.
As you said, with constant acceleration the position is a quadratic function of time, so it is described by
s = b t^2 + c t + d
for some constants b, c, d. The value of d is already fixed by the initial position. The value of c is already fixed by the initial velocity. There is only one free parameter left, and when you solve for s(finalTime) = goalPosition, you can't control whether or not the resulting parabola overshoots the goal or not.
You can increase the polynomial degree, but there will always be some initial velocity that is too large and causes overshoot.
Essentially, you have an optimal control / trajectory optimization problem, something like
minimize: integral(acceleration(t)^2) from t = 0 to T
subject to: x(0) given
v(0) given
x(T) given
x(t) <= x(T) for all t in [0, T] (assuming x(0) < x(T))
As you stated the problem, there was no optimization objective, but you need either a cost or a constraint on the acceleration, otherwise you could get solutions with infinite acceleration (e.g. accelerate really hard in the first time step, then coast to the goal at constant velocity).
The inequality makes it complicated. If you want a continuous-time analytic solution, then Pontryagin's principle would be a hammer to solve it, but there might be easier tricks. If you discretize time and let the acceleration be piecewise-constant, then it's an easy convex optimization problem.
If you are willing to relax the "never overshoot" and "get there at exactly this time" constraints, then very simple solution, that will scale better to more complex scenarios like external forces, is to use a feedback control law like a PD controller:
a = kp * vectorToGoal - kd * velocityVector,
where kp and kd are hand-tuned positive constants, and this expression is re-evaluated in every frame. You can tune kp and kd to minimize overshoot in your typical scenarios.
Or, you could do what most games do - allow velocity to change instantaneously :)
I found the ideal behavior achieved by changing the speed in two steps.
IEnumerator Start()
{
var p = t1.position;
while (true)
{
t1.position = p;
var direction = (t2.position - t1.position).normalized;
var distance = Vector2.Distance(t1.position, t2.position);
var v0 = initSpd;
var M = distance;
var T = duration;
var tm = M / v0;
var vm = v0 / T * tm;
var accel1 = (vm - v0) / (tm - 0);
var accel2 = (0 - vm) / (T - tm);
Debug.Log($"vo={v0}, M={M}, T={T}, tm={tm}, vm={vm}, accel1={accel1}, accel2={accel2}");
var v = initSpd;
var stime = Time.time;
var hist = 0f;
// update
yield return Utils.Coroutine.WhileForSeconds(T, () =>
{
t1.Translate(direction * v * Time.deltaTime);
hist += v * Time.deltaTime;
if (Time.time - stime <= tm)
v += accel1 * Time.deltaTime;
else
v += accel2 * Time.deltaTime;
});
Debug.Log($"elapsed={Time.time - stime}, moved distance={hist}, v={v}");
}
}
Related
I am trying to make a game with Unity that uses rockets that are in orbit and can transition to different planets, but I need to predict the rocket path in real-time, depending on the current velocity/direction of the rocket allot like spaceflight simulator does.
I have managed to get realistic physics and orbits when adding gravitational forces to a rigidBody:
var sateliteCords = Satelite.transform.position;
var planetCords = gameObject.transform.position;
var distance = sateliteCords - planetCords;
distanceFromSatelite = Vector3.Distance(Satelite.transform.position, gameObject.transform.position);
F = (Gravity * Mass) / Mathf.Pow(distanceFromSatelite,2);
forces = (distance / Mathf.Sqrt(Mathf.Pow(distance.x, 2)+ Mathf.Pow(distance.y, 2)+ Mathf.Pow(distance.z, 2))) * -F;
Satelite.GetComponent<Rigidbody>().AddForce(forces * Time.deltaTime, ForceMode.Force);
but I don't know how to predict future points in time to give me the orbit shape.
I have tried "speeding up the simulation" with Time.timeScale = 50; but it isn't fast enough.
I read somewhere that I need a copy of the solar system running independently form the unity updates, and calculate the rocket trajectory from there but I don't know how to approach that.
I'm a bit of a noob when it comes to physics simulations so can one of you geniuses please help?
Here's what your code is actually doing:
var sateliteCords = Satelite.transform.position;
var planetCords = gameObject.transform.position;
var distance = sateliteCords - planetCords;
distanceFromSatelite = distance.magnitude;
F = (Gravity * Mass) / Mathf.Pow(distanceFromSatelite,2);
forces = distance / distanceFromSatellite * -F;
Satelite.GetComponent<Rigidbody>().AddForce(forces * Time.deltaTime, ForceMode.Force);
You can further reduce it by doing:
var distance = sateliteCords - planetCords;
distanceSqrMag = distance.sqrMagnitude;
F = (Gravity * Mass) / distanceSqrMag;
forces = distance.normalized * -F;
Satelite.GetComponent<Rigidbody>().AddForce(forces * Time.deltaTime, ForceMode.Force);
Now that the code is much simpler, I can find a crucial mistake. ForceMode.Force already multiplies the Force by Time.deltaTime. What you want here is to switch to ForceMode.Impulse, or remove the deltaTime.
F = (Gravity * Mass) / distanceSqrMag;
forces = distance.normalized * -F;
Satelite.GetComponent<Rigidbody>().AddForce(forces, ForceMode.Force);
Or better yet, assuming Mass is the mass of the satellite, and gravity is not a general constant, but just the local gravity of your planet. ForceMode.Force divides the force by the mass of the rigidbody it is applied on.
F = Gravity / distanceSqrMag;
forces = distance.normalized * -F;
Satelite.GetComponent<Rigidbody>().AddForce(forces, ForceMode.Acceleration);
Now that's all out the way, you can do a very simple approximation by doing something like
var currentPos = _rigidBody.position;
var prevPos = currentPos;
var currentVelocity = _rigidBody.velocity;
var planetCords = gameObject.transform.position;
for (int i = 0; i < stepCount; i++)
{
var distance = planetCords - currentPos;
var forceMag = Gravity / distance.sqrMagnitude;
forces = distance.normalized * forceMag;
currentVelocity += forces * Time.fixedDeltaTime;
currentPos += currentVelocity * Time.fixedDeltaTime;
//Replace by a TrailRenderer you reset each frame
Debug.DrawLine(prevPos, currentPos, Color.Red, Time.deltaTime);
prevPos = currentPos;
}
Note that this code is untested, I probably made a mistake here or there, but that's the gist of it.
Don't worry about getting any difference in trajectory, this is exactly what unity does to predict positions. No need for fancy calculations when you got all the computing power you need.
If you want more than one-body gravity simulation, it's simple. Either apply the gravity of all planets, or simply the gravity of the closest planet taking its mass into account. I recommend the former if you have less than a dozen planets.
You don't need conics, or advanced math, or any of that. You're not kerbal space program. Sometime brute force is the best solution.
I am trying to create a custom solar system for a research project, to test different numerical methods and see their difference.
We are making our own solar system instead of trying to simulate earth/sun since that seemed harder.
For now we have a sun M1 and earth M2.
M1 = 3500
M2 = 500
distance from M1 to M2 = 200
We use a different G = 0.2
When we calculate the gravitional force it should be, F = 8.75 or -8.75 (F = GM1M2/r^2)
When we calculate the constant speed the earth should have to orbit around earth we find v = 2 (v = sqrt(G(M1+M2)/r))
To calculate the vec2 of the force we use the follow code
static double GravitationalConstant = 0.2f;//6.674e-11;
static public Vector2f GravitationalForce(SolarObject onObj, SolarObject fromObj)
{
Vector2f result = fromObj.OldPosition - onObj.OldPosition;
float distance = result.Lenght();
Console.WriteLine(distance);
result = new Vector2f(result.X / distance, result.Y / distance); // Normalized, but I've the lenght al ready so
result *= (float)((GravitationalConstant * onObj.Mass * fromObj.Mass) / (distance * distance));
return result;
}
And to update the position/velocity we use this
public void Update(float dt, List<SolarObject> objects)
{
foreach(SolarObject s in objects.Skip(1))
{
Vector2f f = Utility.GravitationalForce(s, objects[0]);
Vector2f a = f / s.Mass;
s.OldPosition = s.NewPosition;
s.NewPosition += dt * s.Velocity
s.Velocity += dt * a;
}
}
The object does fly around the sun, but it's not an orbit at all. The distance between M1/M2 is not constant <-> the force is also not always equal to 8.75f. We know euler has an 'error', but this seems to big, since without even orbiting one circle 25% of the distance is al ready increased. So there has to be a bug somewhere.
Unfortunately, this behavior is intrinsic to Euler integration - you will always overshoot a curved path by some degree. This effect can be suppressed by using a smaller timestep, which works even without doubles:
dt = 0.01:
dt = 0.001:
dt = 0.0001:
dt = 0.00001:
As you can see, the Euler method's accuracy improves with decreasing timestep. The inner planets (smaller orbital radius = greater curvature = more overshoot) start to follow their projected orbits (green) more consistently. The overshoot becomes only just visible for the innermost planet at dt = 0.0001, and not at all for dt = 0.00001.
To improve on the Euler method without having to resort to ridiculously small timesteps, one could use e.g. Runge-Kutta integration (the 4-th order variant is popular).
Also, the orbital velocity should be v = sqrt(G*Msun/r)) rather than v = sqrt(G(M1+M2)/r)), although for the large star limit this should not cause too much of a problem.
(If you would like my test code please let me know - although it is very badly written, and the core functions are identical to yours.)
user starts from A and moves to C though(via) B (sample points on screen) in unity3d. at this point, i have calculated angle (theta) which in both images is almost 45 deg(almost). problem is i wanted to conclude that in left image user intended CounterClockWise motion and in right image user intends clockwise rotation.
ahh, its really complicated than i imagined, please suggest.
currently unity code is like
protected void calcAngles(){
v1 = go2.position - go1.position;
v2 = go3.position - go1.position;
v3 = go3.position - go2.position;
float angle = Vector3.Angle (v2, v1);
Debug.Log ("angle:" + angle.ToString ());
//float atan = Mathf.Atan2 (Vector3.Dot (v3, Vector3.Cross (v1, v2)), Vector3.Dot (v1, v2)) * Mathf.Rad2Deg;
//Debug.Log ("atan2:" + atan.ToString ());
}
any ideas.? psudo code.? huge thanks in advance. cheers,lala
It is incredibly difficult to do this.
This may help...
private float bestKnownXYCWAngleFromTo(Vector3 a, Vector3 b)
// the best technology we have so far
{
a.z = 0f;
b.z = 0f;
float __angleCCW = Mathf.Atan2(b.y, b.x) - Mathf.Atan2(a.y, a.x);
if ( __angleCCW < 0f ) __angleCCW += (2.0f * Mathf.PI);
float __angleCWviaatan = (2.0f * Mathf.PI) - __angleCCW;
__angleCWviaatan = __angleCWviaatan * (Mathf.Rad2Deg);
if ( __angleCWviaatan >= 360.0 ) __angleCWviaatan = __angleCWviaatan-360.0f;
return __angleCWviaatan;
}
note that this is a 2D concept, modify as you need.
note that obviously "a" is just your (b-a) and "b" is just your (c-a)
Please note that true gesture recognition is a very advanced research field. I encourage you to get one of the solutions out there,
https://www.assetstore.unity3d.com/en/#!/content/14458
https://www.assetstore.unity3d.com/en/#!/content/21660
which represent literally dozens of engineer-years of effort. PDollar is great (that implementation is even free on the asset store!)
Uuuups. My answer from before was completely wrong. It seems that Vector3.Angle always gives a unsigned angle. But we need the sign to understand whether is rotating clockwise or counterclockwise.
Now, this piece of code will give you a SIGNED angle between your two vectors. The Normal argument should be the normal to the plane you want to consider.
float SignedAngle(Vector3 from, Vector3 to, Vector3 normal){
float angle = Vector3.Angle(from,to);
float sign = Mathf.Sign(Vector3.Dot(normal, Vector3.Cross(from,to)));
float signed_angle = angle * sign;
return signed_angle;
}
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 9 years ago.
Improve this question
so i have torpedos in my game and they start out at 0 meters per second and accelerate realistically. after so many seconds they stop accelerating and travel at a constant rate forward.
I have a distance to the target and I basically am trying to calculate lead time for autoaiming.
So given
Distance to target;
Acceleration (per second);
burn time (number of seconds before acceleration stops);
I need to basically determine I believe the average meters per second the projectile is travelling.
The only way I can see to do it is something like this.
curdistance; //stores distance traveled per second
currentspeed; //stores speed at a given second
acceleration;
for(int timer = 1; curdistance < distanceToTarget;timer++)
{
currentspeed = currentspeed + acceleration;
curdistance = curdistance + ( currentspeed);
if(timer >= burnTime)
{
acceleration = 0;
}
}
Now this works but it has 2 problems.
The burn time has to be an int or else the smaller the fraction the greater the number of runs to keep accuracy.
If i want a 4.2 burn time for example in order to keep the accuracy i have to run it 42 times and calculate for every 10th of a second.
Also the average could be off by quite a bit depending on how much it overshoots a target depending again on how precise the timer is.
if my projectile is going at 30 meters per second and it needs to go 121 meters it'll add another full second of travel before it goes ok you've gone to/past the target which will mean it will actualy be aiming as it were at a point 29 meters further than it really should.
The only way to combat this with this algorithm is to check more often every 10th or 100th of a second.
I feel like though there might be a math equation I don't know that lets me solve this precisely.
Any Help?
During accelerated motion you can use d = a*t^2/2, or equivalently t = sqrt(2*d/a), at which time velocity v = a*t.
Then you can extrapolate to the target using that v.
As you describe it your movement happens in 2 parts. The first part is accelerated movement (with constant acceleration) and the second part is movement under constant velocity.
You can calculate the traveling distance (or time) for each one individually and then combine them for the desired result.
Keep in mind that you need to check for special cases where the target is closer than the burn distance. The code below does that with the check if (distanceToTarget < burnDistance)
// these will be the results
float timeToTarget;
float averageSpeed;
// assign values to these
float distanceToTarget;
float acceleration;
float burnTime;
float burnDistance = acceleration * burnTime * burnTime * 0.5;
if (distanceToTarget < burnDistance)
{
timeToTarget = Math.Sqrt(2 * distanceToTarget / acceleration);
}
else
{
float velocity = acceleration * burnTime;
timeToTarget = burnTime + (distanceToTarget - burnDistance) / velocity;
}
averageSpeed = distanceToTarget / timeToTarget;
If
d = initial distance to the target
b = burn time
a = acceleration
When the projectile stops accelerating, it will have
speed = a*b
distance (traveled) = dt = a*b^2/2
From that moment, it will need
time for impact = ti = (d-dt)/(a*b)
The total time will be
total time for impact = ti + b
This is one way:
Function VelocityGivenTime(burnTime, givenTime)
(
T = givenTime
If T > burnTime Then T = burnTime
return acceleration * T
)
Function DistanceGivenTime(burnTime, givenTime)
(
If burntime >= givenTime Then
T = givenTime
return 0.5 * acceleration * T^2
Else
T = burnTime
D = 0.5 * acceleration * T^2
D = D + VelocityGivenTime(T) * (givenTime - burnTime)
return D
End IF
)
However, if what you really wanted was the time to a target give its distance, you could do it like this:
Function TimeGivenDistance(burnTime, distance)
(
burnDistance = DistanceGivenTime(burnTime)
If distance > burnDistance Then
return burnTime + (distance - burnDistance) / VelocityGivenTime(burnTime)
Else
return SQRT(2 * distance / acceleration)
End If
)
Anyone have a good idea for a method of achieving random floaty motion? Like, just general smooth drift in a constrained area.
This is what I tried:
var rangeX = 100;
var rangeY = 100;
var gravity = .001;
$('.floating').each(function() {
$(this).data('startX', $(this).position().left);
$(this).data('startY', $(this).position().top);
$(this).data('velocityX', 0);
$(this).data('velocityY', 0);
chooseDestination();
});
setInterval(function() {
$('.floating').each(function() {
var changeX = ($(this).data('destinationX') - $(this).position().left) * gravity;
var changeY = ($(this).data('destinationY') - $(this).position().top) * gravity;
var velocityX = $(this).data('velocityX') + changeX;
var velocityY = $(this).data('velocityY') + changeY;
$(this).data('velocityX', velocityX);
$(this).data('velocityY', velocityY);
$(this).css('left', $(this).position().left + velocityX);
$(this).css('top', $(this).position().top + velocityY);
});
}, 10);
setInterval(chooseDestination, 2000);
function chooseDestination() {
$('.floating').each(function() {
$(this).data('destinationX', $(this).data('startX') - rangeX/2 + Math.random() * rangeX);
$(this).data('destinationY', $(this).data('startY') - rangeY/2 + Math.random() * rangeY);
});
}
It really doesn't look very floaty though.
"Floaty" generally requires slow changes in velocity. What I would do, from a high-level perspective, is set up an acceleration/velocity/position model for movement, where your random generator affects acceleration only. You already have the other two layers. Establish limits for velocity and acceleration at any given point in time (I'd limit absolute distance, not just distance in X and Y), and don't update the rate of acceleration every frame. Lastly, deal in fractional values for velocity and acceleration, which will be turned into an integer position change only at the last minute when you're drawing the object's new position. What you'll end up with is an object that will slowly head off in one direction, then start drifting in a different direction, maybe stop and hover for a second, then start going again.