Rotating 3D cube perspective problem - graphics

Since I was 13 and playing around with AMOS 3D I've been wanting to learn how to code 3D graphics. Now, 10 years later, I finally think I have have accumulated enough maths to give it a go.
I have followed various tutorials, and defined screenX (and screenY, equivalently) as
screenX = (pointX * cameraX) / distance
(Plus offsets and scaling.)
My problem is with what the distance variable actually refers to. I have seen distance being defined as the difference in z between the camera and the point. However, that cannot be completely right though, since x and y have the same effect as z on the actual distance from the camera to the point. I implemented distance as the actual distance, but the result gives a somewhat skewed perspective, as if it had "too much" perspective.
My "actual distance" implementation was along the lines of:
distance = new Vector(pointX, pointY, cameraZ - pointZ).magnitude()
Playing around with the code, I added an extra variable to my equation, a perspectiveCoefficient as follows:
distance = new Vector(pointX * perspectiveCoefficient,
pointY * perspectiveCoefficient, cameraZ - pointZ).magnitude()
For some reason, that is beyond me, I tend to get the best result setting the perspectiveCoefficient to 1/sqrt(2).
My 3D test cube is at http://vega.soi.city.ac.uk/~abdv866/3dcubetest/3dtest.svg. (Tested in Safari and FF.) It prompts you for a perspectiveCoefficient, where 0 gives a perspective without taking x/y distance into consideration, and 1 gives you a perspective where x, y and z distance is equally considered. It defaults to 1/sqrt(2). The cube can be rotated about x and y using the arrow keys. (For anyone interested, the relevant code is in update() in the View.js file.)
Grateful for any ideas on this.

Usually, projection is done on the Z=0 plane from an eye position behind this plane. The projected point is the intersection of the line (Pt,Eye) with the Z=0 plane. At the end you get something like:
screenX = scaling * pointX / (1 + pointZ/eyeDist)
screenY = scaling * pointY / (1 + pointZ/eyeDist)
I assume here the camera is at (0,0,0) and eye at (0,0,-eyeDist). If eyeDist becomes infinite, you obtain a parallel projection.

Related

Splitting quadratic Bezier curve into several unequal parts

I am dealing with clipping of quadratic Beziér curves. Clipping is a standard graphics task. Typically, no matter what we display on a screen, we only want to render the part that fits into the screen bounds, as an optimization.
For straight lines, there is something called Cohen-Sutherland algorithm, and a slightly extended version of this algorithm is the Sutherland–Hodgman algorithm, where the first solution is for dealing with lines and the second one for polygons.
Essentially, the algorithms split the computer screen into tik-tac-toe -like squares, where the central square is what fits on the screen, and we special case each of left/right and above/below. After, when one end of the line is right off the screen and the other is not, we replace the x coordinate for this point with the screen's max value of x, and calculate the y value for it. This becomes the new endpoint of the clipped line. Pretty simple and it works well.
With Beziér curves, the same approach can be taken, only in addition to the ends, we need to consider control points. In the case of a quadratic curve, there is only one control.
To clip the curve, we can do something very similar to Cohen-Sutherland. Only, depending on the situation, we might need to cut the original curve into up to five (5) pieces. Just like both ends of a straight line might be offscreen, while the center is visible, the same situation needs to be handled with curves, yet here we only need to deal with the hull [height] of the curve causing a mid-section to be invisible. Therefore, we might end up with two new curves, after the clipping.
Finding one of the coordinates for these curves is pretty easy. It is still the min/max coordinate for one of the axis, and the value of the other coordinate. There is prior art for this, for example even calculate x for y is a good starting point. We want to adopt the formula so vectors turn into separate x and y coordinates, but the rest is doable.
Next, however, we still have an unsolved problem these one or two new curves, are completely new quadratic curves and each will have therefore a new control point.
There is a thread at split quadratic curve into two where the author seems to be doing kind of what I need, albeit in a slightly different way. There is an accepted answer, yet I could not get the results to work.
I want to end-up with a function like:
function clipSegment(sx, sy, cx, cy, ex, ey, bounds) {
let curves: {sx, sy, cx, cy, ex, ey}[] = [];
...
return curves;
}
It should take coordinates and the bounds object that would have both min and max for both x and y coordinates. I think that Cohen-Sutherland approach with squares and bit-codes should work here just as well. We get more cases for curves, but everything is doable. My problem is the new control point coordinates. For example, we could calculating t from one of the coordinates, doing something like:
function getQuadraticPoint(t, sx, sy, cp1x, cp1y, ex, ey) {
const x = (1 - t) * (1 - t) * sx + 2 * (1 - t) * t * cp1x + t * t * ex;
const y = (1 - t) * (1 - t) * sy + 2 * (1 - t) * t * cp1y + t * t * ey;
return { x, y };
}
Once we have the new start and/or beginning, how do we get the new control points?
Some developers I found online, working on similar problems, recommended just working with t and changing the interval from t from 0 to 1 to 0 to t. This however won't work easily for Canvas 2D API. The 2D Path thing needs the control point and the end point [after the pen move to the beginning with moveTo].
I believe that the quadratic Beziér case should have a closed-form solution. Yet, I have not figured out what it is. Any ideas?

