Interpolate Up Vector Linearally when Up isn't truly Up? - c#

Overview
I've been looking around for a while and haven't found an answer so hopefully the community here can help me out. I am re-working my look-at camera (written pre 2000) and am having trouble getting rid of an issue where the look-at and up vectors become aligned causing the camera to spin wildly out of control. I originally understood this to be gimbal lock, but now I'm not so sure of that.
From my understanding of gimbal lock, when pitch becomes aligned with roll, pitch becomes roll; and in essence this is what it appears to be, but the problem is that the rate of change shouldn't increase just because the axes become aligned, I should just get a smooth roll. Instead I get a violent roll in which I can't really tell which way the roll is going.
Updating the Camera's Position
When the user moves the mouse I move the camera based on the mouse's X and Y coordinates:
Vector2 mousePosition = new Vector2(e.X, e.Y);
Vector2 delta = mousePosition - mouseSave;
mouseSave = mousePosition;
ShiftOrbit(delta / moveSpeed);
Within the ShiftOrbit method, I calculate the new position based on the look-at, right, and up vectors in relationship to the delta passed from the mouse event above:
Vector3 lookAt = Position - Target;
Vector3 right = Vector3.Normalize(Vector3.Cross(lookAt, Up));
Vector3 localUp = Vector3.Normalize(Vector3.Cross(right, lookAt));
Vector3 worldYaw = right * delta.X * lookAt.Length();
Vector3 worldPitch = localUp * delta.Y * lookAt.Length();
Position = Vector3.Normalize(Position + worldYaw + worldPitch) * Position.Length();
This works smoothly as it should and moves the camera around its target in any direction of my choosing.
The View Matrix
This is where I experience the problem mentioned in the overview above. My Up property was previously set to always be 0, 0, 1 due to my data being in ECR coordinates. However, this is what causes the axis alignment as I move the camera around and the view matrix is updated. I use the SharpDX method Matrix.CreateLookAtRH(Position, Target, Up) to create my view matrix.
After discovering that the Up vector used when creating the view matrix should be updated instead of always being 0, 0, 1, I encountered another issue. I now caused roll when yaw and pitch were introduced. This shouldn't occur due to a requirement so I immediately began pursing a fix.
Originally I performed a check to see if was coming close to being axis aligned, if I was, then I set the Up used to create my view matrix to the local up of the camera, and if I wasn't then I used only the Z axis of the local up to ensure that up was either up or down.
float dot = Math.Abs(Vector3.Dot(Up, Position) / (Up.Length() * Position.Length()));
if (dot > 0.98)
Up = localUp;
else
Up = new Vector3(0, 0, localUp.Z);
However, this was a bit jumpy and still didn't seem quite right. After some trial and error, along with some extensive research on the web trying to find potential solutions, I remembered how linear interpolation can transition smoothly from one value to another over a period of time. I then moved to using Vector3.Lerp instead:
float dot = Math.Abs(Vector3.Dot(Up, Position) / (Up.Length() * Position.Length()));
Up = Vector3.Lerp(new Vector3(0, 0, localUp.Z), localUp, dot);
This is very smooth, and only causes any roll when I am very near to being axis aligned which isn't enough to be noticeable by the every day user.
The Problem
My camera also has the ability to attach to a point other than 0, 0, 0, and in this case, the up vector for the camera is set to the normalized position of the target. This causes the original issue in the overview when using Vector3.Lerp as above; so, in the case where my camera is attached to a point other than 0, 0, 0 I do the following instead:
Up = Vector3.Lerp(Vector3.Normalize(Target), localUp, dot);
However, even this doesn't work and I have no idea how to get it to do so. I've been working at this problem for a few days now and have made an extensive effort to fix it, and this is a big improvement so far.
What can I do to prevent the violent spinning using Vector3.Lerp when the up isn't equivalent to 0, 0, z?

