How to compare between two lines? - c#

I have a code that allows me to draw lines and limit the number of lines that can be drawn.
My problem is that I want to create a line (with for example line renderer)
and then allow the user to try drawing a similar (not necessarily exactly the same) line and the code needs to know according to the setting if the line is similar enough or not, but I can't figure it.
I would appreciate any tips.
public class DrawLine : MonoBehaviour
{
public GameObject linePrefab;
public GameObject currentLine;
public LineRenderer lineRenderer;
public EdgeCollider2D edgeCollider;
public List<Vector2> fingerPositions;
public Button[] answers;
public bool isCurrButtonActive;
int mouseButtonState = 0;
void Update()
{
Debug.Log(rfgrhe);
if (isCurrButtonActive)
{
if (Input.GetMouseButtonDown(0))
{
if (mouseButtonState == 0)
{
CreateLine();
}
}
if (Input.GetMouseButtonUp(0))
{
mouseButtonState++;
}
if (Input.GetMouseButton(0))
{
if (mouseButtonState == 1)
{
Debug.Log(Input.mousePosition.ToString());
if (Input.mousePosition.x < 100 || Input.mousePosition.y > 420 || Input.mousePosition.x > 660 || Input.mousePosition.y < 7)
{
return;
}
Vector2 tempFingerPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
if (Vector2.Distance(tempFingerPos, fingerPositions[fingerPositions.Count - 1]) > .1f)
{
UpdateLine(tempFingerPos);
}
}
}
}
}
void CreateLine()
{
mouseButtonState++;
currentLine = Instantiate(linePrefab, Vector3.zero, Quaternion.identity);
lineRenderer = currentLine.GetComponent<LineRenderer>();
edgeCollider = currentLine.GetComponent<EdgeCollider2D>();
fingerPositions.Clear();
fingerPositions.Add(Camera.main.ScreenToWorldPoint(Input.mousePosition));
fingerPositions.Add(Camera.main.ScreenToWorldPoint(Input.mousePosition));
lineRenderer.SetPosition(0, fingerPositions[0]);
lineRenderer.SetPosition(1, fingerPositions[1]);
edgeCollider.points = fingerPositions.ToArray();
}
void UpdateLine(Vector2 newFingerPos)
{
fingerPositions.Add(newFingerPos);
lineRenderer.positionCount++;
lineRenderer.SetPosition(lineRenderer.positionCount - 1, newFingerPos);
edgeCollider.points = fingerPositions.ToArray();
}
public void ActivateCurrentButton()
{
// Debug.Log(isCurrButtonActive);
isCurrButtonActive = true;
for (int i = 0; i < answers.Length; i++)
{
if (answers[i].CompareTag("onePoint"))
{
answers[i].GetComponent<MapLvl>().isCurrButtonActive = false;
}
else if (answers[i].CompareTag("TwoPoints"))
{
answers[i].GetComponent<DrawLine>().isCurrButtonActive = false;
}
}
}
}
For example in that case, the blue line is the correct one, the green and the red ones are two options of an answer from the user.
What I want is that the program will acknolage only the green line as a correct answer.

