Math Help - Adjust camera center to keep graphics on screen - math

Either my coffee hasn't kicked in yet or I didn't pay enough attention in college, either way I can't wrap my head around the right way to do this calculation.
I have a maze that is made up of zones, the player can only see one zone at a time. At the moment the camera stays centered on the player, so as they nears the edge of the zone we see every expanding blackness because there is nothing there. I would like for the camera's center to stay stationary when the player gets close to the adjust of the zone.
This is the code that I have so far:
private void ResetRenderView()
{
// Make sure this control always fills the parent
this.Size = this.Parent.Size;
//Calculate and correct CellSize so the screen can always be filled
int zoneSize = DM.currentDungeon.Settings.ZoneSize;
float minCellSize = (float)this.Size.Width / (float)zoneSize;
if (cellSize < minCellSize + .5f) { cellSize = minCellSize + .5f; }
//Calculate Center Point (usually on Player except when close to the edge)
Vector2f center = new Vector2f(PlayerLocation.X * cellSize, PlayerLocation.Y * cellSize);
/**** NEED HELP HERE ****/
if (center.X < (this.Size.Width / 2) - ((zoneSize * cellSize) / 2)) { center.X = ((zoneSize * cellSize) / 2); }
renderwindow.SetView(new SFML.Graphics.View(center, new Vector2f(this.Size.Width, this.Size.Height)));
}
I believe everything needed to solve this is shown above:
cellSize is the pixel size of each cell
zoneSize is the number of cells (width and height) in each zone
this.Size returns the viewable pixels
cellSize * zoneSize will return the total number of pixels (width and height) that the zone uses.
There is currently a single if statement for adjusting one part center.X and I was planning on doing 3 more. But I can't get that one to work. I've tried several things. Any help would be appreciated.

Coffee kicked in. Here is what I have working:
float totalSize = zoneSize * cellSize;
if (center.X > totalSize - (this.Size.Width / 2))
{
center.X = totalSize - (this.Size.Width / 2);
}
if (center.X < this.Size.Width / 2)
{
center.X = this.Size.Width / 2;
}
if (center.Y > totalSize - (this.Size.Height / 2))
{
center.Y = totalSize - (this.Size.Height / 2);
}
if (center.Y < this.Size.Height / 2)
{
center.Y = this.Size.Height / 2;
}
If anyone can post a better answer (less if statements, more elegant, etc...), that isn't just a rewrite of the above code, withing the next few days, I will be happy to mark that as the answer.

Related

2d physics ball bouncing and rolling on incline response