Imagine a vertical plane that is rotated around the vertical axis by yaw (ϕ):
The camera is only allowed to rotate with the plane or in the plane, its in-plane orientation given by the pitch (θ):
ϕ and θ should be stored and incremented with the input delta. With this setup, the camera will never tilt, and the local up direction can always be computed:
d and u are the local front and up directions respectively, and are always perpendicular (so alignment won't be an issue). The target can of course be taken as the position + d.
But wait, there's a catch.
Suppose if you move your mouse to the right; ϕ increases, and you observe:
If the camera is upright, the view rotates to the right.
If the camera is upside-down, the view rotates to the left.
Ideally this should be consistent regardless of the vertical orientation.
The solution is to flip the sign of increments to ϕ when the camera is upside down. One way would be to scale the increments by cos(θ), which also smoothly reduces the sensitivity as θ approaches 90 / 270 degrees so that there is no sudden change in horizontal rotational direction.

Related

Unity Aim Assist

i am creating a 3d top down shooter with unity and wanted to create an aim assist for it since hitting enemies is difficult. my idea was to create a trigger that slows down the rotation of the joystick on the weapon when the player aims at an enemy. i implement my rotation as follows:
float eulerY = (Mathf.Atan2(_JoystickShoot.Direction.x, _JoystickShoot.Direction.y) * 180 / Mathf.PI);
However, I don't know how to implement the slowdown now. The player should rotate slower than the actual rotation of the joystick.
My approaches have not really been successful so far.
does anyone have any idea?
thanks :)
You can add a variable that scales the rotation. Set it to 1, except when you're pointing at an enemy, where you set it to a value between 0 and 1 that feels good for you.
float eulerY = rotationScaling * (Mathf.Atan2(_JoystickShoot.Direction.x, _JoystickShoot.Direction.y) * 180 / Mathf.PI);
If you want to draw the crosshair towards an enemy, you can calculate the angle (or distance) to the closest enemy. If it is below a certain threshold you can adjust your rotation function to favor rotations towards the enemy over rotations away from it. You can use something like this:
float eulerY = (Mathf.Atan2(_JoystickShoot.Direction.x, _JoystickShoot.Direction.y) * 180 / Mathf.PI);
if (closest enemy is within threshold range)
if (eulerY * (difference in Y angle to closest enemy) < 0) // if we rotate away from the enemy we scale the rotation down
eulerY *= 0.5;
else // if we rotate towards the enemy we scale it up
eulerY *= 1.5;
You can adjust this for multiple angles as well, but just doing the same calculation for X and Y direction might feel a bit awkward. The reason is that in that case you have to compare it against the "true" angle/distance, not against the X and Y angles/distances individually.
I think it is more useful to talk about 'direction' rather than 'rotation'.
Presumably your ship travels and/or shoots in the same direction as the joystick is pointing in. This is a natural and intuitive relationship. "slowing down rotation" will most likely break this, making your interface less intuitive to use. There are also usually ways to go directly from a direction-vector to a rotation, for example Quaternion.LookRotation, avoiding the need to deal angles directly.
You could instead check if there is any enemy within a small arc, select the enemy closest to the center of the arc, and use the direction towards that enemy to fire in. Note that this can fail if enemies are moving and shots are slow, since it can prevent 'leading' your shots.
Other alternatives could be to simply make your enemies easier to hit. Make the enemies or shots larger, or possibly, just make the hitboxes larger. Or increase fire-rate and/or dispersion.

Set direction of rotation Unity C#

I am trying to make a spin wheel that is divided into 6 sections. Each section is marked with a gameObject that is centered in that section. After the player spins the wheel, it will rotate until its starts stopping and then the wheel moves and stops in the center based on the section that was selected (Randomly). I used the following code to rotate the wheel towards the 0 on X axis. this code works fine and the wheel rotates fine, but only if the selected section was on the positive X axis.
float rotateFloat = (((randomReward + 1) * 60) - 30) - transform.rotation.z;
Quaternion targetRotation = Quaternion.Euler(new Vector3(0, 0, rotateFloat));
transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, f_difX * Time.deltaTime);
I did some digging and found that Quaternion.RotateTowards()finds the closest way towards the target and rotates using that direction (This caused a problem with the direction of rotation).
Example (Follow image): The player swipes and randomReward (Number 5 on spin wheel) and the wheel starts rotating. When the wheel slows down, it starts moving towards the center. Then it will stop spinning along the direction of the swipe and will rotate towards the other side (Because the distance to the center is closer from the left side).
How can I set it to follow the same direction of the swipe regardless of which is closer to the center. And maybe if there is a better way to do this, please enlighten me. Thanks.
I think the easiest way to rotate a GameObject is by using:
float Speed = 1f;
void Update()
{
// Rotate the object around its local X axis at 1 degree per second
transform.Rotate(Vector3.right * Time.deltaTime * Speed);
}
You can read more about his here
It can happen sometimes the center of the GameObject it´s not placed in the center of the mesh or sprite. If you can´t modify this for any reason, what you can do is place this model/sprite as a child of an Empty GameObject and then attach the rotation script to the Empty GameObject.
While i understand that you don't want people to rotate the disk to the reward they want. Why do you use a random reward and go through the trouble of lining the rotation to the reward?
You should be able to say catch a 'swipe time', then do some math to it (say so it turns at least once if the screen touch time is very short) and then add a random range to it, that is within the circumference of the disk. Then when the disk is done spinning do a ray cast at the top location to determine the reward? (could also link the spinning time to the swipe-time, so that the reward is offered in somewhat the same time interval).
//this comment would have been made as a comment if i would have had the rights to do so, as i think this response may help the question asker, it is provided as an answer instead. i do hope this doesn't piss any one off to much :(

