I'm using GDI+ to implement some simple graphics, I've taken the code from this example http://www.vcskicks.com/3d_gdiplus_drawing.php and can get it to do what I want, but I don't understand how it's doing the conversion from 3D data point to 2D data point:
//Convert 3D Points to 2D
Math3D.Point3D vec;
for (int i = 0; i < point3D.Length; i++)
{
vec = cubePoints[i];
if (vec.Z - camera1.Position.Z >= 0)
{
point3D[i].X = (int)((double)-(vec.X - camera1.Position.X) / (-0.1f) * zoom) + drawOrigin.X;
point3D[i].Y = (int)((double)(vec.Y - camera1.Position.Y) / (-0.1f) * zoom) + drawOrigin.Y;
}
else
{
tmpOrigin.X = (int)((double)(cubeOrigin.X - camera1.Position.X) / (double)(cubeOrigin.Z - camera1.Position.Z) * zoom) + drawOrigin.X;
tmpOrigin.Y = (int)((double)-(cubeOrigin.Y - camera1.Position.Y) / (double)(cubeOrigin.Z - camera1.Position.Z) * zoom) + drawOrigin.Y;
point3D[i].X = (float)((vec.X - camera1.Position.X) / (vec.Z - camera1.Position.Z) * zoom + drawOrigin.X);
point3D[i].Y = (float)(-(vec.Y - camera1.Position.Y) / (vec.Z - camera1.Position.Z) * zoom + drawOrigin.Y);
point3D[i].X = (int)point3D[i].X;
point3D[i].Y = (int)point3D[i].Y;
}
}
I've found a couple of resources which discuss conversion from a 3d data point to a 2d one:
https://amycoders.org/tutorials/3dbasics.html
https://en.wikipedia.org/wiki/Isometric_projection
https://en.wikipedia.org/wiki/3D_projection
However none of these resources seem to detail the maths used in the above example.
I'd be really grateful if someone could point me at the derivation for the maths and/or explain how the above code works.
The article and code is a bit confusing, indeed. Before we start, let's do some modifications to the rest of the code. Through these modifications, you will probably see what's going on more easily. Let's specify a static camera position. Instead of this weird formula:
double cameraZ = -(((anchorPoint.X - cubeOrigin.X) * zoom) / cubeOrigin.X) + anchorPoint.Z;
Let's just do this:
cameraZ = 200;
zoom = 100;
And after that, we keep
camera1.Position = new Math3D.Point3D(cubeOrigin.X, cubeOrigin.Y, cameraZ);
This will position the camera at a depth of 200 such that its x/y coordinates coincide with the cube center. I'll come back to the meaning of zoom.
The camera model uses a perspective projection and a right-handed coordinate system. That means that the camera look in the negative z-direction and things that are far away will appear smaller.
Let's take a closer look at the 3D->2D conversion code step by step:
if (vec.Z - camera1.Position.Z >= 0)
vec is the point that we want to project. A more intuitive way to write that would be:
if (vec.Z >= camera1.Position.Z)
So, this branch applies to all points that are behind the camera (remember that the camera looks into the negative z-direction). What happens in this branch is a bit hacky. It has nothing to do with real projections. What you actually want to do is to cut off those points (as they are not visibile). Luckily, in the example, none of the points lie behind the camera. So, we don't need to care about this. I'll come back to that later.
Let's continue to the else branch.
tmpOrigin = ...
This variable is not used anywhere, so we can ignore it.
point3D[i].X = (float)((vec.X - camera1.Position.X) / (vec.Z - camera1.Position.Z) * zoom + drawOrigin.X);
This is the actual projection (I will only consider the X part. The same goes for the Y part). Let's take a look at the individual parts:
vec.X - camera1.Position.X
This is the vector from the camera position to the point drawn. Everything left of the camera has a negative coordinate, everything right of the camera has a positive coordinate.
vec.Z - camera1.Position.Z
This is the negative depth of the camera. Not sure why the negative depth is used here. This will give you a mirrored image. What you actually wanted to do is (due to the camera looking into the negative z-axis)
camera1.Position.Z - vec.Z
Then,
(vec.X - camera1.Position.X) / (vec.Z - camera1.Position.Z)
is the perspective divide. The difference vector is scaled by its inverse depth (i.e. far objects become smaller).
* zoom
This scales the image from world space (which is very small) to image space (convert world units to pixels). The factor is kind of arbitrary (that's why we just specified 100). More involved camera models use a field of view.
drawOrigin.X
And finally, we align the camera center to the drawOrigin. Remember that points left of the camera had a negative coordinate. With this, these will get a positive coordinate (but still be left of drawOrigin).
point3D[i].X = (int)point3D[i].X;
This is just a cast to int.
For the y-coordinate, there is an additional -. This turns the y-axis around (in the pixel coordinate system of the image, the y-axis points downwards).
Let's go back to the hacky if branch. You see that the formula is exactly the same. Except that the part that had the negative depth of the point before now has (-0.1f). So these points will be considered having a constant depth of 0.1. Pretty dubious and far from actual projections.
And that's basically it. One more note: The article has a section about Gimbal lock. Thing is, the properties of matrix multiplications that are described there have nothing to do with Gimbal lock. So, don't rely on this article too much. It's a nice practical application, but it has quite some flaws.
Related
If a circle is defined by the X, Y of it's center and a Radius, then how can I find a Circle that encompasses a given number of circles? A single circle that is the smallest possible circle to completely contain 2 or more circles of any size and location.
At first I tried just encompassing 2 circles by finding the midpoint of the centers and that being the midpoint of the new circle while the radius was equal to the half of the radius of the 2 initial circles and half the distance between their centers, but somehow it always turned out to be a little off. The problem always seemed to be a problem with finding the radius, but I have such a headache about this I can't make it work.
I don't necessarily need a method for finding a circle that encompasses 3 or more circles. I can find a circle that encompasses 2, take that circle and encompass it with another, and another, and the final circle should encompass all circles given throughout the steps.
Given two circles, with centers [x1,y1], [x2,y2], and radii R1 and R2. What is the center of the enclosing circle?
Assume that R1 is no larger than R2. If the second circle is the smaller, then just swap them.
Compute the distance between centers of the circles.
D = sqrt((x1-x2)^2 + (y1-y2)^2)
Does the first circle lie entirely inside the second circle? Thus if (D + R1) <= R2, then we are done. Return the larger circle as the enclosing circle, with a center of [x2,y2], with radius R2.
If (D+R1) > R2, then the enclosing circle has a radius of (D+R1+R2)/2
In this latter case, the center of the enclosing circle must lie along the line connecting the two centers. So we can write the new center as
center = (1-theta)*[x1,y1] + theta*[x2,y2]
where theta is given by
theta = 1/2 + (R2 - R1)/(2*D)
Note that theta will always be a positive number, since we have assured that (D+R1) > R2. Likewise, we should be able to ensure that theta is never larger than 1. These two conditions ensure that the enclosing center lies strictly between the two original circle centers.
There problem you have at hand is called Smallest enclosing sphere of spheres. I have written my thesis about it, see "Smallest enclosing ball of balls", ETH Zurich.
You can find a very efficient C++ implementation in the Computational Geometry Algorithms Library (CGAL) in package Bounding Volumes. (There is no need to use all of CGAL; just extract the required source and header files and you are up and running.)
Note: If you are looking for an algorithm to compute the smallest enclosing sphere of points only, there are other implementations out there, see this post.
Since my inexact solution was not liked. Heres a way to get the exact solution. But its slow ( O(N^4)? ) and computationally nasty. (Unlike the inexact method)
First you need to know that given three circles we can find a circle tangential to them all than contains all three. This is one of the circles of Apollonius. You can get the algorithm from mathworld.
Next you can show that the smallest enclosing circle for N circles is tangential to at least 3 of the N circles.
To find this circle we do the following
loop through all triples of circles - O(N^3)
find the enclosing Apollonius circle of those 3 circles - computationally nasty
if it encloses all the circles add it to a list of potentials - check is O(N)
Solution is potential with smallest radius
There may be some tricks to speed this up, but it should give you the exact solution.
Some of the "tricks" for getting Smallest Enclosing Circle algorithms to linear time may be applicable here, but I suspect they would not be trivial adaptions.
I'm going to recommend against this, now
See the discussion below.
Original thoughts
I would consider an iterative push-pull method.
Guess where to put the center (simplest would be the mean position of all centers)
Compute the vectors to the farthest point on each circle. These are always in the direction to the center of that circle and have length distance_to_center_of_circle[i]+radius_of_circle[i] and form the vector sum as you go. Also note that the necessary radius at the current location is the maximum of these lengths.
Propose a step of (say) 1/5 or 1/10 of the vector sum from 2, and redo the computations from 2 for the new point
If the new point needs a smaller circle than the old, make the new point the current point, otherwise, split the difference, reduce the size of the proposed step (say half it).
goto 3
You're done when it stops[+] converging.
Nikie poked at it until...
As requested clarifying step two. Call the position to be tested \vec{P} (a vector quantity).[++] Call the centers of each circle \vec{p}_i (also vector quantities) and the radius of each circle is r_i. Form the sum \sum_i=1^n \hat{p_i - P}*|(p_i-P)|+r_i).[+++] Each element of the sum points in the direction from the current evaluation point towards the center of the circle in question, but is longer by r_i. The sum itself it a vector quantity.
The radius R need to enclose all the circle from P is the max(|p_i-P|_r_i).
Pathological case
I don't think the particular case nikie's brought up is a problem, but it has put me onto a case where this algorithm fails. The failure is one of failing to improve a solution, rather than one of diverging, but still...
Consider four circles all of radius 1 positioned at
(-4, 1)
(-5, 0)
(-4, 1)
( 5, 0)
and a starting position of (-1, 0). Symmetric by design so that all distances lie along the x axis.
The correct solution is (0, 0) with radius 6, but the vector calculated in step 2 be about ::calculates furiously:: (-.63, 0), pointing in the wrong direction resulting in never finding the improvement towards the origin.
Now, the algorithm above would actual pick (-2, 0) for the starting point, which gives an initial vector sum of ::calculates furiously:: about +1.1. So, a bad choice of step size on (3) would result in a less than optimal solution. ::sigh::
Possible solution:
In (3) throw a random fraction between (say +1/5 and -1/5) possibly weighted towards the positive size.
In (4) if the step is rejected, simply return to step three without altering the step size limits.
However, at this point it is not much better than a pure random walk, and you don't have an easy condition for knowing when it has converged. Meh.
[+] Or slows to your satisfaction, of course.
[++] Using latex notation.
[+++] Here \hat{} means the normalized vector pointing in the same direction as the argument.
I've taken what some of you had to say and here's the solution I discovered:
public static Circle MinimalEnclosingCircle(Circle A, Circle B) {
double angle = Math.Atan2(B.Y - A.Y, B.X - A.X);
Point a = new Point((int)(B.X + Math.Cos(angle) * B.Radius), (int)(B.Y + Math.Sin(angle) * B.Radius));
angle += Math.PI;
Point b = new Point((int)(A.X + Math.Cos(angle) * A.Radius), (int)(A.Y + Math.Sin(angle) * A.Radius));
int rad = (int)Math.Sqrt(Math.Pow(a.X - b.X, 2) + Math.Pow(a.Y - b.Y, 2)) / 2;
if (rad < A.Radius) {
return A;
} else if (rad < B.Radius) {
return B;
} else {
return new Circle((int)((a.X + b.X) / 2), (int)((a.Y + b.Y) / 2), rad);
}
}
Circle is defined by the X, Y of it's center and a Radius, all are ints. There's a constructor that is Circle(int X, int Y, int Radius). After breaking out some old trig concepts, I figured the best way was to find the 2 points on the circles that are farthest apart. Once I have that, the midpoint would be the center and half the distance would be the radius and thus I have enough to define a new circle. If I want to encompass 3 or more circles, I first run this on 2 circles, then I run this on the resulting encompassing circle and another circle and so on until the last circle is encompassed. There may be a more efficient way to do this, but right now it works and I'm happy with that.
I feel weird answering my own question, but I could not have come to this solution without everybody's ideas and links. Thanks everybody.
So if you don't need the exact circle this approximation might do.
Take the average of all your centers of the circles call this
point X
Let R1 be the maximum distance from X to a circle center.
Let R2 be the maximum radius of the circles
The all the circles must fall inside the circle centered at X with radius R1+R2
This is not a trivial problem. I haven't read all the answers above, so if I repeat what someone has already said, the fault is mine.
Each circle c_i is defined by 3 parameters x_i,y_i,r_i
3 parameters need to be found x*,y*,r* for the optimal circle C*
C* is such that it contains c_i for all i
Let d_i = ||(x,y)-(x_i,y_i)|| + r_i
Then if r is radius of a circle that contains all c_i, then r >= d_i for all i
We want r to be as small as possible
So, r* = max(d_i)
Thus we want to minimize the max of d_i
So (x*,y*) are given by the arg min of max(d_i). And once (x*,y*) are found, r* can be readily computed and will equal max(d_i). This is a minimax problem.
To make things easier to understand consider just 2 circles, how can we find (x*,y*)?
(x*,y*) can be found by finding the (x,y) that minimize (d_1 - d_2)^2. In the general case
let e_ij = (d_i - d_j)^2
Then define e = \sum e_ij for i != j (there are n Choose 2 terms in this sum)
(x*,y*) = arg min of e
And this is what needs to be solved for.
Tip: if r_i = 0 for all i, then this reduces to the traditional minimum enclosing circle problem when the input is a bunch of points, and we want to find minimum circle that covers all of them.
Just understand the equations of the circle and derive an equation (or a series) for find the answer then start implementing. Perhaps we will be able to help you in that given you have done something.
Can anyone please explain the world to screen function (c#) to me?
I want to understand what it does, how to use it and what I need to use it.
So far I know, that it converts 3d vectors to 2d vectors, that then can be shown on the monitor.
Now I want to know the steps needed to show a 3d point as a drawing on the screen.
Thx in advance :)
I am going to assume that your desired screen space runs from top to bottom so that {0, 0} is top left and {screenWidth, screenHeight} is bottom right. I am also going to assume that normalized device coordinates are in the range of [{-1, -1, 0}, {1, 1, 1}].
The first thing you wanna do, is convert your 3D world coordinates into what are known as Normalized Device Coordinates (NDC for short). This is an intermediary step which simplifies the mapping to ScreenSpace.
Normalized Device Coordinates are used to measure relative positions on the monitor. These are not pixel coordinates. The center of the screen is always 0, the top is always 1 and the bottom is always -1. The left side is always -1 and the right side is always 1.
Knowing view and projection matrices, the transformation is following:
Vector3 pointInNdc = Vector3.Transform(pointInWorld, view * projection);
To understand how this transformation works, you could take a look at the source code of Paradox Game Engine for example.
Once the coordinate is normalized, you want to map it into Screen Space:
To find the X coordinate, move it from range [-1,1] -> [0,2] by adding 1 to it and dividing it by 2 to move it from range [0,2] -> [0,1]. Multiply that by screen width and you have the X coordinate in Screen Space.
float screenX = (pointInNdc.X + 1) / 2f * screenWidth;
Finding Y coordinate is similar, but instead of adding 1, you need to subtract pointInNdc.Y from 1 to invert the coordinate upside down (move from Y running bottom to top to Y running top to bottom)
float screenY = (1 - pointInNdc.Y) / 2f * screenHeight;
There you have both X and Y coordinates of Screen Space. There are many great articles out there (such as this one) which describe this exact same process and also how to go back from ScreenToWorld.
Here is the full listing of WorldToScreen:
Vector2 WorldToScreen(Vector3 pointInWorld, Matrix view, Matrix projection, int screenWidth, int screenHeight)
{
Vector3 pointInNdc = Vector3.Transform(pointInWorld, view * projection);
float screenX = (pointInNdc.X + 1) / 2f * screenWidth;
float screenY = (1 - pointInNdc.Y) / 2f * screenHeight;
return new Vector2(screenX, screenY);
}
I am using this equation to work out the angles of my x, y and z compared to gravity:
directionalVector = Math.Sqrt(Math.Pow(accelForceX, 2) + Math.Pow(accelForceY, 2) + Math.Pow(accelForceZ, 2));
accelAngleX = (Math.Acos(accelForceX / directionalVector) * (180f / Math.PI)); ;
accelAngleY = (Math.Acos(accelForceY / directionalVector) * (180f / Math.PI));
accelAngleZ = (Math.Acos(accelForceZ / directionalVector) * (180f / Math.PI));
accelForceN is a reading from an accelerometers axis, measured in G's
This way produces a range of result from 0-180degress, no negative numbers.
How can I find the sign of the angles?
I think you are confused about what you are actually calculating here. Make sure you are aware that you are simply calculating the angle by using the definition of cosinus:
cos(accelAngleN*2*Math.Pi/360) = accelForceN/directionalVector
(the multiplication with 2Pi/360 merely transforms an angle into Radian). Now consider that the angular sum in a triangle is 180° and thus in this case a negative angle or an angle larger than 180° as result would not make any sense. This is just another way of looking at the fact that cos is not injective over the whole field of real numbers and thus arccos is defined as function on [-1,1] -> [0,Pi] (or [0°,180°] for that matter).
So what you are currently calculating is the angle of your x/y/z-vectors to your directional vector (rather than to gravity, which would be the z-achsis direction anyways, wouldn't it?) and from this standpoint your output is perfectly valid.
If you need help with transforming your result any further, please provide an example (2 dimensional should be good enough for the sake of simplicity) of what you are expecting.
I originally posted a much simpler (and less helpful) question, and have edited this to be more specific.
This animation from wikipedia shows basically what I want to accomplish, however - I'm hoping to have it flipped around, where it starts progressing more towards the destination and "up" (in this image), and then arcs more directly to the end point. However, I only have access to a starting point and ending point, what I am hoping to do is be able to determine the other points by specifying a "height" (or width, whatever you want to call it), to determine how high the arc actually goes.
http://en.wikipedia.org/wiki/File:Bezier_3_big.png (can't post image due to low rep)
I would like to be able to call a function with the start and end points and a height, and have it return all the points along the way of the curve.
Help or direction would be appreciated.
I have wrapped up a blog for calculating Bezier curve Angle and to determine its various points in my blog http://johnexalt.wordpress.com/2011/05/21/bezier-curve-angle-calculation-silverlight/
the code below shows how to calculate the bezier curve points at any given value of t(where t ranges from 0 to 100 % and is represented in 0- 1.
x = ((1 - t) * (1 - t) * p0.X) + (2 * t * (1 - t) * p1.X) + (t * t * p2.X);
//this statement is used to determine the x coordinate of the curve.
y = ((1 - t) * (1 - t) * p0.Y) + (2 * t * (1 - t) * p1.Y) + (t * t * p2.Y);
//this statement is used to determine the y coordinate of the curve.
x = Math.Round(x, 3);
y = Math.Round(y, 3);
angle = Math.Round(Angle(xold, yold, x, y), 3);
There was a previous article given by Carlos Femmer which helps in calculating the angle between 2 points. http://www.carlosfemmer.com/post/2006/02/Calculate-Angle-between-2-points-using-C.aspx.
Without loss of generality, suppose the ending point is on the x axis and the starting point is above and to the left of the ending point.
Imagine the starting point is at the top of a cliff, and the ending point is at the bottom of a cliff. Imagine you throw a ball horizontally from the starting point, such that gravity will pull it down so that it smacks exactly into the ending point.
That curve seems to have the properties you want. It starts shallow and then increases towards the vertical as the ball accelerates.
By changing the angle at which you throw the ball initially you can make the curve more shallow at the beginning. By changing the strength of gravity you can make it more steep at the end.
Does that curve fit your needs? Finding that curve is a pretty basic physics problem.
There seems to be a mechanism in .NET that can help you:
Graphics.DrawCurve
Draws a cardinal spline through a
specified array of Point structures
Also, a quick Google search found these
Writing Name Using Bezier Curves In C#
http://www.codeproject.com/KB/recipes/BezirCurves.aspx
http://www.codeproject.com/KB/cs/SplineInterpolation.aspx
http://www.c-sharpcorner.com/UploadFile/apundit/DrawingCurves11182005012515AM/DrawingCurves.aspx
You basically want a bezier curve with three control points - the start point, the end point and another point somewhere in between.
If the start point 1 is ( x1, y1 ) and the end point 2 is ( x2, y2 ) then the vector from point 1 to point 2 is ( dx = x2-x1, dy = y2-y1 ).
A point along the line by an amount along between zero and one is ( x1 + along * dx, y1 + along * dy ).
The vector ( -dy, dx ) is at right angles to the line, so if you want to go off the line by an amount above then the middle point would be ( x1 + along * dx - above * dy, y1 + along * dy + above * dx).
Vary the values of along and above until you find the sort of skewed curve you want.
Apart for the start-point and the end-point you need to describe the ”angle” or curvature of the arc. A Bezier curve can be good but they are usually implemented with longer sequences of points (as the curvature of the arc is defined by the other points in the line). Have a look at http://en.wikipedia.org/wiki/B%C3%A9zier_curve , at the bottom you can find some information about ”Quadratic curves”. I bet a quick google search will give you some implementation examples.
I've scored the internet for sources and have found a lot of useful information, but they are math sites trying to tell me how to solve what angle an object has to be at to reach y location. However, I'm trying to run a simulation, and haven't found any solid equations that can be implemented to code to simulate a parabolic curve. Can those with some knowledge of physics help me on this?
While Benny's answer is good, especially in its generality, you can solve your problem exactly rather than using finite integration steps. The equation you want is:
s = u*t + 0.5*a*t^2;
Look here for an explanation of where this comes from.
Here s is the displacement, u is the initial speed, a is the acceleration and t is time. This equation is only 1 dimensional, but can be easily used for your problem. All you need to do is split the motion of your projectile into two components: one parallel to your acceleration and one perpendicular. If we let Sx describe the displacement in the x direction and Sy the displacement in the y direction we get:
Sx = Ux*t + 0.5*Ax*t;
Sy = Uy*t + 0.5*Ay*t;
Now in your particular example Ax is 0 as the only acceleration is due to gravity, which is in the y direction, ie Ay = -g. The minus comes from the fact that gravity will be acting in the opposite direction to the original motion of the object. Ux and Uy come from simple trigonometry:
Ux = U*cos(angle);
Uy = U*sin(angle);
Putting this all together you get two equations describing where the projectile will be at a time t after being launched, relative to its starting position:
Sx = U*cos(angle)*t;
Sy = U*sin(angle)*t - 0.5*g*t^2;
Don't use the equations for position. Instead, use the equations for velocity. Calculate the new velocity each loop of your simulation from the object's old velocity and apply it to your object. You will need to know the elapsed time between each loop of the simulation. Of course, this works for vertical or horizontal velocity.
v_new = v_old + acceleration * delta_time (from wikipedia)
Then apply:
position_new = position_old + v_new * delta_time;
You can use a simple acceleration of -9.8 m/s (don't forget that "down" on the screen is really an increase in the vertical position! So you can use +9.8 for simplicity). Or you could get fancy and add variable acceleration (for example, from wind, if you are also modeling the horizontal motion of the object).
Basically, the acceleration you apply is based on the sum of forces applied to the object (force of gravity, friction, jet propulsion, etc.).
F_final = F1 + F2 + ... + Fn
The following can help you with that.
If you are modeling a force applied to the object, first break the force into it's horizontal and vertical components using:
F_horiz = F * sin( angle )
F_vert = F * cos( angle ) where angle is the angle between the force and the horizontal.
Then calculate the acceleration from the force using:
a = F / mass
(I give all credit for this knowledge to my first programming experience: GORILLA.BAS =) )
Some definitions:
x = x-coordinate (horizontal)
y = y-coordinate (vertical)
Vx = x-velocity
Vy = y-veloctiy
t = time
A = initial angle
V0 = intial velocity
g = acceleration due to gravity
Some equations:
Vx = V0*cos(A)
Vy = V0*sin(A) - g*t
x = V0*cos(A)*t
y = V0*sin(A)*t - (1/2)*g*t^2
Heres a nice library that might help you
http://sites.google.com/site/physics2d/
I haven't looked into it too much to be honest, I came across it in Scott Whitlock's code project article.
http://www.codeproject.com/KB/WPF/SoapBoxCorePinBallDemo.aspx