Why does NavMeshAgent.remainingDistance return values of Infinity and then a float in Unity for unreachable destinations? - unity3d

When I set an unreachable target position with SetDestination() for my NavMeshAgent and Debug.Log() the NavMeshAgent.remainingDistance every frame, I get Infinity for some of the path until it starts returning floats (21.21864, 21.0846, 20.95449...) until it gets as close to the destination as possible, which returns 0.

As of Unity 2019.3, NavMeshAgent.remainingDistance is still calculated only after the penultimate corner of the path has been reached, and the agent is traversing the last segment. Before that, remainingDistance will return infinity. Sadly, this is undocumented.
Here is a NavMeshAgent extension method to get the remaining distance at any moment, or any point of the path:
public static class ExtensionMethods
{
public static float GetPathRemainingDistance(this NavMeshAgent navMeshAgent)
{
if (navMeshAgent.pathPending ||
navMeshAgent.pathStatus == NavMeshPathStatus.PathInvalid ||
navMeshAgent.path.corners.Length == 0)
return -1f;
float distance = 0.0f;
for (int i = 0; i < navMeshAgent.path.corners.Length - 1; ++i)
{
distance += Vector3.Distance(navMeshAgent.path.corners[i], navMeshAgent.path.corners[i + 1]);
}
return distance;
}
}
So instead of using NavMeshAgent.remainingDistance, you can use NavMeshAgent.GetPathRemainingDistance(). Be aware though this can be performance expensive depending the situation, so have that in mind when using it.
As for the second part of your question, we would need more contextual information of your setup, but sounds like your target position may have an offset towards the up vector, while the agent is constrained to the x, z plane, but this is only especulation.

I made this simple script that measures all agent path corner points and adds up the distances. Or just returns agent.remaingDistance if the remaining path is straight.
public float GetRemainingDistance()
{
float distance = 0;
Vector3[] corners = m_navMeshAgent.path.corners;
if (corners.Length > 2)
{
for (int i = 1; i < corners.Length; i++)
{
Vector2 previous = new Vector2(corners[i - 1].x, corners[i - 1].z);
Vector2 current = new Vector2(corners[i].x, corners[i].z);
distance += Vector2.Distance(previous, current);
}
}
else
{
distance = m_navMeshAgent.remainingDistance;
}
return distance;
}
Works well in my project.

Related

Physics.Raycast doesnt seem to work consistently

I'm working on a raycast based pathfinding system. Basically what I'm trying to do is generate points around an object/check if that object can reach those points, and check if those points can reach the target. The target is the green cylinder in the back of the photo. Here is my layer mask which basically says to ignore the player as a collider/obstacle:
layerMask = Physics.DefaultRaycastLayers & ~(1 << 3);
Here is my raycasting code:
// Check if enemy can see player without any obstructions
bool CanSeeDestination(Vector3 startingPoint, Vector3 destination)
{
if(Physics.Raycast(startingPoint, destination, 50f, layerMask))
{
Debug.DrawLine(startingPoint, destination, Color.red);
return false;
} else
{
Debug.DrawLine(startingPoint, destination, Color.green);
return true;
}
}
And finally my pathfinding function:
// Raycast based pathfinding
void Pathfind()
{
List<Vector3> surroundingPoints = new List<Vector3>();
bool foundTarget = false;
// Nested loop to build surrounding points vector array
for(var i = 1; i <= 10; i++)
{
for(var k = 1; k <= 10; k++)
{
// Offset by half of max to get negative distance
int offsetI = i - 5;
int offsetK = k - 5;
surroundingPoints.Add(new Vector3(transform.localPosition.x + offsetI, stepOverHeight.y, transform.localPosition.z + offsetK));
}
}
// Loop through array of surrounding vectors
for(var m = 0; m < surroundingPoints.Count; m++)
{
// If enemy can reach this surrounding point and this surrounding point has an unobstructed path to the target
if(CanSeeDestination(transform.localPosition, surroundingPoints[m]) && CanSeeDestination(surroundingPoints[m], player.transform.position))
{
float distanceFromEnemyToTarget = Vector3.Distance(transform.position, surroundingPoints[m]);
float distanceFromTargetToPlayer = Vector3.Distance(surroundingPoints[m], player.transform.position);
float totalDistance = distanceFromEnemyToTarget + distanceFromTargetToPlayer;
// If this total path distance is shorter than current path distance set this as target
if(totalDistance < currentPathDistance)
{
currentPathDistance = totalDistance;
target = surroundingPoints[m];
foundTarget = true;
}
}
}
if (!foundTarget)
{
target = transform.position;
}
}
For some reason the raycasts trigger on the right side of the obstacle but not the left. Also if I increase the obstacle size or collider size I can eventually block the left side. Not sure why raycasts on the left are green and still passing through the collider.
I resolved the issue. The problem was in this line:
if(Physics.Raycast(startingPoint, destination, 50f, layerMask))
I should have been using Physics.Linecast two go between two points. Raycast goes in a vector "Direction" linecast goes between two points. The correct code is:
if(Physics.Linecast(startingPoint, destination, layerMask))

