I am encountering an issue when updating a bullet's position when I rotate the ship that shoots it. Currently the code almost works except that at most angles the bullet is not fired from where i want it to. Sometimes it fires slightly upward than the centre where it should be, and sometimes downwards. I'm pretty sure it has something to do with the trigonometry but I'm having a hard time finding the problem.
This code is in the constructor; it sets the bullet's initial position and rotation based on the ship's position (sp.position) and its rotation (Controls.rotation):
this.backgroundRect = new RotatedRectangle(Rectangle.Empty, Controls.rotation);
//get centre of ship
this.position = new Vector2(sp.getPosition().X + sp.getTexture().Width/2,
(sp.getPosition().Y + sp.getTexture().Height / 2));
//get blast pos
this.position = new Vector2((float)(this.position.X + (sp.getTexture().Width / 2 *
Math.Cos(this.backgroundRect.Rotation))), (float)(this.position.Y +
(sp.getTexture().Width / 2 * Math.Sin(this.backgroundRect.Rotation))));
//get blast pos + texture height / 2
this.position = new Vector2((float)(this.position.X + (this.texture.Height / 2 *
Math.Cos(this.backgroundRect.Rotation))), (float)(this.position.Y +
(this.texture.Height / 2 * Math.Sin(this.backgroundRect.Rotation))));
this.backgroundRect = new RotatedRectangle(new Rectangle((int)this.position.X,
(int)this.position.Y, this.texture.Width, this.texture.Height), Controls.rotation);
speed = defSpeed;
Then in the update method I use the following code; I'm pretty sure this is working fine though:
this.position.X = this.position.X + defSpeed *
(float)Math.Cos(this.getRectangle().Rotation);
this.position.Y = this.position.Y + defSpeed *
(float)Math.Sin(this.getRectangle().Rotation);
this.getRectangle().CollisionRectangle.X = (int)(position.X + defSpeed *
(float)Math.Cos(this.getRectangle().Rotation));
this.getRectangle().CollisionRectangle.Y = (int)(position.Y + defSpeed *
(float)Math.Sin(this.getRectangle().Rotation));
Also I should mention that my ship had not been working correctly when I rotated it since the origin (center of rotation) was (0,0). To remedy this I changed the origin to the center of the ship (width/2,height/2) and also added those values to the drawing rectangle so that the sprite would draw correctly. I imagine this could be the problem and suppose there's a better way of going around this. The game is in 2D by the way.
The problem is that the sprites are not drawn on the positions they are supposed to be. When calculating the center position, you assume that the sprite is not rotated. This causes some trouble. Furthermore, rotating an arbitrary vector is not as easy as you did. You can only rotate a vector by multiplying its components with sin/cos when it is on the x-axis.
Here is how you can rotate arbitrary vectors:
Vector2 vecRotate(Vector2 vec, double phi)
{
double length = vec.Length(); //Save length of vector
vec.Normalize();
double alpha = Math.Acos(vec.X); //Angle of vector against x-axis
if(vec.Y < 0)
alpha = 2 * Math.PI - alpha
alpha += phi;
vec.X = Math.Cos(alpha) * length;
vec.Y = Math.Sin(alpha) * length;
return vec;
}
Make sure, the angle is in radians.
With that you can calculate the correct position of the ship:
Vector2 ShipCenter = sp.Position() + vecRotate(new Vector2(sp.getTexture().Width/2, sp.getTexture().Height/2), Controls.Rotation);
You can use the same function to determine the position of the bullet's center. I don't exactly know, where you want to place the bullet. I will assume, it is at the center of the right edge:
Vector2 offset = new Vector2(sp.getTexture.Width/2, 0);
this.position = ShipCenter + vecRotate(offset, Controls.Rotation);
If you then need the position of the upper left corner of the bullet, you can go further:
this.position -= vecRotate(new Vector2(this.texture.Width/2, this.texture.Height/2), Controls.Rotation)
However, as I stated, it is probably easier to handle the ship's and bullets' positions as central positions. Doing so, you need far less trigonometry.
Related
I have a cylinder that moves with Translate, I need the platform to always be near the center of the cylinder. As in the photo below.
.
To do this, I wrote a script that calculates how far the cylinder has moved, and adds this number to the platform scale and adds this number divided by two to the platform Z position.
if (transform.position.z > spawnedBlocks[spawnedBlocks.Count - 1].transform.position.z)
{
zDistance = transform.position.z - spawnedBlocks[spawnedBlocks.Count - 1].transform.position.z;
spawnedBlocks[spawnedBlocks.Count - 1].transform.position = new Vector3(spawnedBlocks[spawnedBlocks.Count - 1].transform.position.x, spawnedBlocks[spawnedBlocks.Count - 1].transform.position.y, spawnedBlocks[spawnedBlocks.Count - 1].transform.position.z + (zDistance / 2));
spawnedBlocks[spawnedBlocks.Count - 1].transform.localScale = new Vector3(1, 0.1f, spawnedBlocks[spawnedBlocks.Count - 1].transform.localScale.z + zDistance);
}
This code is in my Fixed Update and it turns out that the platform moves much faster and goes forward.
The main mistake you make is in the positioning.
Cleaned up a bit your code looks somewhat like this:
var myPos = transform.position;
var block = spawnedBlocks[spawnedBlocks.Count - 1].transform;
var blockPos = block.position;
if (myPos.z > blockPos.z)
{
zDistance = myPos.z - blockPos.z;
block.position += Vector3.forward * zDistance / 2f;
block.localScale = new Vector3(1, 0.1f, block.z + zDistance);
}
But now have a careful look what you are doing here:
if the myPos.z is bigger than blockPos.z you will move it forward
Now wait a minute! Shouldn't it rather stay at the center between its original spot and the myPos?
Example:
Let's say both objects start at z=0.
Now the myPos moves to z=2 (just example numbers)
zDistance = 2
you move the block to current position + half of distance so 0 + 1
This frame you don't move at all (easier to explain this way)
myPos.z (2) is still bigger than blockPos.z (1)
so distance is now 1
you move the block to current position + half of distance 1 + 0.5
Etc. You see what you are doing is basically interpolating the block position towards the transform.position, not how it should be to the center between the original position and transform.position!
So one solution (I don't see your full code) would be to store the original z position and make sure you are always at the center between that original position and the current transform.position
block.position = originalBlockPosition + Vevtor3.forward * (transform.position - originalBlockPosition).z / 2f;
As mentioned way simpliest though would be to rather just create a parent for your object and make an offset this that the parent is exactly the pivot you want to scale around. Somewhat like
*-ParentPivot
[*****Your Block*****]
This way you can simply scale the parent and the block will always be stretched correctly! You would only have to make sure your block by default has a width of 1 unit the you could directly use something like
parent.localScale = new Vevtor3(1, 1, transform.position.z - parent.position.z);
So, here is what i have:
SteamVR's hand gameobject
3D sphere.
what i want:
The sphere to move to same direction/position as the hand does, but it to move further with a multiplier. E.g. i move the VR controller and the hand moves 1 unit. I want that the sphere moves to the same direction in a same amount of time but e.g. 2 units. how do i do this?
i tried simple
sphere.transform.position = ControllerToFollow.position +2f;
but then the sphere is always offset.
position is a Vector3, which is essentially 3 floats - you can't plus a Vector3 with a float unless you overload the + operator. Otherwise what you can do is the following:
Vector3 followPos = new Vector3(ControllerToFollow.position.x + 2f,
ControllerToFollow.position.y + 2f,
ControllerToFollow.position.z + 2f);
sphere.transform.position = followPos;
If you only want it to follow on one axis, then you can do the following:
Vector3 followPos = new Vector3(ControllerToFollow.position.x + 2f, // Follow on x Axis
ControllerToFollow.position.y, // Y axis is the same
ControllerToFollow.position.z); // X Axis is the same
sphere.transform.position = followPos;
Edit: I think I understand your problem better now. Here's a better version.
if (Vector3.Distance(sphere.transform.position, ControllerToFollow.position) >= 2f)
{
// Code that makes the sphere follow the controlling
}
Just track the movement Delta of the hand and multiply it by a certain multiplier.
At the beginning of the manipulation store
private Vector3 lastControllerPosition;
...
lastControllerPosition = ControllerToFollow.position;
then in every frame compare
var delta = ControllerToFollow.position - lastHandPosition;
// Don't forget to update lastControllerPosition for the next frame
lastControllerPosition = ControllerToFollow.position;
Now in delta you have a movement of the controller since the last frame. So you can assign it to the sphere with a multiplier using Transform.Translate
sphere.transform.Translate(delta * multiplier, Space.World);
or simply using
sphere.transform.position += delta * multiplier;
Example Image here
I am trying to find a way to calculate points on my cylinders top circle surface. My situation looks like this, I have a vector which is defining my cylinders direction in 3d room. Then I already calculated me a perpendicular vector with
Vector3.Cross(vector1, vector2)
Now I use the diameter/2 to calculate the point which is lying on the edge of the circular top surface of my cylinder. Now I want to rotate my vector always 90 degrees in order to get 4 points on the edge of the surface. All the 4 vectors defining them should be perpendicular to the cylinders direction. Can you help me how I can rotate the first perpendicular to achieve this?
I already tried:
Matrix4x4.CreateFromAxisAngle(vectorcylinderdirection, radiant)
Then I calculated again cross product but it doesnt work like I want to.
Edit:
public static void calculatePontsOnCylinder()
{
//Calculate Orthogonal Vector to Direction
Vector3 tCylinderDirection = new Vector3(1, 0, 0);
Vector3 tOrthogonal = Vector3.Cross(tCylinderDirection, new Vector3(-tCylinderDirection.Z,tCylinderDirection.X,tCylinderDirection.Y));
Vector3 tNormOrthogonal = Vector3.Normalize(tOrthogonal);
//Calculate point on surface circle of cylinder
//10mm radius
int tRadius = 10;
Vector3 tPointFinder = tNormOrthogonal * tRadius;
//tPointFinder add the cylinder start point
//not yet implemented
//now i need to rotate the vector always 90 degrees to find the 3 other points on the circular top surface of the cylinder
//don't know how to do this
// I thought this should do it
Matrix4x4.CreateFromAxisAngle(tCylinderDirection, (float)DegreeToRadian(90));
}
private static double DegreeToRadian(double angle)
{
return Math.PI * angle / 180.0;
}
In the picture you can see a example, the vector1 is what I need, always rotated 90 degrees and vector2 would be my cylinder direction vector
I possibly have found the correct formula:
Vector3 tFinal = Vector3.Multiply((float)Math.Cos(DegreeToRadian(90)), tPointFinder) + Vector3.Multiply((float)Math.Sin(DegreeToRadian(90)), Vector3.Cross(tCylinderDirection, tPointFinder));
Vector3 tFinal180 = Vector3.Multiply((float)Math.Cos(DegreeToRadian(180)), tPointFinder) + Vector3.Multiply((float)Math.Sin(DegreeToRadian(180)), Vector3.Cross(tCylinderDirection, tPointFinder));
Vector3 tFinal270= Vector3.Multiply((float)Math.Cos(DegreeToRadian(270)), tPointFinder) + Vector3.Multiply((float)Math.Sin(DegreeToRadian(270)), Vector3.Cross(tCylinderDirection, tPointFinder));
Interesting is that if I try it with (1,1,0) as cylinder direction it gives me correct directions but the length is different for 90 degrees and 270.
Here is the code that should solve your problem assuming that the input requirements are satisfied.
float zCutPlaneLocation = 20; // should not get bigger than cylinder length
float cylinderRadius = 100;
Vector3 cylinderCenter = new Vector3(0, 0, 0); // or whatever you got as cylinder center point, given as Vector3 since Point type is not defined
// will return 360 points on cylinder edge, corresponding to this z section (cut plane),
// another z section will give another 360 points and so on
List<Vector3> cylinderRotatedPointsIn3D = new List<Vector3>();
for (int angleToRotate = 0; angleToRotate < 360; angleToRotate++)
{
cylinderRotatedPointsIn3D.Add(GetRotatedPoint(zCutPlaneLocation, angleToRotate, cylinderRadius, cylinderCenter));
}
....
private static Vector3 GetRotatedPoint(
float zLocation, double rotationAngleInRadian, float cylinderRadius, Vector3 cylinderCenter)
{
Vector2 cylinderCenterInSection = new Vector2(cylinderCenter.X, cylinderCenter.Y);
float xOfRotatedPoint = cylinderRadius * (float)Math.Cos(rotationAngleInRadian);
float yOfRotatedPoint = cylinderRadius * (float)Math.Sin(rotationAngleInRadian);
Vector2 rotatedVector = new Vector2(xOfRotatedPoint, yOfRotatedPoint);
Vector2 rotatedSectionPointOnCylinder = rotatedVector + cylinderCenterInSection;
Vector3 rotatedPointOnCylinderIn3D = new Vector3(
rotatedSectionPointOnCylinder.X,
rotatedSectionPointOnCylinder.Y,
zLocation + cylinderCenter.Z);
return rotatedPointOnCylinderIn3D;
}
I just created a console app for this. First part of code should be added in main method.
Working with those matrices seems is not that easy. Also I am not sure if your solution works ok for any kind of angle.
Here the idea is that the rotated points from cylinder are calculated in a section of the cylinder so in 2D than the result is moved in 3D by just adding the z where the Z section was made on cylinder. I suppose that world axis and cylinder axis are on the same directions. Also if your cylinder gets along (increases) on the X axis, instead of Z axis as in example just switch in code the Z with X.
I attached also a picture for more details. This should work if you have the cylinder center, radius, rotation angle and you know the length of the cylinder so that you create valid Z sections on cylinder. This could get tricky for clockwise/counter clock wise cases but lets see how it works for you.
If you want to handle this with matrices or whatever else I think that you will end up having this kind of result. So I think that you cannot have "all" the rotated points in just a list for the entire cylinder surface, they would depend on something like the rotated points of a Z section on the cylinder.
I'm developing a simulation where player should be able to move around inside a 2D circle (referred to as sphere in my code). The players movement must be relative to the center of the circle.
My first step was to make sure the player always faces the center. I got the working fine. However when I tried to do the relative movement it doesn't give my quite the result I'm looking for.
When I move the player close to the center of circle and move sideways (which is relative to the player's facing vector), the player spins around the center but then slowly starts spiraling outwards. The outwards spiral is much more prominent near the center and takes about 8 orbits to reach the inner edge of the circle. Instead the player should be spinning around the center at a constant distance from the center. Why does the player spiral outwards?
Here is the code I use:
// center of the sphere
Vector3 center = sphereComponent.transform.position - player.transform.position;
// always rotate towards the center so that transform.up is
float angle = Vector3.Angle(center, Vector3.up);
float sign = (center.x < rigidbody.transform.position.x) ? 1.0f : -1.0f;
rigidbody.MoveRotation(angle * sign);
// use the input vector to calculate a vector relative to the objects right and up vectors
Vector2 relativeInputVector =
(rigidbody.transform.right * player.InputVector.x) +
(rigidbody.transform.up * player.InputVector.y);
// below is same as doing: rigidbody += relativeInputVector.normalized * 20 * Time.deltaTime;
rigidbody.MovePosition(rigidbody.position + (relativeInputVector.normalized * 20 * Time.deltaTime));
So I've tried a few things already:
I thought it was maybe a rounding issue. So I rounded the relativeInputVector's X and Y to the 2nd decimal place. Didn't help.
I normalized the relativeInputVector vector. Didn't seem to do much...
I also thought maybe I should move and then rotate instead of rotate then move. Didn't work.
Now I'm thinking the issue is somewhere in the math (probably where I define relativeInputVector) but I can't find simular use cases regarding this so that I can compare and troubleshoot.
(this is a rather saturated topic when it comes to the keywords I'm search with)
Your intuition would make sense if you were moving to the side then adjusting the direction of your forward vector simultaneously and continuously, but it's being done alternating and discretely.
Consider what happens if Time.deltaTime was absolutely enormous for one frame. You would sidestep a huge amount, maybe even going off the screen in one direction, and then you would adjust your angle to face the center of the circle. That's an exaggerated example but its exactly what's happenening on a small scale.
Here's a diagram showing why your code spirals out:
The way you're doing it, The angle between the circle's radius to the player's position at the beginning of the frame (A in the diagram) and the direction the rigidbody moves (1->2 in the diagram) is a right angle. At position 1, the radius A might be the correct distance, but the hypotenuse of a right triangle is always longer than each leg, so the new radius at position 2 (B) must be larger, and likewise, C must be larger than B.
The result of that is a spiral motion as you continue to accumulate length to your radius by switching from legs to hypotenuses of these right triangles.
Basically, in order for your code to work, you would need to be making infinitely small triangles--Time.deltaTime would need to be infinitely small--as a right triangle with one infinitely small leg is just a line, its other leg and its hypotenuse are the same length.
Of course if Time.deltaTime were infinitely small, the player would never move. ;) So, a different approach is needed:
Instead, we can calculate the player's angular velocity and then move the player according to that.
So, dirst determine the player's new distance from the center first, then how many degrees the player would travel around the circle at that radius:
Vector3 sphereCenterPoint = sphereComponent.transform.position
Vector3 playerToCenter = sphereCenterPoint - player.transform.position;
float playerVerticalSpeed = 20f * player.InputVector.normalized.y;
newVerticalPosition = rigidbody.position + playerToCenter.normalized
* playerVerticalSpeed * Time.deltaTime;
playerToCenter = sphereComponent.transform.position - newVerticalPosition;
float circumferenceOfPlayerPath = 2f * playerToCenter.magnitude * Mathf.PI;
float playerHorizontalSpeed = 20f * player.InputVector.normalized.x;
float degreesTraveled = ( playerHorizontalSpeed * Time.deltaTime / circumferenceOfPlayerPath ) * 360f;
Then, rotate the player's new vertical position around the center point and set the player's rotation and position accordingly. You can use Quaternion.LookRotation to determine the rotation needed to make the rigidbody point forward/up in desired directions:
// rotates newVerticalPosition around sphereCenterPoint by degreesTraveled around z axis
Vector3 newPosition = Quaternion.Euler(0f,0f, degreesTraveled)
* (newVerticalPosition - sphereCenterPoint ) + sphereCenterPoint;
rigidbody.MovePosition(newPosition);
rigidbody.MoveRotation(
Quaternion.LookRotation(Vector3.forward, sphereCenterPoint - newPosition));
To remove a few calculations, you can include the part where you divide by 2 pi and multiply by 360f into the 20f factor:
Vector3 sphereCenterPoint = sphereComponent.transform.position
Vector3 playerToCenter = sphereCenterPoint - player.transform.position;
float playerVerticalSpeed = 20f * player.InputVector.normalized.y;
newVerticalPosition = rigidbody.position + playerToCenter.normalized
* playerVerticalSpeed * Time.deltaTime;
playerToCenter = sphereComponent.transform.position - newVerticalPosition;
float playerHorizontalSpeed = 1146f * player.InputVector.normalized.x;
float degreesTraveled = playerHorizontalSpeed * Time.deltaTime / playerToCenter.magnitude;
// rotates newVerticalPosition around sphereCenterPoint by degreesTraveled around z axis
Vector3 newPosition = Quaternion.Euler(0f,0f, degreesTraveled)
* (newVerticalPosition - sphereCenterPoint ) + sphereCenterPoint;
rigidbody.MovePosition(newPosition);
rigidbody.MoveRotation(
Quaternion.LookRotation(Vector3.forward, sphereCenterPoint - newPosition));
I did a search on this, but didn't find a question that quite matched what I was after. I want the user to be able to define a textured plane. The parameters I have are:
Size (A Vector2)
Direction (A Vector3)
Origin (A Vector3)
So, I want to be able to calculate the 4 vertices of the rectangle given the above information. So, if I wanted a plane facing up, with a width and height of 1000:
Size = (1000, 1000)
Direction = 0, 1, 0 (Up)
Origin = 0, 0, 0
So this would define a plane on the X & Z axis, facing upwards. What I do not understand is how to calculate the 4 corners in 3D space given this information. Do I need extra information, or is there a better way to arbitrarily specify a plane?
Edit : Current Code
In the following code:
Size = 10000, 10000
Center = 0, 0, 0
Normal = 0, 1, 0
Vector3 arb = new Vector3(1, 1, 1);
Vector3 planeY = Vector3.Normalize(Vector3.Cross(Normal, arb));
Vector3 planeX = Vector3.Normalize(Vector3.Cross(Normal, planeY));
planeX *= Size.X / 2;
planeY *= Size.Y / 2;
Vector3[] ret = new Vector3[4]
{
(Center - planeX - planeY),
(Center - planeX + planeY),
(Center + planeX - planeY),
(Center + planeX + planeY)
};
Your plane isn't fully defined yet. You need another vector going along the plane, the so called 'tangent' vector. In your above example, where should the Y-axis of the texture be pointing? Along the X-axis, along the Z-axis? Or maybe a completely different user defined axis? Your tangent vector is a vector that should point in the general direction where the X-axis of the plane should go.
Let's say we have a tangent vector as well, it doesn't neccesarilly need to point along the plane. You can construct the plane as follows:
Vector3[] vertices(Vector2 size, Vector3 center, Vector3 normal, Vector3 tangent)
{
Vector3 planeY = Vector3.Normalize(Vector3.Cross(normal, tangent));
Vector3 planeX = Vector3.Normalize(Vector3.Cross(normal, planeY));
planeX *= size.X / 2;
planeY *= size.Y / 2;
vertices = new Vector3[]
{
(center - planeX - planeY),
(center - planeX + planeY),
(center + planeX - planeY),
(center + planeX + planeY),
};
return vertices;
}
planeX and planeY are normalized vectors which point along X and Y axes of the plane itself. By multiplying these by size / 2, we get 2 vectors that span from the center to the edge of the plane in both X and Y directions. By adding these two together in different ways, we get the four corners.
Here's a diagram so you can get a better picture in your head. The tangent vector T is "flattened" onto the X-axis.
This is fine as a definition of a plane: you have a point and the normal vector. You need to get two vectors (A & B) on the plane and add one (A * one of the size values) to the origin to get the second corner. Add the second vector (B * the other size value) to get the third corner and add both vectors * their corresponding size values to the origin to get the forth corner.
To get the first vector, calculate the cross product of the normal vector (Direction) with an arbitrary vector (not equal to the direction). This will give you vector A. To get vector B calculate the cross product of A and the Direction.