For a screen overlay I am making for a 3-dimensional game, I need to display icons over certain locations in the 3D world. I've managed to get several variables from the game that should make this possible:
Player position (x,y,z)
Camera position (x,y,z)
Point position (x,y,z)
Camera Angle(FOV)
Using these variables I managed to get the right and left edge of the camera view. Using these 2 variables I am able to get the point between 0 and 1 where the icon should be displayed on the x axis. (0 is on the left edge, 1 is right edge) This seems to work quite well, but only if I am aligned to either the X or Z axis, as shown in the following example:
I've been trying to fix this using the following rotation matrix:
[ Math.Cos(angle), -Math.Sin(angle) ]
[ Math.Sin(angle), Math.Cos(angle) ]
What I do is, I put the player position, camera position and the camera edge positions in the matrix with as rotation point the world point. The problem is, as soon as I put the angle amount at 90 degrees, the X and Y are being flipped. I've been trying to find a solution for this for a few days now, but I can't find it, so I hope anyone can push me in the right direction here. Below are a few parts of my code that might help in finding the solution:
float PCDistXZ = (float)Math.Sqrt(deltaPCx * deltaPCx + deltaPCz * deltaPCz); // X/Z distance from the world point to the camera
Point fakeAvatarPos = RotateAround(new Point((int)point.x, (int)point.z), new Point((int)avatar.x, (int)avatar.z), (int)90);
Point fakeCameraPos = RotateAround(new Point((int)point.x, (int)point.z), new Point((int)camera.x, (int)camera.z), (int)90);
double edgeRight = fakeC.X + (Math.Sin(45) * PCDistXZ);
double edgeLeft = fakeC.X - (Math.Sin(45) * PCDistXZ);
float edgeTest_ScreenPositionX = (1 - (float)((edgeRight - P.x) / (edgeRight - edgeLeft))) * screenWidth;
public static Point RotateAround(Point pCenter,Point pPoint, float pAngle)
{
double angle = (pAngle * Math.PI) / 180;
double[,] matrix = new Double[2, 2] {
{ Math.Cos(angle), Math.Sin(angle) },
{ Math.Sin(angle), Math.Cos(angle) }
};
double xOffset = pPoint.X - pCenter.X;
double yOffset = pPoint.Y - pCenter.Y;
Point newPoint = new Point(
(int)(((pPoint.X - xOffset) * matrix[0, 0]) - ((pPoint.Y - xOffset) * matrix[0, 1])),
(int)(((pPoint.X - yOffset) * matrix[1, 0]) + ((pPoint.Y - yOffset) * matrix[1, 1]))
);
newPoint.X += (int)xOffset;
newPoint.Y += (int)yOffset;
return new Point(newPoint.X,newPoint.Y);
}
Note: I've changed the names of some of the variables to more understandable one, so it could be possible that there are inconsistencies in the names.
EDIT: I found out about view- and projection matrices. I might be able to use those to convert the 3D position to screen. I'm not sure if it's possible to make this matrices with the limited information I have though.
Related
I'm working on a rather Large project. It was already finished when I started and I have to implement some small gimics.
One of those is the rotation of a marker on a map.
When the marker is selected a rectangle (System.Wndows.FrameWorkElement) is drawn around the picture. Since I would basically have to rewrite the whole program to use another rectangle, I have to stick with the framework element.
To rotate this thing, I added a line and a circle.
The line connects the circle with the rectangle. When the user clicks on the circle and drags the mouse, the whole thing is supposed to rotate around the center of the rectangle.
So far, the rotation of the rectangle and the line works fine. But the circle, though it is rotating around the center of the rectangle, is also rotating around a point at it's own border.
I rotate the rectangle with a RenderTransform object, which works well enough and is easy enough.
For the line and the circle, I wrote a method to calculate the rotation.
The line I can calculate without using the angle.
Here's the method:
private void SetPositionOfRotationShaft(Point center)
{
double l = Math.Sqrt(Math.Pow((this.ConnectionLineDirection.X - center.X), 2) + Math.Pow((this.ConnectionLineDirection.Y - center.Y), 2));
double factor = Math.PI / 180;
this.connectionLine.X1 = center.X + (this.surroundingRectangle.Height / (2 * l)) * (this.ConnectionLineDirection.X - center.X);
this.connectionLine.Y1 = center.Y + (this.surroundingRectangle.Height / (2 * l)) * (this.ConnectionLineDirection.Y - center.Y);
this.connectionLine.X2 = center.X + ((this.surroundingRectangle.Height + 40) / (2 * l)) * (this.ConnectionLineDirection.X - center.X);
this.connectionLine.Y2 = center.Y + ((this.surroundingRectangle.Height + 40) / (2 * l)) * (this.ConnectionLineDirection.Y - center.Y);
double translatedLeft = Canvas.GetLeft(this.rotationSign) - center.X;
double translatedTop = Canvas.GetTop(this.rotationSign) - center.Y;
double left = ((translatedLeft * Math.Cos(-this.rotateSurroundingRectangle.Angle*factor)) + (translatedTop * Math.Sin(-this.rotateSurroundingRectangle.Angle*factor))) + center.X;
double top = ((translatedTop * Math.Cos(-this.rotateSurroundingRectangle.Angle * factor)) - (translatedLeft * Math.Sin(-1 * this.rotateSurroundingRectangle.Angle * factor))) + center.Y;
Canvas.SetLeft(this.rotationSign, left);
Canvas.SetTop(this.rotationSign, top);
}
Also curious, when i use the same calculation for the line as i do for the circle, the line rotates at a higher speed. The same thing happend to the circle until i added the factor.
So, the problem was, that i had to set the position of the circle with Canvas.SetLeft() and SetTop(), which is essentialy the upper left corner of a square around the circle.
For my rotation to work, i should have set the center (but that's not possible). So i had to subtract the radius of the circle from top and left.
Canvas.SetLeft(this.rotationSign, left-radius);
Canvas.SetTop(this.rotationSign, top-radius);
I'm trying to follow a tutorial which is written in a math programming language I'm not familiar with and attempting to convert the tutorial to C# code for Unity 3d.
See here: http://blog.wolfram.com/2011/07/28/how-i-made-wine-glasses-from-sunflowers/
float theta = Mathf.PI * (3 - Mathf.Sqrt(5));
for (int i = 0; i < spawnPoints.Length; i++)
{
float r = (radius / 2) * Mathf.Sqrt(i) / Mathf.Sqrt(points);
float a = theta * i;
Vector3 coords = transform.TransformDirection( new Vector3(Mathf.Cos(a) * r, 0, Mathf.Sin(a) * r) )+transform.position;
spawnPoints[i] = coords;
}
This if course generates the flat phillotaxic arrangement in 2d. I'm trying to modify Y (up) axis for depth (creating the sphere).
I cannot seem to set the Y (up) axis correctly in proportion with i and radius.
Considering the tutorial above, how should I be calculating Y?
The 3D version is called a spherical Fibonacci lattice. This paper gives a nice explanation. This stackoverflow post has more links.
There are the planet and a few satellites in 3d space. I need to calculate axis of rotation for every satellite. They should rotate around planet center.
I calculated the vector from satellite to the center of the planet.
vec1 = planetCenter - sputnikCenter;
With vec1 and planetCenter I can calculate the equation of plane, that perpendicular to vec1.
equation like that:
A.x + B.y + C.z + D = 0
Now, I should get random vector on this plane. This vector will be axis of rotation. But how can I get this random vector?
well if you got the plane A.x + B.y + C.z + D = 0 then n(A,B,C) is the normal vector. So I think the easiest approach to your task is to use basis vectors. So you need 2 perpendicular vectors on this plane. For that you can exploit cross product. first some definitions:
knowns:
p planet center position (or the center point of your rotations or any point on the plane so in worst case you can try p=0,0,-D/C or any other combinationn...)
n normal vector
q= (1,0,0) or (0,1,0) chose the one that has lesser |dot(n,q)|
operations:
vector = cross(a,b) = a x b - cross product returns perpendicular vector to a,b
scalar = dot(a,b) = (a.b) - dot product returns 0 if a,b are perpendicular
|a| = abs(a) - absolute value (both scalar and vector)
scalar = Rand() - float pseudo random value on interval <0.0,1.0>
unknowns:
u,v - basis vectors
r - your pseudo-random point
So first get u,v by exploiting cross product:
u=cross(n,q)
v=cross(n,u)
And now the point:
r = p + u*(2.0*Rand()-1.0) + v*(2.0*Rand()-1.0)
If you want just random vector then ignore the start position p
r' = u*(2.0*Rand()-1.0) + v*(2.0*Rand()-1.0)
That is all ... so you can compute u,v once (per normal vector change) and generate the r as often as you need. If u,v are unit vectors then this will generate points inside 2x2 square ... if you want more or less just add scales to them ...
see Is it possible to make realistic n-body solar system simulation? and generate random orbital parameters for Kepler's equation instead ...
It seems your rotation axis might be random vector that is independent from vec1.
You can generate random unit vector with uniform distribution using methods for Sphere Point Picking.
Marsaglia method (eq. 9-11) is convenient to generate this vector:
Generate x1 and x2 in range -1..1 such as p = x1^2 +x2^2 <= 1 (rejecting bad pairs).
Then
x = 2 * x1 * Sqrt(1 - p)
y = 2 * x2 * Sqrt(1 - p)
z = 1 - 2 * p
Now your question is clear. You want to rotate object around another, like Earth and Sun. May be some other solutions may available but I would do it through LookAt and parametric equation of circle.
x = r * cos(theta) + displacementX
z = r * sin(theta) + displacementZ
where r is radius, distance in your case
displacementX and displacementZ are the distance from origin. If both (displacementX and displacementZ) is 0 then it will rotate around origin (0,0)
In Object(Earth) script, do it as follow
public Transform _sun;
float _theta = 0;
void Start ()
{
StartCoroutine ("ChangeAngle");
}
void Update ()
{
transform.LookAt (_sun);
float newX = (5 * Mathf.Cos (_theta)) + _sun.position.x;
float newZ = (5 * Mathf.Sin (_theta)) + _sun.position.z;
transform.position = new Vector3 (newX, _sun.position.y, newZ);
}
IEnumerator ChangeAngle ()
{
while (true) {
yield return new WaitForSeconds (0.01f);
_theta += 0.1f;
if (_theta >= 360)
_theta = 0;
}
}
You can further play with it
I've been trying to figure this relationship out but I can't, maybe I'm just not searching for the right thing. If I project a world-space coordinate to clip space using Vector3.Project, the X and Y coordinates make sense but I can't figure out how it's computing the Z (0..1) coordinate. For instance, if my nearplane is 1 and farplane is 1000, I project a Vector3 of (0,0,500) (camera center, 50% of distance to far plane) to screen space I get (1050, 500, .9994785)
The resulting X and Y coordinates make perfect sense but I don't understand where it's getting the resulting Z-value.
I need this because I'm actually trying to UNPROJECT screen-space coordinates and I need to be able to pick a Z-value to tell it the distance from the camera I want the world-space coordinate to be, but I don't understand the relationship between clip space Z (0-1) and world-space Z (nearplane-farplane).
In case this helps, my transformation matrices are:
World = Matrix.Identity;
//basically centered at 0,0,0 looking into the screen
View = Matrix.LookAtLH(
new Vector3(0,0,0), //camera position
new Vector3(0,0,1), //look target
new Vector3(0,1,0)); //up vector
Projection = Matrix.PerspectiveFovLH(
(float)(Math.PI / 4), //FieldOfViewY
1.6f, // AspectRatio
1, //NearPlane
1000); //FarPlane
Standard perspective projection creates a reciprocal relationship between the scene depth and the depth buffer value, not a linear one. This causes a higher percentage of buffer precision to be applied to objects closer to the near plane than those closer to the far plane, which is typically desired. As for the actual math, here's the breakdown:
The bottom-right 2x2 elements (corresponding to z and w) of the projection matrix are:
[far / (far - near) ] [1]
[-far * near / (far - near)] [0]
This means that after multiplying, z' = z * far / (far - near) - far * near / (far - near) and w' = z. After this step, there is the perspective divide, z'' = z' / w'.
In your specific case, the math works out to the value you got:
z = 500
z' = z * 1000 / (1000 - 999) - 1000 / (1000 - 999) = 499.499499499...
w' = z = 500
z'' = z' / w' = 0.998998998...
To recover the original depth, simply reverse the operations:
z = (far / (far - near)) / ((far / (far - near)) - z'')
In my C# WinForms application I have a picturebox that hosts 2 curves (Resulted from a voltage/current measurement). The X axis is voltage and Y axis is current. The voltage axis is ranged from -5 to 5 but the current axis is a much smaller scale ranged from -10 uA to 10 uA. The task is to see if the second curve is within 10% of the first curve.
For visual inspection I am trying to draw an envelope around the first curve (Blue one). The curve is just a PointF array. At the moment since I have no idea how to draw a correct envelope around the blue curve, I just draw two other curves that are result of X points of the actual curve added and subtracted by 10% of the original curve. Of course this is a bad approach, but atleast for the section of the curve that is noticably vertical, it works. But as soon as the curve is on its non vertical section, this trick does not work anymore, as you can see in the picture below:
Here is the code that I am using to draw the envelope:
public Bitmap DrawEnvelope(double[,] pinData, float vLimit, float iLimit)
{
g = Graphics.FromImage(box);
g.SmoothingMode = SmoothingMode.AntiAlias;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
PointF[] u = new PointF[pinData.GetLength(0)]; //Up line
PointF[] d = new PointF[pinData.GetLength(0)]; //Down Line
List<PointF> joinedCurves = new List<PointF>();
float posX = xMaxValue * (vLimit / 100);
float minX = posX * -1;
for (int i = 0; i < pinData.GetLength(0); i++)
{
u[i] = new PointF(400 * (1 + (((float)pinData[i, 0]) + minX) / (xMaxValue + vExpand)), 400 * (1 - ((float)pinData[i, 1] * GetInvers((yMaxValue + iExpand)))));
}
for (int i = 0; i < pinData.GetLength(0); i++)
{
d[i] = new PointF(400 * (1 + (((float)pinData[i, 0]) + posX) / (xMaxValue + vExpand)), 400 * (1 - ((float)pinData[i, 1] * GetInvers((yMaxValue + iExpand)))));
}
Pen pengraph = new Pen(Color.FromArgb(50, 0 ,0 ,200), 1F);
pengraph.Alignment = PenAlignment.Center;
joinedCurves.AddRange(u);
joinedCurves.AddRange(d.Reverse());
PointF[] fillPoints = joinedCurves.ToArray();
SolidBrush fillBrush = new SolidBrush(Color.FromArgb(40, 0, 0, 250));
FillMode newFillMode = FillMode.Alternate;
g.FillClosedCurve(fillBrush, fillPoints, newFillMode, 0);
g.Dispose();
return box;
}
The green circles are added by myself, and they indicate the region that the second curve (Red one) is potentially has a difference bigger than 10% from the orginal curve.
Would be nice if someone put me in the right way, what should I look to to achive a nice envelope around original curve?
UPDATE
Because I am so noob I cant find a way to implement the answers given to this question until now, So put a bounty to see if somone can kindly show me atleast a coding approach to this problem.
You could try finding the gradient between each pair of points and calculating two points either side that are on the orthogonal that passes through the midpoint.
You would then have two more lines defined as a set of points that you could use to draw the envelope.
Your best bet is to iterate your point array and to calculate a perpendicular vector to two consecutive points each time (see Calculating a 2D Vector's Cross Product for implementation clues). Project in either direction along these perpendicular vectors to generate the two point arrays of your envelope.
This function generates them roughly using segment midpoints (as long as the point count is high and your offset is not too small it should look ok when plotted):
private void GetEnvelope(PointF[] curve, out PointF[] left, out PointF[] right, float offset)
{
left = new PointF[curve.Length - 1];
right = new PointF[curve.Length - 1];
for (int i = 1; i < curve.Length; i++)
{
PointF normal = new PointF(curve[i].Y - curve[i - 1].Y, curve[i - 1].X - curve[i].X);
float length = (float)Math.Sqrt(normal.X * normal.X + normal.Y * normal.Y);
normal.X /= length;
normal.Y /= length;
PointF midpoint = new PointF((curve[i - 1].X + curve[i].X) / 2F, (curve[i - 1].Y + curve[i].Y) / 2F);
left[i - 1] = new PointF(midpoint.X - (normal.X * offset), midpoint.Y - (normal.Y * offset));
right[i - 1] = new PointF(midpoint.X + (normal.X * offset), midpoint.Y + (normal.Y * offset));
}
}
It all depends on the way you want the envelop to be sized.
You could calculate/guestimate the slope of the curve in each point by calculating the slope to the next point and the slope to the previous point, average these and then calculate a perpendicular vector to the slope.
Add this vector to the point of the curve; this gives you the right-hand edge of the envelop.
Subtract this vector from the point of the curve; this gives you the left-hand edge of the envelop.
This method will fail if the points are too far apart or very sudden changes in the points appear.
This is probably a dumb suggestion. Perhaps instead of drawing the envelope yourself, maybe you could let winforms do it for you. Try drawing the envelope as a line with a pen that has a larger width. Perhaps it might work.
If you look at this msdn example on varying the pen width, you might see what I mean.
http://msdn.microsoft.com/en-us/library/3bssbs7z.aspx
2 (probably incorrect) possibilities.
Do what you did originally to get the pale blue wide area, but also do it in the vertical direction (not just the horizontal)
Do what Dan suggested with a REALLY thick line (in pale blue) then draw it again, then draw the original (thin) line on top of it.