I asked this question before but it got closed because it said I was asking for recommendations which isn't allowed.
Really I'm just looking for a solution whether it's from a book,website or someone who knows how to do this.
I progressed somewhat since I asked the question the last time, but anyways:
So far I've got a program where you can spawn balls which can collide with other balls and bounce off the window boundaries just fine, you can also draw a linestrip (am using sfml as I'm just a beginner and it requires very little setting up before you can get going with coding), so you hold down the mouse and every however many pixels it'll set the line you're currently drawing and start a new one at the end, so I can either do straight edges or change the setting to make a new line every pixel and make very ugly curves with the mouse (need to figure out how to do splines).
It makes no difference for the collision as it finds the closest point on the line every frame.
So dropping the ball onto the incline is fine, it bounces off like it should, or even several times,
but getting it to roll is a different story, even if you start the ball on the incline with 0 velocity as soon as it starts colliding it gets sent up off the incline instead of moving along it, and starts doing tiny bounces.
The only solution I found, which is terrible, is to time each bounce (or height of bounce I guess) and when its low enough to stop it bouncing and only have gravity act on it.
I'd be happy with it except what happens is it will bounce lower and lower until it reaches the
threshold, at which point it visibly slows down before it starts accelerating again rolling down the incline, which looks really bad, like it comes to a halt and starts getting pulled along.
Ideally I'd like it to actually simulate the physical forces rather than having to resort to tricks like timing the bounces.
While I was writing this I put in some code to work out what the angle is that the ball hits the incline at and I think the problem lies there, it's fine if it hits it from free fall but when rolling the angle shows as about 67-72 degrees when it should be..0 i guess?
Any help appreciated.
Here's the code for the collision.
void Particle::collideRamp(sf::Vector2f cp,sf::Vector2f l1,sf::Vector2f l2) {
//distance between ball and closest point on line
float dx = x - cp.x;
float dy = y - cp.y;
float distance = hypot(dx, dy);
//find line normal
float lx = l2.x - l1.x;
float ly = l2.y - l1.y;
sf::Vector2f LineNormal = normalize(ly, -lx);
//make velocity and projection same length so can mirror velocity against normal
sf::Vector2f normalMag(velocity.x * LineNormal.x,velocity.y * LineNormal.y);
sf::Vector2f Projection = LineNormal * hypotf(normalMag.x, normalMag.y);
sf::Vector2f vel = normalize(velocity.x, velocity.y) * hypotf(normalMag.x, normalMag.y);
//working on making circles rotate but early days
float rsx = prevx - x;
float rsy = prevy - y;
float rSpeed = hypot(rsx, rsy) / circumference;
//work out gravity forces for the incline, sin#mg down incline etc.
sf::Vector2f gravPerpendicular = normalize(-ly, lx);
sf::Vector2f gravParallell = normalize(lx, ly);
float gravPerMag;
float gravParMag;
gravParMag = sin(M_halfPI + atan2f(gravParallell.x,gravParallell.y));
gravPerMag = cos(M_halfPI + atan2f(gravParallell.x,gravParallell.y));
//// Collision detected.
if (distance < (radius)) {
//work out angle the ball struck the incline
float iAngle = atan2f(lx, ly) + atan2f(velocity.x, velocity.y);
//cout << iAngle * 180 / M_PI << " aft " << endl;
//make sure its 0-90
if (iAngle > 1.5708)
iAngle = 1.5708 - (iAngle - 1.5708);
//move the ball back if it went past the line by the amount it passed it by
sf::Vector2f v = normalize(velocity.x + forceAcc.x, velocity.y + forceAcc.y);
float overlap = (radius - distance );
x -= v.x * overlap;
y -= v.y * overlap;
rotationSpeed = rSpeed;
//messing with changing the angle only if bounce is a certain height as this is what's causing the
//problem i think, however the ball slows down before it starts rolling making it look weird
if (collClock.getElapsedTime().asSeconds() > 0.01) {
sf::Vector2f newVel = ((velocity + Projection) + Projection);
forceAcc = newVel;
float e = elasticity * elasticity;
forceAcc *= e;
velocity = sf::Vector2f(0, 0);
}
//add gravity forces, since the force that goes along the line normal is cancelled out
//by an equal force from the line I guess it doesn't need to be added?
//accelerateI(sf::Vector2f(-sin(gravPerpendicular) * gravPerMag * gravity * mass, cos(gravPerpendicular) * gravPerMag * gravity * mass));
//accelerate(sf::Vector2f(sin(gravPerpendicular) * gravPerMag * gravity * mass, -cos(gravPerpendicular) * gravPerMag * gravity * mass));
//this one rolls it down the incline
accelerateI(sf::Vector2f(gravParallell.x * gravParMag * gravity * mass, gravParallell.y * gravParMag * gravity * mass ));
//trying wether subtracting gravity helps
//accelerateI(sf::Vector2f(0, -1 * gravity * mass));
//friction
//accelerateI(sf::Vector2f(gravParallell.x * forceAcc.x * friction * mass, gravParallell.y * forceAcc.y * friction * mass));
collClock.restart();
}
}
Thanks guys

Detecting collision with known size and position on a rectangle

I want to detect collision between two rectangles where I know the size and position for both of them. I tried this c# code inside my "brick" class that uses the center of the player and the brick as reference:
public bool IsTouchingPlayer(Player player)
{
return (player.xPos > xPos - (width / 2) && player.xPos < xPos + (width / 2)) &&
(player.yPos > yPos - (height / 2) && player.yPos < yPos + (height / 2));
}
But sadly, this only accounts for the center of the player colliding with the brick; I think it's safe to say this is a problem.
If you need more info let me know.

Drawing the "Seed of Life" without redrawing anything

