Collision detection by sliding against a plane in XNA - c#

I am attempting to engineer a collision detection algorithm for a custom Minecraft client I'm making. Basically, the entire world is made up of cubes, and the player (or camera) needs to be able to stand on and move against these cubes. The result I want is illustrated in this image:
The green line is the player's movement vector. When the player is brushing up against a plane of one of the cubes, I want the vector to change to one that is perpendicular with the plane. The vector should, however, keep all of it's velocity in the plane's direction, yet lose all velocity towards the plane.
I hope I've made my question clear. What is the best and most efficient way to implement a collision detection system like this? Also, will a system like this allow for a simple gravity component?
EDIT: Forgot to mention, the cubes are stored in a three-dimensional array, [x,y,z].

A simple approach to implementing this would be to detect the collision of the ball and the plane. Calculate the penetration depth, this is how far the ball has actually gone past the plane, and push the ball back in the direction of the plane's normal.
This will have the effect of putting the ball on the surface of the plane. If you do this for each frame, the ball will effectively slide along the plane, assuming of course that the ball's velocity vector is not not parallel to the plane's normal.
The field of collision detection is big and complex, and depending on your game you have to determine what is sufficient for your requirements in terms of the level of realism you require and the performance requirements. You should always go for the simplest solution that give a realistic enough feedback, depending on the game it often does not have to be perfect.
Basically you should break your collision detection into 2 phases typically known as broad phase and narrow phase.
The broad phase could be as simple as perform a quick bounding box check to determine potential collisions and then submit those potential collision to the narrow phase collision detection to do the more detailed checks where you determine if there really was a collision and the collision depth. If you have many objects then the broad phase might use some kind of quadtree indexing to select only the blocks in the vicinity of your object to perform the collision detection against.

In a world of Axis Aligned cubes, this is really easy. It's easy to think that you need something elaborate, but in reality it's really simple. This is from experience after writing my own minecraft clone.
Here's how:
position.X += velocity.X;
if(colliding())
position.X -= velocity.X;
position.Y += velocity.Y;
if(colliding())
position.Y -= velocity.Y;
position.Z += velocity.Z;
if(colliding())
position.Z -= velocity.Z;
Here's the code for finding out if you're colliding or not:
bool colliding()
{
int minX = Position.X - size.X / 2;
int minY = Position.Y - size.Y / 2;
int minZ = Position.Z - size.Z / 2;
int maxX = Position.X + size.X / 2;
int maxY = Position.Y + size.Y / 2;
int maxZ = Position.Z + size.Z / 2;
for (int x = minX; x <= maxX; x++)
for (int y = minY; y <= maxY; y++)
for (int z = minZ; z <= maxZ; z++)
{
if(blockType[x, y, z] != 0)
return true;
}
return false;
}

Related

Object spawn on procedural terrain spawns on unspecified area