How do I rotate an object to a specific position around another object? (Essentially orbiting to specified position)

I have a planet and a moon. The moon is parented to a dummy object, which is located at the center of the planet (essentially 0,0,0). The moon is free to rotate around (to any position on) the planet. It should maintain a constant distance from the planet.
I have a specific point in mind that I want to rotate the moon to, though it needs to stay upright. That is, the moon should consistently point "up" relative to the planet's surface. Basically it's like a "moveTo" script only in my case the moon should "rotate" around the planet until it reaches the point I'm looking for.
Here's what I have so far, though I can't work out the correct logic to use:
Vector3 targetDir = moveToPos - moon.transform.position;
float angle = Vector3.Angle( targetDir, moon.transform.up );
dummy.transform.RotateAround (moveToPos, moon.transform.up, angle);
Am I thinking of this correctly? Once I get this working, I'd like to feed the moon different Vector3 positions and have the moon rotate to them on the surface of the planet. I have searched for something similar but can't find what I'm looking for.
The marker displayed in this screenshot should say "Rotate here", but this is essentially what my scene looks like:
You've already made things a lot easier by nesting your moon inside an empty transform. If it's properly set up*, this means you won't have to directly manipulate the moon's transform - you just need to rotate the container object until it faces the target position.
*By this, I mean the container object is at (0, 0, 0) relative to the planet, and the moon is only locally translated along the z-axis so it lines up with the container's transform.forward vector.
The problem is easier to approach if we break it down into smaller steps:
Determining the target direction the container needs to face. We can get this by just subtracting the container's position from the target position.
Calculating the required rotation for the container to face the target direction. This is a good place for Quaternion.LookRotation().
Rotating the container until its direction matches the target direction. Quaternion.Lerp() can be used to achieve this.
Here's how you might implement these steps:
Quaternion targetRotation;
Quaternion startRotation;
float progress = 1;
void SetTargetPosition(Vector3 position)
{
// Calculating the target direction
Vector3 targetDir = position - dummy.transform.position;
// Calculating the target rotation for the container, based on the target direction
targetRotation = Quaternion.LookRotation(targetDir);
startRotation = dummy.transform.rotation;
// Signal the rotation to start
progress = 0;
}
void Update()
{
if (progress < 1)
{
// If a rotation is occurring, increment the progress according to time
progress += Time.deltaTime;
// Then, use the progress to determine the container's current rotation
dummy.transform.rotation = Quaternion.Lerp(startRotation, targetRotation, progress);
}
}
Note: If the moon is rotating too quickly (it will currently complete the rotation in ~1 second), just add less to progress every frame, eg. Divide it by a factor.
As long as the container is centered in the planet, the moon should remain a constant distance away from the surface at all times, and by this method will always keep a consistent "up" direction relative to the planet's surface.
Hope this helps! Let me know if you have any questions.

Breakout paddle collision with angle