EDIT: Since it's clearer now what we want, here's a way to achieve it:
The function float DifferenceBetweenLines(Vector3[], Vector3[]) below gives you a measure of the "distance between the two lines".
It walks along the line to match with a maximum step length, and for each point, computes the distance from the closest point on the draw line.
It sums the squares of those distances and divide them by the length of the line to match (don't ask me to explain this with mathematical rigor).
The smaller the return value, the closer the first line matches the second -- the threshold is yours to decide.
float DifferenceBetweenLines(Vector3[] drawn, Vector3[] toMatch) {
float sqrDistAcc = 0f;
float length = 0f;
Vector3 prevPoint = toMatch[0];
foreach (var toMatchPoint in WalkAlongLine(toMatch)) {
sqrDistAcc += SqrDistanceToLine(drawn, toMatchPoint);
length += Vector3.Distance(toMatchPoint, prevPoint);
prevPoint = toMatchPoint;
}
return sqrDistAcc / length;
}
/// <summary>
/// Move a point from the beginning of the line to its end using a maximum step, yielding the point at each step.
/// </summary>
IEnumerable<Vector3> WalkAlongLine(IEnumerable<Vector3> line, float maxStep = .1f) {
using (var lineEnum = line.GetEnumerator()) {
if (!lineEnum.MoveNext())
yield break;
var pos = lineEnum.Current;
while (lineEnum.MoveNext()) {
Debug.Log(lineEnum.Current);
var target = lineEnum.Current;
while (pos != target) {
yield return pos = Vector3.MoveTowards(pos, target, maxStep);
}
}
}
}
static float SqrDistanceToLine(Vector3[] line, Vector3 point) {
return ListSegments(line)
.Select(seg => SqrDistanceToSegment(seg.a, seg.b, point))
.Min();
}
static float SqrDistanceToSegment(Vector3 linePoint1, Vector3 linePoint2, Vector3 point) {
var projected = ProjectPointOnLineSegment(linePoint1, linePoint1, point);
return (projected - point).sqrMagnitude;
}
/// <summary>
/// Outputs each position of the line (but the last) and the consecutive one wrapped in a Segment.
/// Example: a, b, c, d --> (a, b), (b, c), (c, d)
/// </summary>
static IEnumerable<Segment> ListSegments(IEnumerable<Vector3> line) {
using (var pt1 = line.GetEnumerator())
using (var pt2 = line.GetEnumerator()) {
pt2.MoveNext();
while (pt2.MoveNext()) {
pt1.MoveNext();
yield return new Segment { a = pt1.Current, b = pt2.Current };
}
}
}
struct Segment {
public Vector3 a;
public Vector3 b;
}
//This function finds out on which side of a line segment the point is located.
//The point is assumed to be on a line created by linePoint1 and linePoint2. If the point is not on
//the line segment, project it on the line using ProjectPointOnLine() first.
//Returns 0 if point is on the line segment.
//Returns 1 if point is outside of the line segment and located on the side of linePoint1.
//Returns 2 if point is outside of the line segment and located on the side of linePoint2.
static int PointOnWhichSideOfLineSegment(Vector3 linePoint1, Vector3 linePoint2, Vector3 point){
Vector3 lineVec = linePoint2 - linePoint1;
Vector3 pointVec = point - linePoint1;
if (Vector3.Dot(pointVec, lineVec) > 0) {
return pointVec.magnitude <= lineVec.magnitude ? 0 : 2;
} else {
return 1;
}
}
//This function returns a point which is a projection from a point to a line.
//The line is regarded infinite. If the line is finite, use ProjectPointOnLineSegment() instead.
static Vector3 ProjectPointOnLine(Vector3 linePoint, Vector3 lineVec, Vector3 point){
//get vector from point on line to point in space
Vector3 linePointToPoint = point - linePoint;
float t = Vector3.Dot(linePointToPoint, lineVec);
return linePoint + lineVec * t;
}
//This function returns a point which is a projection from a point to a line segment.
//If the projected point lies outside of the line segment, the projected point will
//be clamped to the appropriate line edge.
//If the line is infinite instead of a segment, use ProjectPointOnLine() instead.
static Vector3 ProjectPointOnLineSegment(Vector3 linePoint1, Vector3 linePoint2, Vector3 point){
Vector3 vector = linePoint2 - linePoint1;
Vector3 projectedPoint = ProjectPointOnLine(linePoint1, vector.normalized, point);
switch (PointOnWhichSideOfLineSegment(linePoint1, linePoint2, projectedPoint)) {
case 0:
return projectedPoint;
case 1:
return linePoint1;
case 2:
return linePoint2;
default:
//output is invalid
return Vector3.zero;
}
}
The math functions at the end are from 3d Math Functions - Unify Community Wiki
Here is how it can be used to compare a LineRenderer against another:
Array.Resize(ref lineBuffer1, lineRenderer1.positionCount);
Array.Resize(ref lineBuffer2, lineRenderer2.positionCount);
lineRenderer1.GetPositions(lineBuffer1);
lineRenderer2.GetPositions(lineBuffer2);
float diff = DifferenceBetweenLines(lineBuffer1, lineBuffer2);
const float threshold = 5f;
Debug.Log(diff < threshold ? "Pretty close!" : "Not that close...");
A few things to consider:
The performance of SqrDistanceToLine could definitely be improved on
You get a measure of how close the first line matches the second, not the other way around -- that is, the first line can be longer or go for a walk mid-way as long as it comes back on track and "covers" the other line closely enough. You can solve this by calling DifferenceBetweenLines a second time, swapping the arguments, and taking the biggest result of them two.
We could work with Vector2 instead of Vector3
Original answer:
Similar?
As #Jonathan pointed out, you need to be more precise about "similar enough":
does similarity in size matter ?
does orientation matter ?
do similarity in proportions matter (or only the "changes of direction" of the line) ?
...
As you might guess, the fewer of those criteria matter, the harder it will be; because
your concept of similarity will become more and more abstract from the raw positions
you've got in the first place.
For example, if the user needs to draw a cross, with exactly two strokes,
that cover more or less a defined area, the task is as easy as it gets:
You can measure the distance between the area's corners and each stroke's first
and last points, and check that the lines are kind of straight.
If you want to check if the user drew a perfect heart-shape, in any orientation,
it's noticeably trickier...
You might have to resort to a specialized library for that.
Another thing to consider is, does the user really need to make a line similar to another one,
or should it only be close enough that it can be differentiated from other possible lines?
Consider this example:
The user needs to draw either a cross (X) or a circle (O):
If there is only one stroke that comes back close to the starting point, assume a circle.
If there is strokes whose general directions are orthogonal, assume a cross.
In this case, a more involved system would probably be overkill.
A few "raw pointers"
Assuming simple requirements (because assuming the opposite, I wouldn't be able to help much),
here are a few elements:
Exact match
The user has to draw on top of a visible line: this is the easiest scenario.
For each point of his line, find out the distance from the closest point on the reference line.
Sum the square of those distances -- for some reason it works better than summing the distances
themselves, and it's also cheaper to compute the square distance directly.
LineRenderer.Simplify
Very specific to your use-case, since you're using Unity's LineRenderer,
it's worth knowing that it packs a Simplify(float) method, that decreases the
resolution of your curve, making it easier to process, and particularly effective
if the line to match is made of somewhat straight segments (not complex curves).
Use the angles
Sometimes you'll want to check the angles between the different sub-segments of your line,
instead of their (relative) lengths. It will measure changes in direction regardless
of the proportions, which can be more intuitive.
Example
A simple example that detects equilateral triangles:
LineRenderer.Simplify
close the shape if the ends are close enough
check the angles are ~60deg each:
For arbitrary lines, you could run the line to match through the same "filter" as the lines the user draws, and compare the values. It will be yours to decide what properties matter most (angles/distances/proportions...), and what's the threshold.

Personally I would take points along the users line and then figure out the angles on the lines and if the average angle is within a specific range then it is acceptable. If the points you draw angles from are close enough together then you should have a pretty accurate idea whether the user is close to the same line.
Also, if the line needs to be in a particular area then you can just check and make sure the line is within a specified distance of the "control" line. The math for these should be pretty simple once you have the points. I am sure there are many other ways to implement this, but I personally would do this. Hope this helps!

Related

Unity find objects within range of two angles and with a max length (pie slice)

I've been programming an ability for a Hack n Slash which needs to check Units within a pie slice (or inbetween two angles with max length). But I'm stuck on how to check whether an unit is within the arc.
Scenario (Not enough, rep for an image sorry im new)
I currently use Physics2D.OverlapSphere() to get all of the objects within the maximum range. I then loop through all of the found objects to see whether they are within the two angles I specify. Yet this has janky results, probably because angles don't like negative values and value above 360.
How could I make this work or is there a better way to do this?
I probably need to change the way I check whether the angle is within the bounds.
Thanks in advance guys! I might respond with some delay as I won't be at my laptop for a couple hours.
Here is the code snippet:
public static List<EntityBase> GetEntitiesInArc(Vector2 startPosition, float angle, float angularWidth, float radius)
{
var colliders = Physics2D.OverlapCircleAll(startPosition, radius, 1 << LayerMask.NameToLayer("Entity"));
var targetList = new List<EntityBase>();
var left = angle - angularWidth / 2f;
var right = angle + angularWidth / 2f;
foreach (var possibleTarget in colliders)
{
if (possibleTarget.GetComponent<EntityBase>())
{
var possibleTargetAngle = Vector2.Angle(startPosition, possibleTarget.transform.position);
if (possibleTargetAngle >= left && possibleTargetAngle <= right)
{
targetList.Add(possibleTarget.GetComponent<EntityBase>());
}
}
}
return targetList;
}
Vector2.Angle(startPosition, possibleTarget.transform.position);
This is wrong. Imagine a line from the scene origin (0,0) to startPosition and a line to the transform.position. Vector2.Angle is giving you the angle between those two lines, which is not what you want to measure.
What you actually want is to give GetEntitiesInArc a forward vector then get the vector from the origin to the target position (var directionToTarget = startPosition - possibleTarget.transform.position) and measure Vector2.Angle(forward, directionToTarget).

Xna Isometric point to click movement

I'm working on an isometric game (diamond grid) and I've stumbled across a minor problem regarding a character movement.
I'm using A* to find a path between 2 points and then I want to move my character from point A to point B going through all the tiles which form the path but I can't find a way to do this , I mean a simpler and accurate method.
So far I've scrapped this piece of code but it's kinda "rusty"
public void Destination(tile destination)
{
for (int i = 0; i < 8; i++)
{
if (AdjacentTile[i] == destination)
{
characterDirection = i;
}
}
animation.changeSpriteDirection(characterDirection); //After I found which adjacent tile is the next destination I change the character direction based on it's position (1 = North , 2 = Nort Est etc) .. so the Y of the Animation_sourceRectangle it's changed//
Vector2 Position;
Position.X = current_characterTile.X - destination.X;
Position.Y = current_characterTile.Y - destination.Y;
rotation = (float)Math.Atan2(-Position.X, Position.Y);
moveVector = (Vector2.Transform(new Vector2(0, -1), Matrix.CreateRotationZ(rotation))) * characterSpeed;
movingCommand = 1; // the character is supposed to be moving..
Move(); //this function moves the sprite until the *tile.i and tile.j* of the character is the same as tile.j and tile.i of the destination
//something like this
if ( characterTile.i == destination.i && characterTile.j == destination.j)
movingCommand = 0 //stop
else
character_Position += moveVector;
}
If anyone could give me a hint on what to do or help me I'll be very grateful.
Thank You.
Possibilities:
At each tile, determine the character's speed vector and also determine how much time it will take for the character to move to next tile. When that time elapses, immediately begin moving to the next tile. (This is what I implemented below.)
At each tile, determine the character's speed vector. Then, when the character is sufficiently close to the next tile (say, the difference between their X and Y coordinates is less than 2 pixels?), snap it to the tile and begin moving to the next tile. This will causes artifacts and be in general less precise.
A solution:
Let's assume you already ran your pathfinding algorithm and found a linked list of a tiles that you must go through to arrive at target. Let's also assume those tiles cannot become blocked partway through the movement (it is simple to modify the algorithm if they can, though).
I usually do something like this to handle this problem:
Run the pathfinding algorithm, which returns a List, if a path
exists.
character.Path = theListThatAStarReturned;
character.beginMovingToTarget(character.Path[0]);
character.Path.RemoveAt(0);
The beginMovingToTarget() method will determine the velocity vector and also determine the the time needed to arrive at the tile. When the time is reached, we immediately go to the next tile, until the Path is empty. Let's call this time variable character.timeToArrival.
Update():
if (!character.Moving) return; // Or just don't execute the rest of this code.
character.position += character.speed * elapsedSeconds;
character.timeToArrival -= elapsedSeconds;
// Did the character arrive in a tile?
if (character.timeToArrival <= 0)
{
// This will ensure the character is precisely in the tile, not a few pixels veered off.
character.position = character.movingToTile.position;
if (character.Path.Count == 0)
{
character.Moving = false;
// We are at final destination.
}
else
{
character.beginMovingToTarget(character.Path[0]);
character.Path.RemoveAt(0);
}
}
And the beginMovingToTarget(targetTile) function:
this.movingToTile = targetTile;
Vector2 direction;
direction = targetTile.position - this.position;
this.timeToArrival = direction.Length() / this.speedPerSeconds;
direction.Normalize();
direction *= this.speedPerSeconds;
this.speed = direction;
// Here, you may also want to change the character's animation, if you want to, or you may do that directly in the Draw() method based on its speed vector.
Make sure the division is in floats, not integers.

XNA Find nearest Vector from player

I have a List of vectors and a PlayerVector I just want to know how I can find the nearest Vector to my PlayerVector in my List.
Here are my variables:
List<Vector2> Positions;
Vector2 Player;
The variables are already declared and all, I just need a simple code that will search for the nearest position to my player. Isn't there a simple way?
Since you don't need the exact distance (just a relative comparison), you can skip the square-root step in the Pythagorean distance formula:
Vector2? closest = null;
var closestDistance = float.MaxValue;
foreach (var position in Positions) {
var distance = Vector2.DistanceSquared(position, Player);
if (!closest.HasValue || distance < closestDistance) {
closest = position;
closestDistance = distance;
}
}
// closest.Value now contains the closest vector to the player
Create a int called distanceToPlayer, set it to 0.
Create a int called nearestObject set it to 0.
Loop through all the objects with a for loop. It is slightly faster than a foreach loop, and is more useful in this situation.
In the loop:
Get the distance with Vector2.Distance, and check it against distanceToPlayer, if less, then store the index number of the object in nearestObject, and store the new distance in distanceToPlayer.
After the loop is done, you will have the distance in whole pixels, and the index of the item in the list stored. You can access the item using Positions[index].
I'm writing it from memory, because I don't have access to XNA now:
Vector2 nerrest = Positions.Select(vect => new { distance= vect.Distance(Player), vect})
.OrderBy(x => x.distance)
.First().vect;
Little tips:
In this solution you probably can use PLINQ to gain little speedup on distance computing.

Collision checking on slopes

I'm working on a new game, and am trying to detect whether or not the player (on a slope) is colliding with a given mesh based off of their coordinates relative to the coordinates of the slope. I'm using this function, which doesn't seem to be working (the slope seems too small or something)
//Slopes
float slopeY = max.Y-min.Y;
float slopeZ = max.Z-min.Z;
float slopeX = max.X-min.X;
float angle = (float)Math.Atan(slopeZ/slopeY);
//Console.WriteLine(OpenTK.Math.Functions.RadiansToDegrees((float)Math.Atan(slopeZ/slopeY)).ToString()+" degrees incline");
slopeY = slopeY/slopeZ;
float slopeZX = slopeY/slopeX;
//End slopes
float surfaceposX = max.X-coord.X;
float surfaceposY = max.Y-coord.Y;
float surfaceposZ = min.Z-coord.Z;
min-=sval;
max+=sval;
//Surface coords
//End surface coords
//Y SHOULD = mx+b, where M = slope and X = surfacepos, and B = surfaceposZ
if(coord.X<max.X& coord.X>min.X&coord.Y>min.Y&coord.Y<max.Y&coord.Z>min.Z&coord.Z<max.Z) {
if(slopeY !=0) {
Console.WriteLine("Slope = "+slopeY.ToString()+"SlopeZX="+slopeZX.ToString()+" surfaceposZ="+surfaceposZ.ToString());
Console.WriteLine(surfaceposY-(surfaceposY*slopeY));
//System.Threading.Thread.Sleep(40000);
if(surfaceposY-(surfaceposZ*slopeY)<3 || surfaceposY-(surfaceposX*slopeZX)<3) {
return true;
} else {
return false;
}
} else {
return true;
}
} else {
return false;
}
Any suggestions?
Sample output:
59.86697
6.225558 2761.331
68.3019 degrees incline
59.86698,46.12445
59.86698
6.225558 2761.332
0 degrees incline
EDIT: Partially fixed the problem. Slope detection works, but now I can walk through walls???
//Slopes
float slopeY = max.Y-min.Y;
float slopeZ = max.Z-min.Z;
float slopeX = max.X-min.X;
float angle = (float)Math.Atan(slopeZ/slopeY);
//Console.WriteLine(OpenTK.Math.Functions.RadiansToDegrees((float)Math.Atan(slopeZ/slopeY)).ToString()+" degrees incline");
slopeY = slopeY/slopeZ;
float slopey = slopeY+1/slopeZ;
float slopeZX = slopeY/slopeX;
//End slopes
float surfaceposX = min.X-coord.X;
float surfaceposY = max.Y-coord.Y;
float surfaceposZ = min.Z-coord.Z;
min-=sval;
max+=sval;
//Surface coords
//End surface coords
//Y SHOULD = mx+b, where M = slope and X = surfacepos, and B = surfaceposZ
if(coord.X<max.X& coord.X>min.X&coord.Y>min.Y&coord.Y<max.Y&coord.Z>min.Z&coord.Z<max.Z) {
if(slopeY !=0) {
Console.WriteLine("Slope = "+slopeY.ToString()+"SlopeZX="+slopeZX.ToString()+" surfaceposZ="+surfaceposZ.ToString());
Console.WriteLine(surfaceposY-(surfaceposY*slopeY));
//System.Threading.Thread.Sleep(40000);
surfaceposZ = Math.Abs(surfaceposZ);
if(surfaceposY>(surfaceposZ*slopeY) & surfaceposY-2<(surfaceposZ*slopeY) || surfaceposY>(surfaceposX*slopeZX) & surfaceposY-2<(surfaceposX*slopeZX)) {
return true;
} else {
return false;
}
} else {
return true;
}
} else {
return false;
}
Have you considered implementing a BSP tree? Even if you work out the bugs with the code you're using now, it'll be dog slow with a mesh of any decent size/complexity. A BSP or quadtree will go a long way towards simplifying your code and improving performance, and they're very easy to implement.
Edit
Here's a link to a nice BSP tutorial and overview.
If you're only concerned with terrain (no vertical walls, doors, etc), a quadtree might be more appropriate:
Here's a nice quadtree tutorial at gamedev.net.
Both of these algorithms are intended to divide your geometry into a tree to make searching easier. In your case, you're searching for polygons for collision purposes. To build a BSP tree (very briefly):
Define a structure for the nodes in the tree:
public class BspNode
{
public List<Vector3> Vertices { get; set; }
// plane equation coefficients
float A, B, C, D;
BspNode front;
BspNode back;
public BspNode(Vector3 v1, Vector3 v2, Vector3 v3)
{
Vertices = new List<Vector3>();
Vertices.AddRange(new[] { v1, v2, v3 });
GeneratePlaneEquationCoefficients();
}
void GeneratePlaneEquationCoefficients()
{
// derive the plane equation coefficients A,B,C,D from the input vertex list.
}
bool IsInFront(Vector3 point)
{
bool pointIsInFront=true;
// substitute point.x/y/z into the plane equation and compare the result to D
// to determine if the point is in front of or behind the partition plane.
if (pointIsInFront && front!=null)
{
// POINT is in front of this node's plane, so check it against the front list.
pointIsInFront = front.IsInFront(point);
}
else if (!pointIsInFront && back != null)
{
// POINT is behind this plane, so check it against the back list.
pointIsInFront = back.IsInFront(point);
}
/// either POINT is in front and there are no front children,
/// or POINT is in back and there are no back children.
/// Either way, recursion terminates here.
return pointIsInFront;
}
/// <summary>
/// determines if the line segment defined by v1 and v2 intersects any geometry in the tree.
/// </summary>
/// <param name="v1">vertex that defines the start of the ray</param>
/// <param name="v2">vertex that defines the end of the ray</param>
/// <returns>true if the ray collides with the mesh</returns>
bool SplitsRay(Vector3 v1, Vector3 v2)
{
var v1IsInFront = IsInFront(v1);
var v2IsInFront = IsInFront(v2);
var result = v1IsInFront!=v2IsInFront;
if (!result)
{
/// both vertices are on the same side of the plane,
/// so this node doesn't split anything. Check it's children.
if (v1IsInFront && front != null)
result = front.SplitsRay(v1, v2);
else if (!v1IsInFront && back != null)
result = back.SplitsRay(v1, v2);
}
else
{
/// this plane splits the ray, but the intersection point may not be within the face boundaries.
/// 1. calculate the intersection of the plane and the ray : intersection
/// 2. create two new line segments: v1->intersection and intersection->v2
/// 3. Recursively check those two segments against the rest of the tree.
var intersection = new Vector3();
/// insert code to magically calculate the intersection here.
var frontSegmentSplits = false;
var backSegmentSplits = false;
if (front!=null)
{
if (v1IsInFront) frontSegmentSplits=front.SplitsRay(v1,intersection);
else if (v2IsInFront) frontSegmentSplits=front.SplitsRay(v2,intersection);
}
if (back!=null)
{
if (!v1IsInFront) backSegmentSplits=back.SplitsRay(v1,intersection);
else if (!v2IsInFront) backSegmentSplits=back.SplitsRay(v2,intersection);
}
result = frontSegmentSplits || backSegmentSplits;
}
return result;
}
}
Pick a "partition" plane (face) from your mesh that roughly divides the rest of the mesh in two. This is a lot easier to do with complex geometry as fully convex items (spheres and the like) tend to wind up looking like lists instead of trees.
Create a new BspNode instance from the vertices that define the partition plane.
Sort the remaining faces into two lists - one that is in front of the partition plane, and one containing those faces that are behind.
Recurse to step 2 until no more nodes are in the list.
When checking for collisions, you have two options.
Single-point: check the coordinates that the character or object is moving to against the tree by calling your root node's .IsInFront(moveDestination) If the method returns false, the target point is "inside" the mesh, and you've collided. If the method returns true, the target point is "outside" the mesh, and no collision has occurred.
Ray intersection. This one gets a little tricky. Call the root node's .SplitsRay() method with the object's current position and it's target position. If the methods returns true, moving between the two positions will transition through the mesh. This is a superior (though more complex) check because it will catch edge cases, such as when the desired movement would take an object completely through an object in one step.
I just threw that sample code together quickly; it's incomplete and probably won't even compile, but it should get you going in the right direction.
Another nice thing about the BSP: Using the .SplitsRay() method, you can determine if one point on a map is visible from another point. Some games use this to determine if NPCs/AIs can see each other or real players. You could use a slight modification of that to determine if they can hear each other walking, etc.
This may seem a lot more complicated than your original approach, but it's ultimately far more powerful and flexible. It's worth your time to investigate.

What's wrong with my soft-shadow code?

I'm trying to write a simple raytracer as a hobby project and it's all working fine now, except I can't get soft-shadows to work at all. My idea of soft-shadows is that the lightsource is considered to have a location and a radius. To do a shadow test on this light I take the point where the primary ray hit an object in the scene and cast an n-amount of rays towards the lightsource where each new ray has a random component to every axis, where the random component varies between -radius and radius.
If such a ray hits an object in the scene, I increment a hitcounter (if a ray hits multiple objects, it still only increments with one). If it makes it to the lightsource without collisions, I add the distance of the primary ray's intersect point to the lightsource's center to a variable.
When n samples have been taken, I calculate the ratio of rays that have collided and multiply the color of the light by this ratio (so a light with color 1000,1000,1000 will become 500,500,500 with a ratio of 0.5, where half the rays have collided). Then I calculate the average distance to the lightsource by dividing the distance variable of earlier by the amount of non-colliding rays. I return that variable and the function exits.
The problem is: it doesn't work. Not quite at least. What it looks like can be seen here. You can see it sort of resembles soft-shadows, if you squint real hard.
I don't get it, am I making some sort of fundamental flaw here, or is it something tiny? I'm fairly sure the problem is in this method, because when I count the number of partially lit pixels produced directly by this method, there are only about 250, when there should be a lot more. And when you look closely at the picture, you can see there's some partially lit pixels, suggesting the rest of the code processes the partially lit pixels just fine.
Here's the actual light for soft-shadows class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MyFirstRayTracer
{
public class AreaLight : ILight
{
private const int _radius = 5;
private const int _samples = 16;
public Color Color { get; set; }
public Vector Location { get; set; }
#region ILight Members
public float GetLightingInformation(Vector point, ISceneObject[] scene, out Color color)
{
int intersectCount = 0;
float distance = -1;
for(int i = 0; i < _samples; i++)
{
bool intersects = false;
float rand = 0;
rand = _radius - (float)(new Random().NextDouble()*(2*_radius));
foreach (ISceneObject obj in scene)
{
Vector iPoint;
Vector loc = new Vector(Location.X + rand, Location.Y + rand, Location.Z + rand);
if (!obj.Intersect(new Ray(point, loc), out iPoint))
{
distance += (Location - point).SqLength;
}
else
{
intersects = true;
distance -= (Location - point).SqLength;
}
}
if (intersects)
intersectCount++;
}
float factor = 1-((float)intersectCount/_samples);
color = new Color(factor*Color.R, factor*Color.G, factor*Color.B);
return (float)Math.Sqrt(distance / (_samples - intersectCount));
}
#endregion
}
}
minor point but is this the best use of the random class..
for(int i = 0; i < _samples; i++)
{
bool intersects = false;
float rand = 0;
rand = _radius - (float)(new Random().NextDouble()*(2*_radius));
should this not be..
var rnd = new Random()
for(int i = 0; i < _samples; i++)
{
bool intersects = false;
float rand = 0;
rand = _radius - (float)(rnd.NextDouble()*(2*_radius));
Try generating a different "rand" for each component of "loc". As is, your jittered points all lie on a line.
You actually generate the point on the line on a line with direction (1, 1, 1). Is the lightsource really linear?
Also, I can barely see anything in your example. Could you make your camera nearer the to-be shadow and not pointing from the direction of the light?
See, this is why I come to this site :)
Every axis has its own random now, and it looks a lot better. It's still a little weird looking, increasing the number of samples helps though. It now looks like this.
Do you know a more efficient way to reduce the pattern-forming?
The biggest help though: not instantiating Random for every sample. It seriously tripled my rendering speed with soft shadows! I never knew that Random was so costly to instantiate. Wow.
Thanks a lot.
In your response you asked for an improved way to make soft shadows. An improvement could be, instead of randomizing all the rays from the same point, to give each ray a different offset on all axes to effectively give them a seperate little window to randomize in. This should result in a more even distribution. I don't know if that was clear but another way to describe it is as a grid which is perpendicular to the shadow ray. Each tile in the grid contains one of the n shadow rays but the location in the grid is random. Here you can find a part of a tutorial which describes how this can be used for soft shadows.

Categories

Resources