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.
Related
for my 3d board game I am trying to build a "see the whole board" feature which should move the camera to a point where it is able to see the whole board, but without changing the rotation of the camera.
For now, I tried to get the minimum and maximum points which include all objects of interest and use these two points to mock a sphere around them (visualized it by actually placing a sphere there programmatically), so I am ending up like this right now:
Visualization of the sphere
My current problem is, that I am not able to build up a math formula to actually calculate the position of the camera to have the whole sphere in view (remember: rotation has to be unchanged).
This is my code so far for finding the smalles and biggest point and visualizing it by building the sphere:
// Find smallest and biggest point of all objects
var p1 = new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
var p2 = new Vector3(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity);
foreach (var gameObject in gameObjects)
{
foreach (var vertice in gameObject.GetComponent<MeshFilter>().sharedMesh.vertices)
{
var p = vertice + gameObject.transform.position;
p1.x = Math.Min(p1.x, p.x);
p1.y = Math.Min(p1.y, p.y);
p1.z = Math.Min(p1.z, p.z);
p2.x = Math.Max(p2.x, p.x);
p2.y = Math.Max(p2.y, p.y);
p2.z = Math.Max(p2.z, p.z);
}
}
// Center of all objects
var average = (p1 + p2) / 2;
// Visualize by creating a sphere
var diameter = Vector3.Distance(p1, p2);
var sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
sphere.transform.position = average;
sphere.transform.localScale = new Vector3(diameter, diameter, diameter);
Can you help me with the formula to actually calculate the position of my camera?
Regards
The easiest thing to do would be to move the camera back, with or without code. just use the game view to check whether the sphere is in view. if you got that point then you could leave it there or use code to do this:
Vector3 camPos;
//define the camera position in the inspector!
private void Start()
{
transform.position = camPos;
}
For each side of the view frustrum, find the parallel plane that is as close to the scene as possible. Each of these planes should just touch one of the objects in the scene.
Each pair of opposite planes will intersect along a line. Find these two lines. They will be perpendicular.
The camera should be located on whichever line is furthest away from the scene, situated so that the other line passes through the center of the field of view.
In the resulting view, one pair of opposite sides will just touch some objects in the scene, because the corresponding sides of the view frustrum will just touch those objects. In the other dimension, the objects will be centered.
I have a player position, a pointer indicating the players view direction, a distance and a horizontal and vertical angle. I want to calculate a target position:
that is distance away from the players position
that, from the players view direction, is horizontal angle to
the right and vertical angle up
It's about positioning a Hololens-Application UI in a sphere around the player. The UI should i.e. be 40 degrees to the leftand 20 degrees up from the players view direction.
Edit: Added image to clarify. Given is the Player Pos (pX|pY|pZ), the radius (= length of the black bold line) and both angles in degree.
I'm looking for how to calculate the UI Center position (x?|y?|z?).
You can use Quaternion.Euler to create a rotation based on angles in world space and then get the desired result by multiplying it with a known position.
So by using your example you could find the position like this:
float radius, x_rot, y_rot;
Vector3 forwardDirection, playerPos;
Vector3 forwardPosition = playerPos + (forwardDirection * radius);
Vector3 targetPosition = Quaternion.Euler(x_rot, y_rot, 0) * forwardPosition;
Try check out the docs on Quaternion and Quaternion.AngleAxis for more handy rotation stuff.
Answer by a mathematician:
To calculate the spherical position with the given information (distance between objects, x angle, y angle) you use trigonometry:
float x = distance * Mathf.Cos(yAngle) * Mathf.Sin(xAngle);
float z = distance * Mathf.Cos(yAngle) * Mathf.Cos(xAngle);
float y = distance * Mathf.Sin(yAngle);
ui.transform.position = player.transform.position + new Vector3(x,y,z);
// Set UI in front of player with the same orientation as the player
ui.transform.position = player.transform.position + player.transform.forward * desiredDistance;
ui.transform.rotation = player.transform.rotation;
// turn it to the left on the players up vector around the the player
ui.transform.RotateAround(player.transform.position, player.transform.up, -40);
// Turn it up on the UI's right vector around the player
ui.transform.RotateAround(player.transform.position, ui.transform.right, 20);
assuming you also want the UI to face the player, otherwise you have to set another rotation after this.
No need to calculate it yourself, the Unity API already does it for you (
see Rotate around)
If i am understanding you correctly you want to create a UI that hovers above a point. I recently did a similar thing in my game. and this is how i did it.
if (Input.GetMouseButtonDown(0)) // use the ray cast to get a vector3 of the location your ui
// you could also do this manualy of have the computer do it the main thing is to
// get the location in the world where you want your ui to be and the
// WorldTOScreenPoint() will do the rest
{
RaycastHit hit;
Vector3 pos;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit))
{
pos = hit.point;
pos.y += yOffset; // use the offset if you want to have it hover above the point
ui.transform.position = cam.WorldToScreenPoint(pos); // use your main cammera here
// then either make your ui vissible or instanciati it here and make sure if you instanciate it
// that you make it a child of your cnavas
}
}
I hope this solves you problem. If i am not understanding what you are trying to do let me know and i will try to help.
Note: if you want to make the ui look farther away when you move away from the point scale the ui down as you move farther away, and scale it up when you get closer.
The diagram in the question is somewhat confusing:
The axes are in the orientation of a right-handed coordinate system, but Unity uses a left-handed coordinate system.
In terms of Euler angles, the part of the image labeled "x Angle" is actually the Y angle (rotation around Y axis), and the part of the image labeled "y Angle" is actually the X angle (around X axis).
The two angles listed use a different sign convention. The Y angle (labeled "x Angle") is following the right-hand rule, while the other angle is not.
Jonas Zimmer has a great answer that follows the conventions in the image, but I'll try to do something a bit less confusing and follows more standard math conventions.
Here is some code for Unity written in C#, in YX rotation order, treating zero angle as forward (+Z), and follows Unity's conventions of a left-handed, Y-is-up, Z-is-forward coordinate system. Increasing Y angle rotates to the right, and increasing X angle rotates down.
public static Vector3 Vector3FromAngleYX(float y, float x)
{
float cosx = Mathf.Cos(x);
return new Vector3(cosx * Mathf.Sin(y), -Mathf.Sin(x), cosx * Mathf.Cos(y));
}
Also, I found this question looking to implement a Godot version, so here is a version for Godot Engine written in GDScript, in YX rotation order, treating zero angle as forward (-Z), and follows Godot's conventions of a right-handed, Y-is-up, Z-is-back coordinate system. Increasing Y angle rotates to the left, and increasing X angle rotates up.
func vector3_from_angle_yx(y, x):
var neg_cosx = -cos(x)
return Vector3(neg_cosx * sin(y), sin(x), neg_cosx * cos(y))
I have successfully implemented the floor clip plane to measure the distance of left foot to the floor, which is fairly accurate. The problem I have is that as I move away from the camera (i.e. left foot Z axis is increased), the foot distance to the floor changes (increases).
Note: The floor itself is not tilted nor the Kinect stand.
I tested it with Kinect 1 and had the same result. The subject's head height (Y axis) also changes value as I move away or get closer to the camera. It does not matter of the camera is tilted or line of sight. the D value in the FloorClipPlane equation shows a constant number during the test.
A = bodyFrame.FloorClipPlane.X;
B = bodyFrame.FloorClipPlane.Y;
C = bodyFrame.FloorClipPlane.Z;
D = bodyFrame.FloorClipPlane.W;
distanceLeftFoot = A * leftFootPosX + B * leftFootPosY + C * leftFootPosZ + D;
Just to let you know, I have coordinate mapping between depth and colour. Not sure if that has anything to do with the issue.
The FloorClipPlane is expressed in hessian normal form - as explained in the docs. Specifically, your A, B, and C values compromise the unit vector from camera origin (center of the Kinect) to floor plane such that it produces a perpendicular intersection with the floor plane. D is the magnitude of that vector (distance from camera origin to floor plane).
Even if you think the floor is flat and the Kinect is parallel to the ground, you have a perspective warping problem which means the body location (measured in depth space) is going to change as you come closer and further.
To fix this you need to provide as input both your 3D coordinate values and the floor plane, which will then give you back what you want, a measured distance from floor plane to joint:
// j is your joint - left foot or any other joint
float x = j.Position.X;
float y = j.Position.Y;
float z = j.Position.Z;
float distance = (Math.Abs((x * floorPlane.X) + (y * floorPlane.Y) + (z * floorPlane.Z) + floorPlane.W))/((float)Math.Sqrt((Math.Pow(floorPlane.X,2)) + (Math.Pow(floorPlane.Y, 2)) + (Math.Pow(floorPlane.Z, 2))));
I hope this helps you. Can't elaborate further what influence your mapping from depth to color might be doing here without seeing what you are specifically doing
I am trying to extract out 3D distance in mm between two known points in a 2D image. I am using square AR markers in order to get the camera coordinates relative to the markers in the scene. The points are the corners of these markers.
An example is shown below:
The code is written in C# and I am using XNA. I am using AForge.net for the CoPlanar POSIT
The steps I take in order to work out the distance:
1. Mark corners on screen. Corners are represented in 2D vector form, Image centre is (0,0). Up is positive in the Y direction, right is positive in the X direction.
2. Use AForge.net Co-Planar POSIT algorithm to get pose of each marker:
float focalLength = 640; //Needed for POSIT
float halfCornerSize = 50; //Represents 1/2 an edge i.e. 50mm
AVector[] modelPoints = new AVector3[]
{
new AVector3( -halfCornerSize, 0, halfCornerSize ),
new AVector3( halfCornerSize, 0, halfCornerSize ),
new AVector3( halfCornerSize, 0, -halfCornerSize ),
new AVector3( -halfCornerSize, 0, -halfCornerSize ),
};
CoplanarPosit coPosit = new CoplanarPosit(modelPoints, focalLength);
coPosit.EstimatePose(cornersToEstimate, out marker1Rot, out marker1Trans);
3. Convert to XNA rotation/translation matrix (AForge uses OpenGL matrix form):
float yaw, pitch, roll;
marker1Rot.ExtractYawPitchRoll(out yaw, out pitch, out roll);
Matrix xnaRot = Matrix.CreateFromYawPitchRoll(-yaw, -pitch, roll);
Matrix xnaTranslation = Matrix.CreateTranslation(marker1Trans.X, marker1Trans.Y, -marker1Trans.Z);
Matrix transform = xnaRot * xnaTranslation;
4. Find 3D coordinates of the corners:
//Model corner points
cornerModel = new Vector3[]
{
new Vector3(halfCornerSize,0,-halfCornerSize),
new Vector3(-halfCornerSize,0,-halfCornerSize),
new Vector3(halfCornerSize,0,halfCornerSize),
new Vector3(-halfCornerSize,0,halfCornerSize)
};
Matrix markerTransform = Matrix.CreateTranslation(cornerModel[i].X, cornerModel[i].Y, cornerModel[i].Z);
cornerPositions3d1[i] = (markerTransform * transform).Translation;
//DEBUG: project corner onto screen - represented by brown dots
Vector3 t3 = viewPort.Project(markerTransform.Translation, projectionMatrix, viewMatrix, transform);
cornersProjected1[i].X = t3.X; cornersProjected1[i].Y = t3.Y;
5. Look at the 3D distance between two corners on a marker, this represents 100mm. Find the scaling factor needed to convert this 3D distance to 100mm. (I actually get the average scaling factor):
for (int i = 0; i < 4; i++)
{
//Distance scale;
distanceScale1 += (halfCornerSize * 2) / Vector3.Distance(cornerPositions3d1[i], cornerPositions3d1[(i + 1) % 4]);
}
distanceScale1 /= 4;
6. Finally I find the 3D distance between related corners and multiply by the scaling factor to get distance in mm:
for(int i = 0; i < 4; i++)
{
distance[i] = Vector3.Distance(cornerPositions3d1[i], cornerPositions3d2[i]) * scalingFactor;
}
The distances acquired are never truly correct. I used the cutting board as it allowed me easy calculation of what the distances should be. The above image calculated a distance of 147mm (expected 150mm) for corner 1 (red to purple). The image below shows 188mm (expected 200mm).
What is also worrying is the fact that when measuring the distance between marker corners sharing an edge on the same marker, the 3D distances obtained are never the same. Another thing I noticed is that the brown dots never seem to exactly match up with the colored dots. The colored dots are the coordinates used as input to the CoPlanar posit. The brown dots are the calculated positions from the center of the marker calculated via POSIT.
Does anyone have any idea what might be wrong here? I am pulling out my hair trying to figure it out. The code should be quite simple, I don't think I have made any obvious mistakes with the code. I am not great at maths so please point out where my basic maths might be wrong as well...
You are using way to many black boxes in your question. What is the focal length in the second step? Why go through ypr in step 3? How do you calibrate? I recommend to start over from scratch without using libraries that you do not understand.
Step 1: Create a camera model. Understand the errors, build a projection. If needed apply a 2d filter for lens distortion. This might be hard.
Step 2: Find you markers in 2d, after removing lens distortion. Make sure you know the error and that you get the center. Maybe over multiple frames.
Step 3: Un-project to 3d. After 1 and 2 this should be easy.
Step 4: ???
Step 5: Profit! (Measure distance in 3d and know your error)
I think you need to have 3D photo (two photo from a set of distance) so you can get the parallax distance from image differences
I'm making an XNA game and have run into a small problem figuring out a bit of vector math.
I have a class representing a 2D object with X and Y integer coordinates and a Rotation float. What I need is to have a Vector2 property for Position that gets and sets X and Y as a Vector2 that has been transformed using the Rotation float. This way I can just do something like;
Position += new Vector2((thumbstick.X * scrollSpeed), -(thumbstick.Y * scrollSpeed));
and the object will move in it's own upward direction, rather than the View's upward direction.
So far this is what I have...I think the set is right, but for += changes it needs a get as well and the answer just isn't coming to me right now... >.>
public Vector2 Position
{
get
{
// What goes here? :S
}
set
{
X = value.X * (int)Math.Cos(this.Rotation);
Y = value.Y * (int)Math.Cos(this.Rotation);
}
}
No, both are incorrect.
A 2D vector transforms like this:
x' = x*cos(angle) - y*sin(angle)
y' = x*sin(angle) + y*cos(angle)
where the angle is measured in radians, zero angle is along the positive x-axis, and increases in the counterclockwise direction as you rotate around the z-axis out of plane. The center of rotation is at the end of the vector being transformed, so imagine the vector with origin at (0,0), end at (x,y) rotation through an angle until it becomes a vector with origin at (0,0) and end at (x', y').
You can also use the Matrix helper methods to create a Z rotation matrix then multiply your vector by this to rotate it. Something like this:
Vector v1;
Matrix rot = Matrix.CreateRotationZ(angle);
Vector v2 = v1 * rot;
I think this is a bad idea. Keep all of your objects' X and Y co-ordinates in the same planes instead of each having their own axes. By all means have a Position and Heading properties and consider having a Move method which takes your input vector and does the maths to update position and heading.