I know there's so many stuff on the internet about this, and I have looked at quite a lot of it, but I just can't get it to work with my code. I kinda know the maths behind it, but again struggling to get it into code.
I have 2 speed variables for the ball float xSpeed, ySpeed. Both are equal to 3 (or -3 depending on collision - I just have basic collision atm). I'm also using a rectangle for ball ballRect as well as the paddle paddleRect.
I'm updating the position as so:
ballRect.X += xSpeed;
ballRect.Y += ySpeed;
I've found bits of code, and tried doing it myself, but they just were really buggy. One would (I think) work on the first hit, but when it came to the next hit it would stick to the paddle.
double relativeBallPos = (paddleRect.X + ballRect.X);
double ballVelx = xSpeed;
double ballVely = ySpeed;
double angleRads = Math.Tan((ballVelx / relativeBallPos));
double angleInDeg = angleRads * (180 / Math.PI);
double angleOfReflection = (angleInDeg * 2);
ballVelx = ballVelx * angleOfReflection;
if (ballRect.X + (ballRect.Width / 2) < paddleRect.X + (paddleRect.Width / 2))
{
xSpeed = (float)-ballVelx;
}
else if (ballRect.X + (ballRect.Width / 2) > paddleRect.X + (paddleRect.Width / 2))
{
xSpeed = (float)ballVelx;
}
(This goes off at a (probably incorrect) angle and just goes straight up after the first hit)
Thanks for any help :)
If your ball intersects any horizontal threshold (the horizontal sides of the bricks, or of the paddle), negate ySpeed. If it intersects any vertical threshold, negate xSpeed. There, now you have a functional breakout game.
You can add a bit more polish by changing the angle of the ball when it hits the paddle based on the position of the collision along it, with center being a full reflect (ie negate ySpeed) and xSpeed getting a factor of the distance (positive or negative) of the distance from the center.
Yes, what you are seeing is completely normal, if your objects are moving very fast and game tick speed is slow, you may have glitches like sticking to the paddle or even missing the paddle completely, and just going straight through.
The solution to missing the paddle is increase game tick speed, i.e. process game moves more frequently.
Ball to paddle stickiness can be alleviated by doing a roll back of object movement in time. For example you have objects A and B, which are colliding at some point in time. If you simply reverse their X and Y speeds, you may end up with colliding again in the next point in time, so you would then reverse their speeds again, and so on, which appears as though it's stuck. It can sometimes get stuck on one only axis, so it will slide on the paddle, and then go straight down, which is just another side effect of the same issue.
How to do a roll back? You know where A is moving, and suppose B is a paddle, so it's static. You know that A is colliding with B at the moment. You can calculate a vector of A's movement and slide A back in time through that vector reversed. For example, if top left corner of the screen is (0,0), and A was moving at the speed of X=+10,Y=+10, you would move it back by whole or fraction of the step (depends on how complex you want to go with this), so by (-10,-10) or fraction thereof. You can use intersection rectangle to calculate precise values, and only move enough so the objects are barely touching.
Ideally, you would reflect the vector off the hit surface (use physics, i.e. hit angle = reflect angle) and before applying new speeds, move your ball in a way that it does not collide with paddle. To simplify, you can assume your hit surface is horizontal and is just a straight line, so your reflection formula is very simple.
This topic is well covered here (although using XNA, same principle applies):
Stuck in Wall after rectangle bounding box collision

Scale Sprite up and Down to give illusion of a jump