Calculate the number of circles that fit on the circumference of another circle

I'm looking for an algorithm (or pseudo code) that can calculate the maximum number of (smaller) circles with diameter "s" that can be squeezed into the circumference of another (larger) circle with radius "r" ...
Image: http://teasy.space/images/terracolony-squeezingcircles2.jpg
You can alternate between radius/diameter etc if you wish -- as these are the only 2 parameters (other than the center (large circle) coordinate) that i have, i.e. that are known ...
The outer circles may not overlap but can fit "snug" together ...
After various upgrades to my routine through the years, I'm currently using an algorithm that is not perfect (and it needs to be accurate or the galaxy breaks down lol)
which does a broad interpolation between small outside circle diameter and large inside circle circumference, to somewhat accurately plot the circle count in a polygon style fitting pattern, which causes problems (i.e. overlaps) when using larger outside circles ...
; try to fit a random number of circles
num_Circles = Rand( min,max )
; check if the number of circles exceed the maximum that can fit
If num_Circles * SmallCircle_Diameter > LargeCircle_Circumference
; adjust the amount accordingly
num_Circles = LargeCircle_Circumference / SmallCircle_Diameter
End If
Another assumption is that the size of the smaller outer circles never exceeds that of the larger inner circle ...
something less to worry about ;)
I'm using this algorithm for one of my projects called Terra Colony, based on Gravity Well, a 2D space/gravity realtime colonization simulation game with moons, planets, stars, black/white holes, etc
Image: http://teasy.space/images/terracolony-squeezingcircles1.jpg
This is an issue that has plagued this project for over a decade!
Hopefully you can point me in the right direction :D
I have previously done many experiments and wrote different programs to find a solution, and have traveled the internet looking for formulas and solutions which in the end are very close, but not close enough! :P
Thank you! <3
Teasy
P.S. I tried to add the tag "circumference" but it apparently requires "1500 reputation" (points i guess, maybe to prevent spam)
There is formula that establishes relation between radius of big circle R, radius of small circle r and number of (touching) small circles N
R = r / Sin(Pi/N)
So maximum number of small circles might be found as
Sin(Pi/N) = r / R
Pi / N = arcsin(r / R)
and finally
N = Pi / arcsin(r / R)
Example:
R=5
r=2.5
so
N = Pi / arcsin(1/2) =
Pi / (Pi/6) =
6
Given the diam. of the small circle 'd' and the number of them 'c'
then the dia. of the large circle 'D' is
D=d/sin(180/c)

What is the fastest way to find the center of an irregular convex polygon?