I've managed to put together a procedural terrain with defined regions and I am looking to procedurally place objects within the world within these regions. The regions are defined by their height and I am trying to utilise this to correctly place certain objects in certain regions however my result seems to come out slightly odd where objects are able to spawn outside the defined region height. I am using an AnimationCurve as a mesh height curve to prevent water areas from becoming terrain like. I am unsure if this is causing the issue behind in the correct placement. Would appreciate any insight into where I might be going wrong
Defined regions:
The Rock region is defined with a height of 0.7 and I try to spawn trees on the map only at a Rock location
Spawning object (Spawn 10) at rock location
int amount = 0;
for (int y = 0; y < mapHeight; y++)
{
if(amount < 10)
{
for (int x = 0; x < mapWidth; x++)
{
float currentHeight = noiseMap[x, y];
if(currentHeight.ToString("f1") == (0.7f).ToString())
{
Debug.Log(currentHeight.ToString("f1"));
Vector3 spawnPosition = new Vector3(Random.Range((x), (mapWidth / 2)), currentHeight, Random.Range(y, (mapHeight / 2)));
var block = Instantiate(AssetsToSpawn[0].AssetPrefab, spawnPosition, Quaternion.identity);
block.transform.SetParent(this.transform);
amount++;
break;
}
}
} else
{
return;
}
Result
Some seem to spawn in the right location albeit looking slightly weird but the one on the far left is finding itself on flat land, with water and sand; an area not defined as 0.7 or Rock type.
I think the issue lies in the line
Vector3 spawnPosition = new Vector3(Random.Range((x), (mapWidth / 2)), currentHeight, Random.Range(y, (mapHeight / 2)));
you seem to already iterate your map grid using x and y so why pick random positions on your map that might be anywhere between this current position and the center of the map?
I think you would rather want a random position within the current field and do e.g.
Vector3 spawnPosition = new Vector3(x + Random.Range(-0.5f, 0.5f), currentHeight, y + Random.Range(-0.5f, 0.5f));
Besides that why go through strings in
if(currentHeight.ToString("f1") == (0.7f).ToString())
I see that it's probably for the rounding but I would still prefer to rather do e.g.
if(Mathf.Abs(currentHeight - 0.7f) <= 0.05f)
which would have about the same effect but the threshold is better to control.
However, sounds to me like rock rather would be anything between 0.49 and 0.7 actually so actually it should be
if(currentHeight > 0.49f && currentHeight <= 0.7f)
Finally, unless you store somewhere which map position you already populated with a tree your outer for loop will always over and over enter at the exact same grid position, the first one that is encountered to fulfill your inner loop's condition!
So far you where always using the exact se position for all 10 trees, only the random position caused that it didn't seem so.

how to make the transform.up of a gameobject be colliear with a vector, without changing it's Y rotation?

So... I'll try to be as clear as possible, if I let something unclear please let me know.
I have a vector that comes from origin and go to a point in space, and I have an object that I want it's transform.up (Or Y vector) to be colinear with this vector, but the Y rotation of this object is driven by another factor, and I dont want to change it.
So far, what I'm trying to do is project this vector in the local XY and local ZY planes and measure the angles and apply rotation:
float xInclination = Mathf.Atan(Vector3.ProjectOnPlane(orbitMomemtumVector, transform.right).z / Vector3.ProjectOnPlane(orbitMomemtumVector, transform.right).y)*Mathf.Rad2Deg;
float yInclination = Mathf.Atan(initialPos.z / initialPos.x) * Mathf.Rad2Deg;
float zInclination = -Mathf.Atan(Vector3.ProjectOnPlane(orbitMomemtumVector, transform.forward).x / Vector3.ProjectOnPlane(orbitMomemtumVector, transform.forward).y)*Mathf.Rad2Deg;
if (initialPos.x < 0 && initialPos.z > 0)
{
yInclination = 180f - Mathf.Abs(yInclination);
argumentPeriapsis = argumentPeriapsis - yInclination;
}
else if (initialPos.x < 0 && initialPos.z < 0)
{
yInclination = 180f + Mathf.Abs(yInclination);
argumentPeriapsis = argumentPeriapsis - yInclination;
}
else
{
argumentPeriapsis = argumentPeriapsis - yInclination;
}
transform.rotation = Quaternion.Euler(xInclination, (float)argumentPeriapsis, zInclination);
This image shows the problem, I need the Y arrow to be collinear with the blue line
Let me be clear on this, don't use Euler angles in 3d space. In fact, avoid them in 2d games as well. Unless your object truly rotates on a single axis, and you never have to get the angle between two rotations, or lerp your rotations, don't use them.
What you want is Quaternion.LookRotation(A, B).
A being a vector to which Z will be colinear, X being orthogonal to the plane defined by A and B, and Y belonging to that plane.
Followup:
To match other axis to A, there are multiple solutions. First would be to simply apply the lookRotation to a parent object, while the child object is rotated inside to match whatever rotation you want. You can also edit your entire mesh to do so.
The other way is to simply apply another rotation, so that both combined get your desired result, like so:
Quaternion zMatched = Quaternion.LookRotation(zAxisTarget, direction)
Quaternion yMatched = zMatched * Quaternion.AngleAxis(90f, Vector3.right);
transform.rotation = yMatched;
This will rotate the object so that the y axis becomes collinear to the previous z axis.
This is however nor perfect. If you reach this point, you should consider building your own clearer solution based on combining AngleAxis results. But it works well enough.

How to prevent floating point error in point-polygon sliding collision detection

I'm trying to write a function to handle movement within a game I'm programming. What I have nearly works, but there are a couple situations where it breaks down.
I've coded up a minimal demonstrative example, presented below. In this example, I'm trying to calculate the travel of an object, represented by a point, and movement vector. This object's movement path is checked against a collection of polygons, which are broken down into line segments for testing. When this object collides with a line segment, I want it to slide along that segment (rather than stop or bounce away).
To do this, I check along my intended path for collisions, and if I find an intersection, I do a new test from that intersection point along the path of the line segment I've collided with, with the magnitude of the remainder of movement.
The problem arises when we slide along a line segment into a "pocket". Often times, the collision check will pass on both of the line segments that form the pocket, and the object will slip through. Because I'm travelling parallel to one of the line segments, and I'm intersecting with both line segments at an end points, I believe this issue is caused by floating point error. Whether or not it slips through, is caught, or is caught once and then slips through on the second check seems to be totally random.
I'm calculating intersection using a simple algorithm I found here: https://stackoverflow.com/a/20679579/4208739, but I've tried many other algorithms as well. All exhibit the same problems.
(Vector2 is class provided by the Unity library, it just holds x and y coordinates as floats. The Vector2.Dot function just calculates the dot product).
//returns the final destination of the intended movement, given the starting position, intended direction of movement, and provided collection of line segments
//slideMax provides a hard cap on number of slides allowed before we give up
Vector2 Move(Vector2 pos, Vector2[] lineStarts, Vector2[] lineEnds, Vector2 moveDir, int slideMax)
{
int slideCount = 0;
while (moveDir != Vector2.zero && slideCount < slideMax)
{
pos = DynamicMove(pos, lineStarts, lineEnds, moveDir, out moveDir);
slideCount++;
}
return pos;
}
//returns what portion of the intended movement can be performed before collision, and the vector of "slide" that the object should follow, if there is a collision
Vector2 DynamicMove(Vector2 pos, Vector2[] lineStarts, Vector2[] lineEnds, Vector2 moveDir, out Vector2 slideDir)
{
slideDir = Vector2.zero;
float moveRemainder = 1f;
for (int i = 0; i < lineStarts.Length; i++)
{
Vector2 tSlide;
float rem = LineProj(pos, moveDir, lineStarts[i], lineEnds[i], out tSlide);
if (rem < moveRemainder)
{
moveRemainder = rem;
slideDir = tSlide;
}
}
return pos + moveDir * moveRemainder;
}
//Calculates point of collision between the intended movement and the passed in line segment, also calculate vector of slide, if applicable
float LineProj(Vector2 pos, Vector2 moveDir, Vector2 lineStart, Vector2 lineEnd, out Vector2 slideDir)
{
slideDir = new Vector2(0, 0);
float start = (lineStart.x - pos.x) * moveDir.y - (lineStart.y - pos.y) * moveDir.x;
float end = (lineEnd.x - pos.x) * moveDir.y - (lineEnd.y - pos.y) * moveDir.x;
if (start < 0 || end > 0)
return 1;
//https://stackoverflow.com/a/20679579/4208739
//Uses Cramer's Rule
float L1A = -moveDir.y;
float L1B = moveDir.x;
float L1C = -(pos.x *(moveDir.y + pos.y) - (moveDir.x + pos.x)*pos.y);
float L2A = lineStart.y - lineEnd.y;
float L2B = lineEnd.x - lineStart.x;
float L2C = -(lineStart.x * lineEnd.y - lineEnd.x * lineStart.y);
float D = L1A * L2B - L1B * L2A;
float Dx = L1C * L2B - L1B * L2C;
float Dy = L1A * L2C - L1C * L2A;
if (D == 0)
return 1;
Vector2 inter = new Vector2(Dx / D, Dy / D);
if (Vector2.Dot(inter - pos, moveDir) < 0)
return 1;
float t = (inter - pos).magnitude / moveDir.magnitude;
if (t > 1)
return 1;
slideDir = (1 - t) * Vector2.Dot((lineEnd - lineStart).normalized, moveDir.normalized) * (lineEnd - lineStart).normalized;
return t;
}
Is there some way to calculate collision that isn't susceptible to this sort of problem? I imagine I can't totally eradicate floating point error, but is there a way to check that will at least guarantee I collide with ONE of the two line segments at the pocket? Or is there something more fundamentally wrong with going about things in this way?
If anything is unclear I can draw diagrams or write up examples.
EDIT: Having reflected on this issue more, and in response to Eric's answer, I'm wondering if converting my math from floating point to fixed point could solve the issue? In practice I'd really just be converting my values (which can fit comfortably in the range of -100 to 100) to ints, and then performing the math under those constraints? I haven't pieced all the issues together quite yet, but I might give that a try. If anyone has any information about anything like that, I'd be appreciative.
You have a line that, ideally, is aimed exactly at a point, the endpoint of a segment. That means any error in calculation, no matter how small, could say the line misses the point. I see three potential solutions:
Analyze the arithmetic and design it to ensure it is done with no error, perhaps by using extended-precision techniques.
Analyze the arithmetic and design it to ensure it is done with a slight error in favor of collision, perhaps by adding a slight bias toward collision.
Extend the line segment slightly.
It seems like the third would be easiest—the two line segments forming a pocket could just be extended by a bit, so they cross. Then the sliding path would not be aimed at a point; it would be aimed at the interior of a segment, and there would be margin for error.

Contrain camera to rectangle while tracking multiple objects

I'm making a 2D platformer that features a dynamic camera. The camera must track 4 players at once so that they're all on the screen. In addition the camera must not move beyond a predefined rectangle boundary. I've tried implementing it but I just can't seem to get the process of zooming the camera so that it's always close as possible to the four objects.
The general algorithm I have so far is
1. Define the viewing space by calculating a 2D axis aligned bounding box using the 4 object positions being tracked and use its center as a camera postion (or averaging)
2. Calculate an orthographic size by using the largest x OR y value using a vector from the camera's position to each object being tracked.
If the camera is beyond the camera's boundary calculate the excess amount and displace in the opposite direction.
This seems simple enough on paper but I can't seem to get a correct working implementation.
Why dont you just take the Average of the 4 players Position and use it as Camera Position, also check if the players are out of boundary and when they are, zoom out.
float x = 0;
float y = 0;
GameObject[] players = new GameObjects[5];
foreach(GameObject _ply in players)
{
x += _ply.transform.position.x;
y += _ply.transform.position.y;
}
x = x/players.Length;
y = y/players.Length;
foreach(GameObject _ply in players)
{
if(_ply.transform.position.x > (x + (Screen.Width / 2)))
//zoom out
if(_ply.transform.position.y > (y + (Screen.Height / 2)))
//zoom out
}
But you have to fix Zoomin.

Gravity with air-time, acceleration and speed gaining

I am trying to accomplish a gravity, where airtime is included, and also acceleration.I have tried using usual gravity, which looks something like this:
velocity += gravity * dt;
position += velocity * dt;
This would probably work good enough for a normal platformer game, but I am trying to make a game inspired by "The helicopter game", where you have to navigate through a tunnel, without touching the walls.what I want to do different, is that I want to be able to save up speed on the way down, which will be used on the way up again, so I will have some acceleration at the beginning.I also want some kind of airtime, so when you hit the top it would not force you down as fast as It would, if I had used the gravity from the code sample.This image illustrates the curve I would like to have:Please note that the whole controlling is done by one key, so for example you would fly up if you held down space, and dive if you released it.The character also never moves left or right, since it will have a static X position on the screen, so vectors can't be used.I have spent hours trying to make it work, but with no success. I have also tried searching on the internet, but without any luck.The game "Whale Trails" got the gravity I kind of want.Here is a link for a video of the game: http://www.youtube.com/watch?v=5OQ0OWcuDJsI'm not that big of a physics guy, so it would be cool if you could give an example of the actual code I hope anyone can help me figure this out.
Gravity is the force that pulls objects down. Your player is the force that pulls objects up. Accordingly your code must be:
if(keyPressed) {
velocity += POWER_OF_PLAYER;
}
velocity += G;
position += velocity;
This is enough to create a curve like you illustrated. Of course POWER_OF_PLAYER must be of a different sign and the absolute value must be greater to make this work.
G = -9.81
POWER_OF_PLAYER = 20
Saving power is then a simple check.
if(keyPressed) {
if(powerSaved > 0) {
velocity += POWER_OF_PLAYER;
powerSaved -= SOMETHING;
}
} else if (velocity >= SOME_MINIMUM_SPEED_BEFORE_GETTING_POWER) {
powerSaved += SOMETHING;
}
SOME_MINIMUM_SPEED_BEFORE_GETTING_POWER should be something less or equal 0.
P.S. I assumed your Y axis starts at ground and shoots into the sky. Signs put accordingly.
It looks like the velocity of the fish is constant.
Try the following:
velocity is fixed and should not change (unless the fish eats a speed power-up).
angle = 0 is equivalent to level flight.
angle -= gravity * dt;
if (angle < - Math.PI / 2) {
angle = Math.PI / 2;
}
if (keyPressed) {
angle += power * dt;
}
if (angle < - Math.PI / 2) {
// Stop the player from doing a looping
angle = - Math.PI / 2;
}
if (angle > Math.PI / 2) {
// Stop the player from doing an inverted looping
angle = Math.PI / 2;
}
// Move the fish (vertical component of velocity)
position += velocity * dt * Math.sin(angle);
// Move the background (horizontal component of velocity)
background_position -= velocity * dt * Math.sin(angle);
It sounds like incorporating 'lift' that is based on horizontal speed and have the button press trigger a 'noseup' movement would work sufficiently well.
So lift would be some constant k multiplied by the horizontal speed Vx and vertical speed Vy would be the difference of gravity and lift times the change in time dt
lift = k * Vx
Vy += ( lift - gravity ) * dt
def noseup
k = 0.01 #some small chunk
dx = k * Vx
dy = k * Vy
Vx -= dy
Vy += dx
When the plane (or whatever) noses up it basically lowers the velocity on one axis while increasing it on the other.
Probably wouldn't be a bad idea to throw drag in there somewhere as well now that I think about it, would have to be dependent on the absolute velocity V = ( Vx**2 + Vy**2 )**0.5...and weight is a better word than gravity in this case (less confusing, units wise) but it works I guess.
Not exactly "physics" but a close approximation that should work fairly well. Play around with the values of k and see if you can make it do what you want.
BTW sorry for the uber crappy psuedocode :-P

Categories