I have some code that I wrote that works, but I feel it could be better and wanted to get some feedback.
The goal I had is to have a Sprite Scale up and back down in a timely fashion when a button is pushed so that it gives the illusion of jumping in a "Top Down" view of the game. Like the character is jumping off the screen. I already know how to draw scaled images I'm more interested in the logic of the timing aspect.
This works, just not sure it's the best. Thought maybe there was some equation, a math friend told me maybe a linear equation or like a parabola or second order equation. Not being great with math.
Anyway.
Class Properties
private double _jumpingStartedAt;
private double _totalJumpTimeInSeconds = 0.7;
private double _totalJumpFrames = 14;
private double _timeSinceLastScale;
private double _jumpingHalfWayAt;
When button is pushed for the first time I start the "Jump Logic". This runs once per jump. My thought was that I'd mark the "start" time and determine the "halfway" time by the totalJumpTimeInSeconds.
_jumpingStartedAt = gameTime.TotalGameTime.TotalSeconds;
_jumpingHalfWayAt = _jumpingStartedAt + MillisecondsBetweenFrame() * (_totalJumpFrames / 2);
And then this is run on each Update() until my "jump" is complete or isJumping = false. The logic here is that I would scale up every 1 "frame" until half way point then scale back down.
_timeSinceLastScale += gameTime.ElapsedGameTime.TotalSeconds;
if (_timeSinceLastScale > MillisecondsBetweenFrame() && gameTime.TotalGameTime.TotalSeconds < _jumpingHalfWayAt)
{
Scale += 0.2f;
_timeSinceLastScale = 0;
}
else if (gameTime.TotalGameTime.TotalSeconds > _jumpingHalfWayAt)
{
Scale -= 0.2f;
if (Scale < 1.0) Scale = 1; //probably don't need this was worried if it went passed 0
if (Scale == 1.0) _isJumping = false;
}
private double SecondsBetweenFrame()
{
return _totalJumpTimeInSeconds / this._totalJumpFrames;
}
Now this works, but seems a little convoluted to me.
Stretching image when jumping - side view
Yeah, it's pretty complicated, what you created.
I assume your sprite is also moving up and down when jumping. That you have some sort of Vector2 velocity, which you change by dv = gravityAcceleration * dt in every update, and so you change Vector2 position by dp = velocity * dt. If so, I would rather use my velocity.Y value to calculate how the sprite should stretch. I think it's more natural. And your code will become much more simple.
Here's an image to describe better what I mean:
However, you can probably face the other problem here: just at the beginning of the jump your sprite will suddenly get high velocity, when still being near the ground, which can cause it to cross through the floor for a moment. To prevent that you can artificially move your sprite upwards by the smallest needed value for the time of jump. The problem is described by the image below:
As you can very well see, the first stretched ball moved upwards a little bit, but not enough. You have to calculate difference between sizes before and after stretching and then move your sprite up by that distance.
If you do it like that, your Update should shorten to just a few lines. I believe you can do simple calculations on your own.
Easier approach
...Unless you'd rather like your sprite behave like you want. Then you could modify scale according to your Y position:
if (KeyboardState.IsKeyDown(Keys.Space))
{
isJumping = true;
jumpStartPosition = Position;
}
if (!isJumping) Scale = 1f;
else
{
Scale = StretchFactor * (Position.Y - jumpStartPosition.Y);
}
where:
- isJumping is a bool,
- jumpStartPosition is a Vector2,
- Position is a Vector2 property of your sprite,
- StretchFactor is a float property of your sprite telling how much does it stretch.
And you also need to have end-of-jump condition - for example when the sprite's Position.Y becomes smaller than the jumpStartPosition.Y. But generally this solution (as well as yours) has one disadvantage - there will be problems, if you will want to start jump from one height, and end it on another:
so I would rather recommend my first solution. There you can make stop-jump condition by collision check.
Stretching image when jumping - top-down view
Bummer. Since originally it wasn't specified that it is a top-down game, like those first GTA's, I really misunderstood the question, so the answer doesn't fit much. So the answer goes now.
If you wan't it to be realistic, you should use some basic principles of perspective. As we look at the character jumping from the top, it goes closer to us, so it's image grows. Why's that? Look at the pic below.
There are two things, that are needed for perspective to work: the center of perspective and the screen. The center of perspective is the point, where all "rays" are crossing. "Ray" is a line from the any point in the world to the center of our eye. Now the screen is the plane, where image of 3d world is being created. The points of the real world are being cast into screen along their rays. Of course your game is pseudo-3d, but it shouldn't matter in that case.
When z grows, sprite comes closer to the center of perspective. If you imagine ray from the center of perspective to the edge of the sprite, the angle of ray changes, as it's distance to the center of perspective becomes lesser. And the change of angle makes the point's image on the screen moving. That's why image grows, or becomes smaller.
Now we can wonder: ok, how now put this into numbers? Look at the picture below:
I deliberately translated whole world by -C so the z coord of the center of perspective could become 0. That makes calculations simplier. What are we trying to find, is the x' - coord of the point on the screen. Let the Z* = |z - C|. If we look at this picture it becomes clear, that we can find what we need by pretty simple proportion:
Using the same method you can calculate y'. If your character is always at the center of the screen, all that you need will be x'/x = y'/y = S, i.e. your scale. That's because x in this scenario is, in fact, the half-width of the sprite, and y is the half-height. However, if your character will be able to move freely around the screen, you may want to scale & translate it, so it would be more natural:
The white square is the on-the-ground sprite, the gray square is the jumping sprite. In this case you will have to know l (left), r (right), t (top) and b (bottom) coords of the sprite's boundaries (top-bottom means Y-axis, not Z-axis). Then using the same proportion you can get l', r', t' and b' - boundaries of the sprite's image on screen. From this data you should be able to calculate both scale and translation.
Note: L is the parameter of our calculation which you have to choose yourself. Assuming, that the screen has constant width Ws and height Hs, L strictly corresponds with FOV (field of view). You can acquire it also using proportions. So L = (cos(FOV/2) * Ws)/2. I would recommend FOV = 60 deg. If you will make FOV too big, you may face the fisheye problem.

Categories

Resources