I'm interested in a fast way to calculate the rotation-independent center of a simple, convex, (non-intersecting) 2D polygon.
The example below (on the left) shows the mean center (sum of all points divided by the total), and the desired result on the right.
Some options I've already considered.
bound-box center (depends on rotation, and ignores points based on their relation to the axis).
Straight skeleton - too slow to calculate.
I've found a way which works reasonably well, (weight the points by the edge-lengths) - but this means a square-root call for every edge - which I'd like to avoid.(Will post as an answer, even though I'm not entirely satisfied with it).
Note, I'm aware of this questions similarity with:What is the fastest way to find the "visual" center of an irregularly shaped polygon?
However having to handle convex polygons increases the complexity of the problem significantly.
The points of the polygon can be weighted by their edge length which compensates for un-even point distribution.
This works for convex polygons too but in that case the center point isn't guaranteed to be inside the polygon.
Psudo-code:
def poly_center(poly):
sum_center = (0, 0)
sum_weight = 0.0
for point in poly:
weight = ((point - point.next).length +
(point - point.prev).length)
sum_center += point * weight
sum_weight += weight
return sum_center / sum_weight
Note, we can pre-calculate all edge lengths to halve the number of length calculations, or reuse the previous edge-length for half+1 length calculations. This is just written as an example to show the logic.
Including this answer for completeness since its the best method I've found so far.
There is no much better way than the accumulation of coordinates weighted by the edge length, which indeed takes N square roots.
If you accept an approximation, it is possible to skip some of the vertices by curve simplification, as follows:
decide of a deviation tolerance;
start from vertex 0 and jump to vertex M (say M=N/2);
check if the deviation along the polyline from 0 to M exceeds the tolerance (for this, compute the height of the triangle formed by the vertices 0, M/2, M);
if the deviation is exceeded, repeat recursively with 0, M/4, M/2 and M/2, 3M/4, M;
if the deviation is not exceeded, assume that the shape is straight between 0 and M.
continue until the end of the polygon.
Where the points are dense (like the left edge on your example), you should get some speedup.
I think its easiest to do something with the center of masses of the delaunay triangulation of the polygon points. i.e.
def _centroid_poly(poly):
T = spatial.Delaunay(poly).simplices
n = T.shape[0]
W = np.zeros(n)
C = 0
for m in range(n):
sp = poly[T[m,:],:]
W[m] = spatial.ConvexHull(sp).volume
C += W[m] +np.mean(sp, axis = 0)
return C / np.sum(W)
This works well for me!

finding value of a point between measured points on a 2D plane

I'm trying to find the best way to calculate this. On a 2D plane I have fixed points all with an instantaneous measurement value. The coordinates of these points is known. I want to predict the value of a movable point between these fixed points. The movable point coodinates will be known. So the distance betwwen the points is known as well.
This could be comparable to temperature readings or elevation on topography. I this case I'm wanting to predict ionospheric TEC of the mobile point from the fixed point measurements. The fixed point measurements are smoothed over time however I do not want to have to store previous values of the mobile point estimate in RAM.
Would some sort of gradient function be the way to go here?
This is the same algorithm for interpolating the height of a point from a triangle.
In your case you don't have z values for heights, but some other float value for each triangle vertex, but it's the same concept, still 3D points.
Where you have 3D triangle points p, q, r and test point pt, then pseudo code from the above mathgem is something like this:
Vector3 v1 = q - p;
Vector3 v2 = r - p;
Vector3 n = v1.CrossProduct(v2);
if n.z is not zero
return ((n.x * (pt.x - p.x) + n.y * (pt.y - p.y)) / -n.z) + p.z
As you indicate in your comment to #Phpdevpad, you do have 3 fixed points so this will work.
You can try contour plots especially contour lines. Simply use a delaunay triangulation of the points and a linear transformation along the edges. You can try my PHP implementations https://contourplot.codeplex.com for geographic maps. Another algorithm is conrec algorithm from Paul Bourke.

Given a set of points, how do I approximate the major axis of its shape?

Given a "shape" drawn by the user, I would like to "normalize" it so they all have similar size and orientation. What we have is a set of points. I can approximate the size using bounding box or circle, but the orientation is a bit more tricky.
The right way to do it, I think, is to calculate the majoraxis of its bounding ellipse. To do that you need to calculate the eigenvector of the covariance matrix. Doing so likely will be way too complicated for my need, since I am looking for some good-enough estimate. Picking min, max, and 20 random points could be some starter. Is there an easy way to approximate this?
Edit:
I found Power method to iteratively approximate eigenvector. Wikipedia article.
So far I am liking David's answer.
You'd be calculating the eigenvectors of a 2x2 matrix, which can be done with a few simple formulas, so it's not that complicated. In pseudocode:
// sums are over all points
b = -(sum(x * x) - sum(y * y)) / (2 * sum(x * y))
evec1_x = b + sqrt(b ** 2 + 1)
evec1_y = 1
evec2_x = b - sqrt(b ** 2 + 1)
evec2_y = 1
You could even do this by summing over only some of the points to get an estimate, if you expect that your chosen subset of points would be representative of the full set.
Edit: I think x and y must be translated to zero-mean, i.e. subtract mean from all x, y first (eed3si9n).
Here's a thought... What if you performed a linear regression on the points and used the slope of the resulting line? If not all of the points, at least a sample of them.
The r^2 value would also give you information about the general shape. The closer to 0, the more circular/uniform the shape is (circle/square). The closer to 1, the more stretched out the shape is (oval/rectangle).
The ultimate solution to this problem is running PCA
I wish I could find a nice little implementation for you to refer to...
Here you go! (assuming x is a nx2 vector)
def majAxis(x):
e,v = np.linalg.eig(np.cov(x.T)); return v[:,np.argmax(e)]

Resources