If the z-axis increases add 1 to variable in unity

I have a game where the score system is linked directly to the z-axis but I want to know how to make it so if the z-axis increases that it will ad one to a variable and if it decreases it will decrease the variable.
You'd want to check on each Update the Z-axis of the GameObject, and then store it so it can be compared in the next Update:
float zAxis = transform.position.z;
unsigned int score = 0;
void Update()
{
if (zAxis > transform.position.z)
score++;
else if (zAxis < transform.position.z && score > 0)
score--;
zAxis = transform.position.z;
}
The above assumes this script would be placed on the GameObject in question (if not, you'll need to get a reference to it), and the score variable should be unsigned if it's not allowed to go below 0 (unsigned integers can only be positive numbers).
Assuming this script is attached to the object whose z-coordinate you are checking. You can update the score variable given the last z-coord:
private int score = 0;
private float lastZ = transform.position.z;
void Update() {
if (transform.position.z < lastZ) {
score--;
}
else if (transform.position.z > lastZ) {
score++;
}
lastZ = transform.position.z;
}

Projectile trajectory / flight path

I'm trying to make a projectile which has movement behaviour shown as red in the diagram.
What I know and have now is two vectors; Start and End.
The end goal is to have some randomness of the arc at iteration and projectile velocity change in a lerp-fashion. I've done linear movement generations before, but nothing like this.
If my question feels like asking you to do my work for me (My usual fear of asking questions as a novice coder) could I have some tips and hints on what methods/commands I should look into? The language is C# and Unity version is 5.6
EDIT # 1
After getting some head-direction I could achieve something closer to the end-goal function of this.
Blue linear line is just representation of distance and angle between A(initiation point) and B(target). The red arc is the trajectory I'm willing to make my projectile to move as.
Fortunately, I figured what I wanted my path to guide the projectile to follow was a cubic bezier and got the result in editor shown in the diagram above with A, B, modA, and modB. There are just a few more things I need to get working on actually mounting the projectile to follow this path and control its velocity and etc. Following are more questions which I couldn't get through today.
First, the general condition is A is fixed and B is not. In order to maintain the generally desired flight path, I figured I need another virtual line lineB(from modA to modB) to sync lineA's angle and distance so when B(the target) is moving around in all directions the arc is not too extremely skewed, but in my attempts today either I got the wrong angle from lineA or something modA just orbited around A and the numbers were weird like angle changing in straight line movement of B from A.
Second is to have some random-but-similar variety of the red arc after the first projectile fires and to the next. I'm guessing this would be somewhat easier when I get past the first one since it's just matter of controlling lineB.
Edit # 2
All the functions asked above are resolved: A path is generated from A to B with arc made with modA and modB as well as the randomness of modA and modB at each iteration as well as modA and modB adjusting according to B's position in real time.
Now All that's left is to actually make the projectile follow the path and control its velocity till reaching B. Below is the code generating the arc-path. How should I approach this?
public Transform[] controlPoints = new Transform[4];
public LineRenderer lineRenderer;
private int curveCount = 0;
private int SEGMENT_COUNT = 50;
private void DrawCurve()
{
for (int j = 0; j < curveCount; j++)
{
for (int i = 1; i <= SEGMENT_COUNT; i++)
{
float t = i / (float)SEGMENT_COUNT;
int nodeIndex = j * 3;
Vector3 pixel = CalculateCubicBezierPoint(
t,
controlPoints[nodeIndex].position,
controlPoints[nodeIndex + 1].position,
controlPoints[nodeIndex + 2].position,
controlPoints[nodeIndex + 3].position);
lineRenderer.positionCount = (((j * SEGMENT_COUNT) + i));
lineRenderer.SetPosition((j * SEGMENT_COUNT) + (i - 1), pixel);
}
}
}
private Vector3 CalculateCubicBezierPoint(float t, Vector3 start, Vector3 modA, Vector3 modB, Vector3 end)
{
float u = 1 - t;
float t2 = Mathf.Pow(t, 2);
float u2 = Mathf.Pow(u, 2);
float t3 = Mathf.Pow(t, 3);
float u3 = Mathf.Pow(u, 3);
Vector3 p = u3 * start;
p += 3 * u2 * t * modA;
p += 3 * u * t2 * modB;
p += t3 * end;
return p;
}
You should use AnimationCurve.
you can edit the "graphic curve" in the inspector (public variable AnimationCurve) then use this srcipt to move object along the path.
using UnityEngine;
using System.Collections;
public class AnimationPath : MonoBehaviour
{
public AnimationCurve XCurve;
public float TotalTravelTime = 5.0f;
public float TravelSpeed = 50.0f;
public float XRange = 10.0f;
// Use this for initialization
void Start ()
{
StartCoroutine("Travel");
}
IEnumerator Travel()
{
float ElapsedTime = 0.0f;
while(ElapsedTime < TotalTravelTime)
{
float XPos = XCurve.Evaluate(ElapsedTime/TotalTravelTime) * XRange;
transform.position = new Vector3(XPos, transform.position.y, transform.position.z + TravelSpeed * -Time.deltaTime);
yield return null;
ElapsedTime += Time.deltaTime;
}
}
}
I hope this can help you.

AnimationCurve.Evaluate - Get time by value

Is there a build-in way how to get a time by value from Animation curve in Unity3d? (The opposite way of Evaluate)
I need to achieve this (instead of getting value from time):
float time = AnimationCurve.Evaluate(float value);
Generically speaking getting X value from Y value.
I know this is 3 years old, but I found via a Google search, and in case someone else lands here:
I simply create an inverse curve, which allows me to look up by time.
public AnimationCurve speedCurve;
private AnimationCurve inverseSpeedCurve;
private void Start()
{
//create inverse speedcurve
inverseSpeedCurve = new AnimationCurve();
for (int i = 0; i < speedCurve.length; i++)
{
Keyframe inverseKey = new Keyframe(speedCurve.keys[i].value, speedCurve.keys[i].time);
inverseSpeedCurve.AddKey(inverseKey);
}
}
Just a basic implementation maybe it will give an idea for you. Method loops through all time and if your value is near that value at that time it will yield. It's a coroutine but you can change it to use inside Update maybe?
public AnimationCurve curve;
public float valToTime = .5f;
public float treshold = .005f;
public float yourTime;
IEnumerator valueToTime(float determineTime)
{
float timeCounter = 0;
Keyframe[] k = curve.keys;
float endTime = k[k.Length-1].time;
Debug.Log("end "+endTime);
while(timeCounter < endTime)
{
float val = curve.Evaluate(timeCounter);
Debug.Log("val "+ val + " time "+timeCounter);
// have to find a better solution for treshold sometimes it misses(use Update?)!
if(Mathf.Abs(val - determineTime) < treshold)
{
//Your time would be this
yourTime = timeCounter;
yield break;
}
else
{
//If it's -1 than a problem occured, try changing treshold
yourTime = -1f;
}
timeCounter += Time.deltaTime;
yield return null;
}
}
Putting together the best elements of most of the solutions posted, I've come up with an approach that produces pretty high accuracy. It involves doing the work upfront and so, is also quite efficient.
Note: If the original curve possesses any maximum/minimum point (points on the curve with a gradient of zero) this method will still attempt to invert it but can only do so by introducing several discontinuities to the inverted curve. It is not ideal for such cases.
Evaluate the original curve at several "sample-points" using a "sample-delta" constant.
For each "value" evaluated, compute the tangent at that point as the "sample-delta" / "value-delta".
Create keyframes that use the "value" as the "time" and the "sample-point" as the "value", and set the "inTangent" and "outTangent" to the tangent obtained in Step 3.
Add the keyframe generated at every "sample-point" to a new AnimationCurve().
The new AnimationCurve() is therefore an inverted version of the original.
Smooth the tangents of the new AnimationCurve() (the inverted version) to remove discontinuities caused by sudden and rapid tangent changes. NB: Smoothing the tangents may make the inverted curve lose it's general definition if the original curve had at least one maximum/minimum point.
Image of Normal Curve vs Inverted Curve:
invertedCurve = new AnimationCurve();
float totalTime = normalCurve.keys[normalCurve.length - 1].time;
float sampleX = 0; //The "sample-point"
float deltaX = 0.01f; //The "sample-delta"
float lastY = normalCurve.Evaluate(sampleX);
while (sampleX < totalTime)
{
float y = normalCurve.Evaluate(sampleX); //The "value"
float deltaY = y - lastY; //The "value-delta"
float tangent = deltaX / deltaY;
Keyframe invertedKey = new Keyframe(y, sampleX, tangent, tangent);
invertedCurve.AddKey(invertedKey);
sampleX += deltaX;
lastY = y;
}
for(int i = 0; i < invertedCurve.length; i++)
{
invertedCurve.SmoothTangents(i, 0.1f);
}
I needed this very thing just now, so I came up with this. I found it quite accurate and fast (an accuracy value of 10 was enough, and even lower may have done). But it will only work on curves that have ONE definite time for each value (i.e. nothing like waves with multiple times having the same value).
Similar to the other answer, it iterates through possible times - but rather than in a linear fashion the step value starts as the entire time range and halves each time.
Hope it's useful for you.
// NB. Will only work for curves with one definite time for each value
public float GetCurveTimeForValue( AnimationCurve curveToCheck, float value, int accuracy ) {
float startTime = curveToCheck.keys [0].time;
float endTime = curveToCheck.keys [curveToCheck.length - 1].time;
float nearestTime = startTime;
float step = endTime - startTime;
for (int i = 0; i < accuracy; i++) {
float valueAtNearestTime = curveToCheck.Evaluate (nearestTime);
float distanceToValueAtNearestTime = Mathf.Abs (value - valueAtNearestTime);
float timeToCompare = nearestTime + step;
float valueAtTimeToCompare = curveToCheck.Evaluate (timeToCompare);
float distanceToValueAtTimeToCompare = Mathf.Abs (value - valueAtTimeToCompare);
if (distanceToValueAtTimeToCompare < distanceToValueAtNearestTime) {
nearestTime = timeToCompare;
valueAtNearestTime = valueAtTimeToCompare;
}
step = Mathf.Abs(step * 0.5f) * Mathf.Sign(value-valueAtNearestTime);
}
return nearestTime;
}
just stumbled upon this problem myself and didn't like the solutions mentioned here, so i wanted to share my own. It's rather an adaption to the answer which inverts the keyframes.
I improved it by also inverting the tangents and the weight of the points.
I'm sure there is an easier way, but i found this working nicely for reversing the animationcurve.
Edit: Forgot to mention, for me it only worked when the tangents are set to weighted, i don't know what weight calculation unity does when you set it to auto or similar, so weighted was predicatable and easy to inverse.
inverseCurve = new AnimationCurve();
for (int i = 0; i < initialCurve.length; i++)
{
float inWeight = (initialCurve.keys[i].inTangent * initialCurve.keys[i].inWeight) / 1;
float outWeight = (initialCurve.keys[i].outTangent * initialCurve.keys[i].outWeight) / 1;
Keyframe inverseKey = new Keyframe(initialCurve.keys[i].value, initialCurve.keys[i].time, 1/initialCurve.keys[i].inTangent, 1/initialCurve.keys[i].outTangent, inWeight, outWeight);
inverseCurve.AddKey(inverseKey);
}
Thought I'd share my own version, as suggested in other forums too I tried looping over Evaluate() instead of reversing the whole curve which I think is overkill and not always feasible.
This checks for a value approximation down to the indicated decimals, it also assumes that the curve has "normalized" time (if it wasn't the case this could be expanded by looking for the smallest and the biggest time keys.
/// <summary>
/// Inverse of Evaluate()
/// </summary>
/// <param name="curve">normalized AnimationCurve (time goes from 0 to 1)</param>
/// <param name="value">value to search</param>
/// <returns>time at which we have the closest value not exceeding it</returns>
public static float EvaluateTime(this AnimationCurve curve, float value, int decimals = 6) {
// Retrieve the closest decimal and then go down
float time = 0.1f;
float step = 0.1f;
float evaluate = curve.Evaluate(time);
while(decimals > 0) {
// Loop until we pass our value
while(evaluate < value) {
time += step;
evaluate = curve.Evaluate(time);
}
// Go one step back and increase precision of the step by one decimal
time -= step;
evaluate = curve.Evaluate(time);
step /= 10f;
decimals--;
}
return time;
}

How can I find distance traveled with a gyroscope and accelerometer?

I want to build an app that calculates accurate Distance travelled by iPhone (not long distance) using Gyro+Accelerometer. No need for GPS here.
How should I approach this problem?
Basic calculus behind this problem is in the expression
(and similar expressions for displacements in y and z) and basic geometry is the Pythagorean theorem
So, once you have your accelerometer signals passed through a low-pass filter and binned in time with sampling interval dt, you can find the displacement in x as (pardon my C...)
float dx=0.0f;
float vx=0.0f;
for (int i=1; i<n; i++)
{
vx+=(acceleration_x[i-1] + acceleration_x[i])/2.0f*dt;
dx+=vx*dt;
}
and similarly for dy and dz. Here
float acceleration_x[n];
contains x-acceleration values from start to end of measurement at times 0, dt, 2*dt, 3*dt, ... (n-1)*dt.
To find the total displacement, you just do
dl=sqrt(dx*dx + dy*dy + dz*dz);
Gyroscope is not necessary for this, but if you are measuring linear distances, you can use the gyroscope reading to control that rotation of the device was not too large. If rotation was too strong, make the user re-do the measurement.
You get position by integrating the linear acceleration twice but the error is horrible. It is useless in practice.
Here is an explanation why (Google Tech Talk) at 23:20. I highly recommend this video.
Similar questions:
track small movements of iphone with no GPS
What is the real world accuracy of phone accelerometers when used for positioning?
how to calculate phone's movement in the vertical direction from rest?
iOS: Movement Precision in 3D Space
How to use Accelerometer to measure distance for Android Application Development
Distance moved by Accelerometer
Update (24 Feb 2013): #Simon Yes, if you know more about the movement, for example a person walking and the sensor is on his foot, then you can do a lot more. These are called
domain specific assumptions.
They break miserably if the assumptions do not hold and can be quite cumbersome to implement. Nevertheless, if they work, you can do fun things. See the links in my answer Android accelerometer accuracy (Inertial navigation) at indoor positioning.
You should use the Core Motion interface like described in Simple iPhone motion detect. Especially all rotations can be tracked very accurately. If you plan to do something related to linear movements this is very hard stuff. Have a look at Getting displacement from accelerometer data with Core Motion.
I took a crack at this and gave up (late at night, didn't seem to be getting anywhere). This is for a Unity3d project.
If anyone wants to pick up where I left off, I would be happy to elaborate on what all this stuff does.
Basically after some of what turned out to be false positives, I thought I'd try and filter this using a low pass filter, then attempted to remove bounces by finding a trend, then (acc_x[i-1]+acc_x[i])/2.
It looks like the false positive is still coming from the tilt, which I attempted to remove..
If this code is useful or leads you someplace, please let me know!
using UnityEngine;
using System.Collections.Generic;
/// <summary>
/// rbi.noli#gmail.com
/// </summary>
public class AccelerometerInput : MonoBehaviour
{
Transform myTransform;
Gyroscope gyro;
GyroCam gyroCam;
void Awake()
{
gyroCam= FindObjectOfType<GyroCam> ();
myTransform = transform;
if (SystemInfo.supportsGyroscope) {
gyro = Input.gyro;
gyro.enabled = true;
}
}
bool shouldBeInitialized = false;
void Update ()
{
transform.Translate (GetAccelerometer ());// * Time.deltaTime * speed);
//GetComponent<Rigidbody> ().AddForce (GetAccelerometer ());
}
public float speed = 10.0F;
public Vector3 dir;
public float f;
Vector3 GetAccelerometer()
{
dir = Input.acceleration;
dir.x *= gyro.attitude.x;
dir.z *= gyro.attitude.z;
if (Mathf.Abs (dir.x) < .001f)
dir.x = 0;
dir.y = 0;
if (Mathf.Abs (dir.z) < .001f)
dir.z = 0;
RecordPointsForFilter (dir);
//print ("Direction : " + dir.ToString("F7"));
return TestPointsForVelocity();
}
Vector3[] points = new Vector3[20];
int index;
void RecordPointsForFilter(Vector3 recentPoint)
{
if (index >= 20)
index = 0;
points [index] = EvaluateTrend (recentPoint);;
index++;
}
//try to remove bounces
float xTrend = 0;
float zTrend = 0;
float lastTrendyX = 0;
float lastTrendyZ = 0;
Vector3 EvaluateTrend(Vector3 recentPoint)
{
//if the last few points were positive, and this point is negative, don't pass it along
//accumulate points into a trend
if (recentPoint.x > 0)
xTrend += .01f;
else
xTrend -= .1f;
if (recentPoint.z > 0)
zTrend += .1f;
else
zTrend -= .1f;
//if point matches trend, keep it
if (xTrend > 0) {
if (recentPoint.x > 0)
lastTrendyX = recentPoint.x;
} else // xTrend < 0
if (recentPoint.x < 0)
lastTrendyX = recentPoint.x;
if (zTrend > 0) {
if (recentPoint.z > 0)
lastTrendyZ = recentPoint.z;
} else // xTrend < 0
if (recentPoint.z < 0)
lastTrendyZ = recentPoint.z;
return new Vector3( lastTrendyX, 0, lastTrendyZ);
}
Vector3 TestPointsForVelocity()
{
float x = 0;
float z = 0;
float xAcc = 0;
float zAcc = 0;
int successfulHits = 0;
for(int i = 0; i < points.Length; i++)
{
if(points[i]!=null)
{
successfulHits ++;
xAcc += points[i].x;
zAcc += points[i].z;
}
}
x = xAcc / successfulHits;
z = zAcc / successfulHits;
return new Vector3 (x, 0, z);
}
}
(acc_x[i-1]+acc_x[i])/2 is a low pass filter, it is the mean value between two measures in time
also look at here : http://www.freescale.com/files/sensors/doc/app_note/AN3397.pdf
pag :3
Navisens.
https://navisens.com/#how-work
Here the claim - Navisens patent-pending technology processes accelerometer and gyroscope data in a unique way to locate your phone.
Tried out the demo application, which works mostly in mapping the movements with out Location Services or WiFi once the inital location & direction are set.
iOS SDK - https://github.com/navisens/iOS-SDK
Android SDK - https://github.com/navisens/Android-SDK
Note: This is not open source
Here is the answer. Somebody asked before.
There is an app called RangeFinder doing the same thing ( available in App Store ) .