I have a mildly interesting problem which I can't quite figure out (although in fairness, I am pretty drunk)
The "Seed of Life" is a pattern created from drawing circles of equal radius, centred on the intersection of the previous circle.
Language doesn't really matter, the theory is more important here. Anything which can draw a circle will do it. For example, HTML5 + JS canvas can do it. It's a lovely example of how recursion can help solve problems.
The problem is that a naive approach will end up redrawing many, many circles. With 7 layers, you'll end up with over 300,000 circle draws.
A simple approach is to maintain a list of previous circle centre points, and only draw circles which are not in that list.
My question is whether there's a "better" way to approach this? Something which doesn't require checking that list.
A fun problem to ponder.
I think I have this solved thanks to a friend. I'll post here what I'm doing now in case someone ever is curious.
In short, starting from the center and working out, calculate the vertices of a hexagon, and subdivide each edge of the hexagon into i number of places, where i is the layer number.
I drew it in C# using SkiaSharp, but the code is nothing special to the language, there's no reason this couldn't be written in any language. Here's the significant bits:
const float seedAngle = (float)(Math.PI / 3.0);
static void SeedOfLifeDemo(int x, int y) {
//setting up Skia stuff, this will be different depending what language you're using.
var info = new SKImageInfo(x, y);
using var bitmap = FlatImage(info, SKColors.White);
SKCanvas canvas = new SKCanvas(bitmap);
float radius = Math.Min(x, y) / 15;
SKPoint center = new SKPoint(x / 2f, y / 2f);
SKPaint strokePaint = new SKPaint {
Color = SKColors.Black,
Style = SKPaintStyle.Stroke,
StrokeWidth = 1,
IsAntialias = true,
};
int layers = 4;
//Draw the very central circle. This is just a little easier than adding that edge case to SubdividedHexagonAboutPoint
canvas.DrawCircle(center, radius, strokePaint);
for (int i = 1; i <= layers; i++) {
foreach (SKPoint p in SubdividedHexagonAboutPoint(center, radius * i, i)) {
canvas.DrawCircle(p, radius, strokePaint);
}
}
SaveImage(bitmap, "SeedOfLifeFastDemo.Jpg");//More Skia specific stuff
}
//The magic!
static List<SKPoint> SubdividedHexagonAboutPoint(SKPoint centre, float radius, int subdivisions) {
List<SKPoint> points = new List<SKPoint>(6 * subdivisions);
SKPoint? prevPoint = null;
for (int i = 0; i < 7; i++) {//Step around the circle. The 7th step is to close the last edge
float x = (float)(Math.Sin(seedAngle * i) * radius + centre.X);
float y = (float)(Math.Cos(seedAngle * i) * radius + centre.Y);
SKPoint point = new SKPoint(x, y);
if (prevPoint != null) {
points.Add(point);//include the "primary" 6 points
if (subdivisions > 0) {
float xDist = (point.X - prevPoint.Value.X) / subdivisions;
float yDist = (point.Y - prevPoint.Value.Y) / subdivisions;
for (int sub = 1; sub < subdivisions; sub++) {
SKPoint subPoint = new SKPoint(point.X - xDist * sub, point.Y - yDist * sub);
points.Add(subPoint);//include the edge subdivisions
}
}
}
prevPoint = point;
}
return points;
}
This is quite an interesting exercise really, and another example of where recursion can really bite you when used badly.

When rotating 2D sprite towards cursor via angularVelocity, it spins at one point

