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))
Related
How do I calculate the distance of a game object (inside a cube collider) from the cube collider surface? The existing calculations were made from the cube surface outwards so I got 0 when I used the collider.closestpoint or collider.closestpointonbounds.
The simplest (but computationally not the cheapest) would be to not rely on your current collider for the distance, but to add a set of small colliders around the edge of the object (so 6 colliders, one per face of the cube). Using Collider.ClosestPoint() on all 6 faces and calculating the distance like that would give you the results you need.
First convert a point to local space.
var localPoint = transform.InverseTransformPoint(worldPoint);
var extents = collider.size * 0.5f;
var closestPoint = localPoint;
Compute the distance to each face.
var disx = extents.x - Mathf.Abs(localPoint.x);
var disy = extents.y - Mathf.Abs(localPoint.y);
var disz = extents.z - Mathf.Abs(localPoint.z);
Find the closest face (smallest distance) and move the closest point along this axis.
if(disx < disy)
{
if (disx < disz)
closestPoint.x = extents.x * Mathf.Sign(localPoint.x); //disx
else
closestPoint.z = extents.z * Mathf.Sign(localPoint.z); //disz
}
else
{
//......
}
Plus the offset of the collider, convert to world space.
closestPoint += collider.center;
transform.TransformPoint(closestPoint);
I don't know how efficient this is, but here is how I solved it:
public static Vector3 ClosetPointOnBounds(Vector3 point, Bounds bounds)
{
Plane top = new Plane(Vector3.up, bounds.max);
Plane bottom = new Plane(Vector3.down, bounds.min);
Plane front = new Plane(Vector3.forward, bounds.max);
Plane back = new Plane(Vector3.back, bounds.min);
Plane right = new Plane(Vector3.right, bounds.max);
Plane left = new Plane(Vector3.left, bounds.min);
Vector3 topclose = top.ClosestPointOnPlane(point);
Vector3 botclose = bottom.ClosestPointOnPlane(point);
Vector3 frontclose = front.ClosestPointOnPlane(point);
Vector3 backclose = back.ClosestPointOnPlane(point);
Vector3 rightclose = right.ClosestPointOnPlane(point);
Vector3 leftclose = left.ClosestPointOnPlane(point);
Vector3 closest = point;
float bestdist = float.MaxValue;
foreach (Vector3 p in new Vector3[] {
topclose, botclose, frontclose, backclose, leftclose, rightclose
})
{
float dist = Vector3.Distance(p, point);
if (dist < bestdist)
{
bestdist = dist;
closest = p;
}
}
return closest;
}
(note: this assumes and axis-aligned box, which is all I needed at the time. If you want to rotate it you will have to do more work to transform the point.)
You can Calculate by Vector3.Distance
some example
float minDistance =2;
float Distance = Vector3.Distance(other.position, transform.position);
if(Distance < minDistance)
{
//some code stuffs
}
else if(Distance > minDistance){
//some code stuffs
}
Useful information about Vector3.Distance and getting Distance from object
source: https://docs.unity3d.com/ScriptReference/30_search.html?q=Distance
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.
Ok, so, i've been stuck on this for ages. Im working on an AI that will navigate a tank to a waypoint, defined as a Vector3. the position of the tank is also defines as a Vector3, both these have their Y position set to 0, as to ignore terrain elevation, the current rotation of the tank is also a Vector3, though only the Y rotation is needed, as i'm effectively projecting the 3d position onto a 2d navigational grid.
The AI passes anywhere between -1 and 1 into the control for the tank, which then handles the physics operations. so, i need to somehow calculate the angle, positive or negative in relation to the current heading angle of the tank to the position of the waypoint, then send the rotation value to the controls. At the moment I simply cant get it working, I feel like iv'e pretty much tried everything.
This is my code currently, it doesn't work, at all, and is about the 20th revision:
void driveToTarget()
{
Vector3 target0 = driveTarget;
target0.y = 0;
GameObject current0Obj = new GameObject();
Vector3 current0 = this.transform.position;
current0.y = 0;
print(current0);
print(target0);
Vector3 current0Angle = this.transform.eulerAngles;
print(current0Angle.y);
current0Angle.x = 0;
current0Angle.z = 0;
Vector3 heading = target0 - current0;
Quaternion headingAngle = Quaternion.LookRotation(heading);
print("headingAngle" + headingAngle);
print("heading direction, allegidly: " + Quaternion.Euler(heading).ToEulerAngles());
Quaternion distanceToGo = Quaternion.Lerp(Quaternion.Euler(current0Angle), headingAngle, 0.01f);
float angle = Vector3.SignedAngle(current0, target0, Vector3.up);
float difference = Mathf.Abs(angle - current0Angle.y);
print("heading angle " + angle);
if (current0 != driveTarget)
{
steeringVal = Mathf.Abs(1.5f-(1f/Mathf.Abs(distanceToGo.y))) * -Mathf.Sign(distanceToGo.y); ;
throttleVal = 0f;
} else
{
throttleVal = 0;
}
}
--EDIT--
So, I've partially solved it, and now encountered another problem, I've managded to get the tank to detect the angle between itself and the waypoint, BUT, rather than orienting forward towards the waypoint, the right side of the tank orients towards it, so it orbits the waypoint. I actually know why this is, becasue the forward vector of the tank is technically the right vector because of unity's stupid axis ruining my blender import, anyway, heres the updated code:
void driveToTarget()
{
Vector3 target0 = driveTarget;
target0.y = 0;
Vector3 current0 = this.transform.position;
current0.y = 0;
print("Current: " + current0);
print("Target: " + target0);
Vector3 current0Angle = this.transform.rotation.eulerAngles;
print("Curret rotation:" + current0Angle.y);
current0Angle.x = 0;
current0Angle.z = 0;
Vector3 heading = target0 - current0;
Quaternion headingAngle = Quaternion.LookRotation(heading);
print("heading angle: " + headingAngle.ToEuler());
float distanceToGo = (current0Angle.y) - headingAngle.eulerAngles.y;
print("DistanceToGo: " + distanceToGo);
if (current0 != driveTarget)
{
steeringVal = 1 * -Mathf.Sign(distanceToGo);
throttleVal = 0f;
} else
{
throttleVal = 0;
}
Debug.DrawRay(current0, heading, Color.red, 1);
Debug.DrawRay(current0, this.transform.up, Color.red, 1);
}
I'm not sure exactly how your code is setup or how the steering works. You may want to look into using the Unity NavMeshAgent to simplify this.
Regardless here is some code I wrote up that takes a destination and rotates an object towards it. All you'd have to do from there is move the object forwards.
Vector3 nextDestination = //destination;
Vector3 direction = nextDestination - transform.position;
direction = new Vector3(direction.x, 0, direction.z);
var newRotation = Quaternion.LookRotation(direction);
var finalRotation = Quaternion.Slerp(transform.rotation, newRotation, Time.deltaTime); //smoothes out rotation
transform.rotation = finalRotation;
Sorry if this isn't what you needed. Have you been able to figure out which part of the code is behaving unexpectedly from your print statements?
I have made some code that should make my player move into the direction of the finger:
if (Input.touchCount == 1 && Input.GetTouch(0).phase == TouchPhase.Moved)
{
Vector2 pos = Camera.main.ScreenToWorldPoint(new Vector3(Input.GetTouch(0).position.x, Input.GetTouch(0).position.y, 0));
if (pos.x < rb.position.x)
{
movehorizontal = -1;
}
if(pos.x > rb.position.x)
{
movehorizontal = 1;
}
if (pos.y < rb.position.z)
{
movevertical = -1;
}
if(pos.y > rb.position.z)
{
movevertical = 1;
}
}
Vector3 movement = new Vector3(movehorizontal, 0.00f, movevertical)*speed;
It's a 3D game, with a top-view, so my player starts at 0,0,0 and only moves along the x and z axis. My camera is positionated on 0,10,3. The following works on the x axis, so when my finger touches on the right side it goes to the right, if on the left to the left, but no matter where I touch it, it will only move to the front and not to the bottom of my screen.
I tried debugging, but the instructions werent working at the time.
screentoWorldPoint should be stored as a vector3. also since the camera is 10 units away from your plane the last parameter should be 10.
edit, that will only work for a cam pointing straight down. this code should work regardless of the camera angle.
Vector3 pos = Camera.main.ScreenToWorldPoint(new Vector3(Input.GetTouch(0).position.x, Input.GetTouch(0).position.y, 1f));
Vector3 pointDelta = pos - Camera.main.transform.position;
float multiplier = -Camera.main.transform.position.y / pointDelta.y;
pos = Camera.main.transform.position + pointDelta * multiplier;
finally these lines should compare the z values to each other
if (pos.z < rb.position.z)
if(pos.z > rb.position.z)
make those changes and let us know if any other problems still exist
My game has a drawing tool - a looping line renderer that is used as a marker to manipulate an area of the terrain in the shape of the line. This all happens in runtime as soon as the player stops drawing the line.
So far I have managed to raise terrain verteces that match the coordinates of the line renderer's points, but I have difficulties with raising the points that fall inside the marker's shape. Here is an image describing what I currently have:
I tried using the "Polygon Fill Algorithm" (http://alienryderflex.com/polygon_fill/), but raising the terrain vertices one line at a time is too resourceful (even when the algorithm is narrowed to a rectangle that surrounds only the marked area). Also my marker's outline points have gaps between them, meaning I need to add a radius to the line that raises the terrain, but that might leave the result sloppy.
Maybe I should discard the drawing mechanism and use a mesh with a mesh collider as the marker?
Any ideas are appreciated on how to get the terrain manipulated in the exact shape as the marker.
Current code:
I used this script to create the line - the first and the last line points have the same coordinates.
The code used to manipulate the terrain manipulation is currently triggered when clicking a GUI button:
using System;
using System.Collections;
using UnityEngine;
public class changeTerrainHeight_lineMarker : MonoBehaviour
{
public Terrain TerrainMain;
public LineRenderer line;
void OnGUI()
{
//Get the terrain heightmap width and height.
int xRes = TerrainMain.terrainData.heightmapWidth;
int yRes = TerrainMain.terrainData.heightmapHeight;
//GetHeights - gets the heightmap points of the tarrain. Store them in array
float[,] heights = TerrainMain.terrainData.GetHeights(0, 0, xRes, yRes);
if (GUI.Button(new Rect(30, 30, 200, 30), "Line points"))
{
/* Set the positions to array "positions" */
Vector3[] positions = new Vector3[line.positionCount];
line.GetPositions(positions);
/* use this height to the affected terrain verteces */
float height = 0.05f;
for (int i = 0; i < line.positionCount; i++)
{
/* Assign height data */
heights[Mathf.RoundToInt(positions[i].z), Mathf.RoundToInt(positions[i].x)] = height;
}
//SetHeights to change the terrain height.
TerrainMain.terrainData.SetHeights(0, 0, heights);
}
}
}
Got to the solution thanks to Siim's personal help, and thanks to the article: How can I determine whether a 2D Point is within a Polygon?.
The end result is visualized here:
First the code, then the explanation:
using System;
using System.Collections;
using UnityEngine;
public class changeTerrainHeight_lineMarker : MonoBehaviour
{
public Terrain TerrainMain;
public LineRenderer line;
void OnGUI()
{
//Get the terrain heightmap width and height.
int xRes = TerrainMain.terrainData.heightmapWidth;
int yRes = TerrainMain.terrainData.heightmapHeight;
//GetHeights - gets the heightmap points of the tarrain. Store them in array
float[,] heights = TerrainMain.terrainData.GetHeights(0, 0, xRes, yRes);
//Trigger line area raiser
if (GUI.Button(new Rect(30, 30, 200, 30), "Line fill"))
{
/* Set the positions to array "positions" */
Vector3[] positions = new Vector3[line.positionCount];
line.GetPositions(positions);
float height = 0.10f; // define the height of the affected verteces of the terrain
/* Find the reactangle the shape is in! The sides of the rectangle are based on the most-top, -right, -bottom and -left vertex. */
float ftop = float.NegativeInfinity;
float fright = float.NegativeInfinity;
float fbottom = Mathf.Infinity;
float fleft = Mathf.Infinity;
for (int i = 0; i < line.positionCount; i++)
{
//find the outmost points
if (ftop < positions[i].z)
{
ftop = positions[i].z;
}
if (fright < positions[i].x)
{
fright = positions[i].x;
}
if (fbottom > positions[i].z)
{
fbottom = positions[i].z;
}
if (fleft > positions[i].x)
{
fleft = positions[i].x;
}
}
int top = Mathf.RoundToInt(ftop);
int right = Mathf.RoundToInt(fright);
int bottom = Mathf.RoundToInt(fbottom);
int left = Mathf.RoundToInt(fleft);
int terrainXmax = right - left; // the rightmost edge of the terrain
int terrainZmax = top - bottom; // the topmost edge of the terrain
float[,] shapeHeights = TerrainMain.terrainData.GetHeights(left, bottom, terrainXmax, terrainZmax);
Vector2 point; //Create a point Vector2 point to match the shape
/* Loop through all points in the rectangle surrounding the shape */
for (int i = 0; i < terrainZmax; i++)
{
point.y = i + bottom; //Add off set to the element so it matches the position of the line
for (int j = 0; j < terrainXmax; j++)
{
point.x = j + left; //Add off set to the element so it matches the position of the line
if (InsidePolygon(point, bottom))
{
shapeHeights[i, j] = height; // set the height value to the terrain vertex
}
}
}
//SetHeights to change the terrain height.
TerrainMain.terrainData.SetHeightsDelayLOD(left, bottom, shapeHeights);
TerrainMain.ApplyDelayedHeightmapModification();
}
}
//Checks if the given vertex is inside the the shape.
bool InsidePolygon(Vector2 p, int terrainZmax)
{
// Assign the points that define the outline of the shape
Vector3[] positions = new Vector3[line.positionCount];
line.GetPositions(positions);
int count = 0;
Vector2 p1, p2;
int n = positions.Length;
// Find the lines that define the shape
for (int i = 0; i < n; i++)
{
p1.y = positions[i].z;// - p.y;
p1.x = positions[i].x;// - p.x;
if (i != n - 1)
{
p2.y = positions[(i + 1)].z;// - p.y;
p2.x = positions[(i + 1)].x;// - p.x;
}
else
{
p2.y = positions[0].z;// - p.y;
p2.x = positions[0].x;// - p.x;
}
// check if the given point p intersects with the lines that form the outline of the shape.
if (LinesIntersect(p1, p2, p, terrainZmax))
{
count++;
}
}
// the point is inside the shape when the number of line intersections is an odd number
if (count % 2 == 1)
{
return true;
}
else
{
return false;
}
}
// Function that checks if two lines intersect with each other
bool LinesIntersect(Vector2 A, Vector2 B, Vector2 C, int terrainZmax)
{
Vector2 D = new Vector2(C.x, terrainZmax);
Vector2 CmP = new Vector2(C.x - A.x, C.y - A.y);
Vector2 r = new Vector2(B.x - A.x, B.y - A.y);
Vector2 s = new Vector2(D.x - C.x, D.y - C.y);
float CmPxr = CmP.x * r.y - CmP.y * r.x;
float CmPxs = CmP.x * s.y - CmP.y * s.x;
float rxs = r.x * s.y - r.y * s.x;
if (CmPxr == 0f)
{
// Lines are collinear, and so intersect if they have any overlap
return ((C.x - A.x < 0f) != (C.x - B.x < 0f))
|| ((C.y - A.y < 0f) != (C.y - B.y < 0f));
}
if (rxs == 0f)
return false; // Lines are parallel.
float rxsr = 1f / rxs;
float t = CmPxs * rxsr;
float u = CmPxr * rxsr;
return (t >= 0f) && (t <= 1f) && (u >= 0f) && (u <= 1f);
}
}
The used method is filling the shape one line at a time - "The Ray Casting method". It turns out that this method starts taking more resources only if the given shape as a lot of sides. (A side of the shape is a line that connects two points in the outline of the shape.)
When I posted this question, my Line Renderer had 134 points defining the line. This also means the shape has the same number of sides that needs to pass the ray cast check.
When I narrowed down the number of points to 42, the method got fast enough, and also the shape did not lose almost any detail.
Furthermore I am planning on using some methods to make the contours smoother, so the shape can be defined with even less points.
In short, you need these steps to get to the result:
Create the outline of the shape;
Find the 4 points that mark the bounding box around the shape;
Start ray casting the box;
Check the number of how many times the ray intersects with the sides of the shape. The points with the odd number are located inside the shape:
Assign your attributes to all of the points that were found in the shape.