Intro
I've created a spaceship sprite in my Unity project, I wanted it to rotate towards the cursor via angular velocity, because I'd like make my game to be heavily physics based.
Problem
Now my problem with rotating the sprite via by angular velocity is the following:
At -180° / 180° rotation my ship spins around, because while my mouse's angle is already 180°, while my ship's rotation is still -180°, or the other way around.
I tried
I tried to solve it mathematically, wasn't too successful, I could make it spin the right way just much slower/faster, I could fix the 180/-180 point, but made two different ones instead.
Looked for different solutions, but couldn't find a more fitting one.
Code
So I have this code for the rotation:
// Use this for initialization
void Start () {
rb = gameObject.GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void Update () {
//getting mouse position in world units
mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
//getting the angle of the ship -> cursor vector
angle = Mathf.Atan2(mousePos.y - transform.position.y, mousePos.x - transform.position.x) * Mathf.Rad2Deg;
//getting the angle between the ship -> cursor and the rigidbody.rotation vector
diffAngle = angle - (rb.rotation + 90);
//Increasing angular velocity scaling with the diffAngle
rb.angularVelocity = diffAngle * Time.deltaTime * PlayerShipStats.Instance.speed * 100f;
Thank you for your contribution in advance
Solution for Problem 1
Inserting this code made it work, not for long :
if(diffAngle > 180) {
diffAngle -= 360;
} else if (diffAngle < -180) {
diffAngle += 360;
}
Problem 2 and Solution for Problem 2
The new problem is:
rigidbody.rotation can exceed it's boundaries, it can be rotated for more than 360 degrees.
this code patched this bug:
if(rb.rotation + 90 >= 180) {
rb.rotation = -270;
} else if (rb.rotation + 90 <= -180) {
rb.rotation = 90;
}
The perfect code
void AimAtTarget(Vector2 target, float aimSpeed) {
//getting the angle of the this -> target vector
float targetAngle = Mathf.Atan2(target.y - transform.position.y, target.x - transform.position.x) * Mathf.Rad2Deg;
if (rb.rotation + 90 >= 180) {
rb.rotation = -270;
} else if (rb.rotation + 90 <= -180) {
rb.rotation = 90;
}
//getting the angle between the this -> target and the rigidbody.rotation vector
float diffAngle = targetAngle - (rb.rotation - 90);
if (diffAngle > 180) {
diffAngle -= 360;
} else if (diffAngle < -180) {
diffAngle += 360;
}
//Increasing angular velocity scaling with the diffAngle
rb.angularVelocity = diffAngle * Time.deltaTime * aimSpeed * 100;
}
There are two problems I see here:
Problem 1
angle is always going to be between -180 and 180, while rb.rotation is between 0 and 360. So you are comparing angles using two different notations. The first step is to get both angles returning -180 to 180 or 0 to 360. I chose to do the following which puts both angles between -180 and 180:
//getting the angle of the ship -> cursor vector
float targetAngle = Mathf.Atan2(
mousePos.y - transform.position.y,
mousePos.x - transform.position.x) * Mathf.Rad2Deg;
//get the current angle of the ship
float sourceAngle = Mathf.Atan2(
this.transform.up.y,
this.transform.up.x) * Mathf.Rad2Deg;
Problem 2
If you fix problem 1 and tried your app you would notice that the ship sometimes rotates the wrong way, although it will eventually get to its target. The problem is that diffAngle can sometimes give a result that is greater than +180 degrees (or less than -180). When this happens we actually want the ship to rotate the other direction. That code looks like this:
//getting the angle between the ship -> cursor and the rigidbody.rotation vector
float diffAngle = targetAngle - sourceAngle;
//use the smaller of the two angles to ensure we always turn the correct way
if (Mathf.Abs(diffAngle) > 180f)
{
diffAngle = sourceAngle - targetAngle;
}
I made a simple Unity to verify this works. I was able to rotate my ship in either direction smoothly.
One thing you may have to handle, if you don't already, is appropriately stopping the rotation of the ship when the it is facing the cursor. In my test I noticed that the ship would jitter slightly when it reached its target because it would (very) slightly overshoot the cursor's angle in one direction and then the other. The larger the value of PlayerShipStats.Instance.speed the more pronounced this effect will likely be.

Random CGPoint(s)

How could I get a random CGPoint that is outside the screen boundaries (frame)?
Also, given that point, how could I find a symmetrical point to it to the middle of the screen- e.g. say I have the point (width+1,height+1). Now the symmetrical point is (-1,-1). Say I have (-1,height +1)- symmetrical would be (width+1,-1).
Hope this is clear, and thanks!
If I understand your question correctly, you can use the following method:
- (CGPoint) randomPointIn:(CGRect)inrect outsideOf:(CGRect)outrect
{
CGPoint p;
do {
p.x = inrect.origin.x + inrect.size.width * (float)arc4random()/(float)UINT32_MAX;
p.y = inrect.origin.y + inrect.size.height * (float)arc4random()/(float)UINT32_MAX;
} while (CGRectContainsPoint(outrect, p));
return p;
}
It returns a random point that is inside inrect, but outside of outrect.
(I have assumed that inrect is "considerably larger" than outrect,
otherwise many loop iterations might be necessary to find a valid point.)
In your case, you would use outrect = CGRectMake(0, 0, width, height),
and inrect would specify the allowed domain.
And the point symmetrical to (x, y) with respect to the middle of the screen
with size (width, height) is (width - x, height - y).
UPDATE: As I just found here: http://openradar.appspot.com/7684419,
CGRectContainsPoint will return false if you provide it a point that is on the boundary of the CGRect. That means that the above method returns a point that is outside of
or on the boundary of the given rectangle outrect. If that is not desired,
additional checks can be added.
I believe this should work.
//To get a random point
- (CGPoint)randomPointOutside:(CGRect)rect
{
// arc4random()%(int)rect.size.width
// This gets a random number within the width of the rectangle
//
// (arc4random()%2) ? rect.size.width : 0)
// This has a 50:50 to put the point in the q1 quadrant relative to the top right point of the rect
//
// q4 q1
// _____ +
// | |
// | q3 | q2
// |_____|
//
float x = arc4random()%(int)rect.size.width + ((arc4random()%2) ? rect.size.width : 0);
float y = arc4random()%(int)rect.size.height + ((arc4random()%2) ? rect.size.height : 0);
return CGPointMake(x, y);
}
//To get the symmetrical point
- (CGPoint)symmetricalPoint:(CGPoint)p around:(CGRect)rect
{
return CGPointMake((p.x-rect.size.width) * -1, (p.y-rect.size.height) * -1);
}

Resources