Determine whether a 3D object is hidden by another 3D object - wpf

I have some GeometryModel3D balls in a Viewport3D, Some of them are visible and some of them are hidden by a blue cube.
(Althouth the image below is in 2d lets pretend that all the objects are 3D)
I want to determine wich of the red balls can be seen and which are hidden.
How can I do this ?

This problem is also known as Occlusion Culling, although you're interested in counting the occluded primitives. Given the conditions of your scene, a brute force approach to solve this problem (given that you're using perspective projection) is the following pseudocode:
occludedSpheresCount = 0
spheres = {Set of spheres}
cubes = {Set of cubes}
normalizedCubes = {}
# First, build the set of normalized cubes (it means,
# take the cubes that are free in space and transform their
# coordinates to values between [-1, -1, -1] and [1, 1, 1], they are the same
# cubes but now the coordinates are laying in that range
# To do that, use the
ProjectionMatrix
projectionMatrix = GetProjectionMatrix(perspectiveCamera)
for each cube in cubes do
Rect3D boundingBox = cube.Bounds()
Rect3D normalizedBBox = projectionMatrix.transform(boundingBox)
cubes_normalized.add(normalizedBBox)
end for
# Now search every sphere, normalize it's bounding box
# and check if it's been occluded by some normalized cube
for each sphere in spheres do
Rect3D sphereBBox = sphere.Bounds()
Rect3D normalizedSphere = projectionMatrix.transform(sphereBBox)
for each normalizedCube in normalizedCubes do
x0 = normalizedCube.Location.X - (normalizedCube.Location.SizeX / 2)
y0 = normalizedCube.Location.Y - (normalizedCube.Location.SizeY / 2)
z0 = normalizedCube.Location.Z - (normalizedCube.Location.SizeZ / 2)
xf = normalizedCube.Location.X + (normalizedCube.Location.SizeX / 2)
yf = normalizedCube.Location.Y + (normalizedCube.Location.SizeY / 2)
sx0 <- normalizedSphere.Location.X - (normalizedSphere.Location.SizeX / 2)
sy0 <- normalizedSphere.Location.X - (normalizedSphere.Location.SizeY / 2)
sz0 <- normalizedSphere.Location.X - (normalizedSphere.Location.SizeZ / 2)
sxf <- normalizedSphere.Location.X + (normalizedSphere.Location.SizeX / 2)
syf <- normalizedSphere.Location.X + (normalizedSphere.Location.SizeY / 2)
# First, let's check that the normalized-sphere is behind the
# normalized-cube, to do that, let's compare their z-front values
if z0 > sz0 then
# Now that we know that the sphere is behind the frontface of the cube
# lets check if it is fully contained inside the
# the normalized-cube, in that case, it is occluded
if sx0 >= x0 and sxf <= xf and sy0 >= y0 and syf >= yf then
occludedSpheresCount++
# Here you can even avoid rendering the sphere altogether
end if
end if
end for
end for
A way to get the projectionMatrix is using the following code (extracted from here):
private static Matrix3D GetProjectionMatrix(PerspectiveCamera camera, double aspectRatio)
{
// This math is identical to what you find documented for
// D3DXMatrixPerspectiveFovRH with the exception that in
// WPF the camera's horizontal rather the vertical
// field-of-view is specified.
double hFoV = MathUtils.DegreesToRadians(camera.FieldOfView);
double zn = camera.NearPlaneDistance;
double zf = camera.FarPlaneDistance;
double xScale = 1 / Math.Tan(hFoV / 2);
double yScale = aspectRatio * xScale;
double m33 = (zf == double.PositiveInfinity) ? -1 : (zf / (zn - zf));
double m43 = zn * m33;
return new Matrix3D(
xScale, 0, 0, 0,
0, yScale, 0, 0,
0, 0, m33, -1,
0, 0, m43, 0);
}
The only drawback of this method is in the following case:
+--------------+--------------+
| -|- |
| / | \ |
| | | | |
| \ | / |
| -|- |
+--------------+--------------+
or
interception here
|
v
+----------+--+--------------+
| | -|- |
| /| | \ |
| | | | | |
| \| | / |
| | -|- |
+----------+--+--------------+
In which two intercepting cubes occlude the sphere, in that case, you have to build a set of sets of normalized cubes (Set{ Set{ cube1, cube2}, Set{cube3, cube4}, ... }) when two or more cube areas intercepts (that can be done in the first loop) and the contention test would be more complex. Don't know if that (cubes intercepting) is allowed in your program though
This algorithm is O(n^2) because is a brute force approach, hope this could give you a hint for the definitive solution, if you're looking for an efficient-more general solution, please use something like the Hierarchical Z Buffering

Related

Comparing rows in spark dataframe to obtain a new column

I'm a beginner in spark and I'm dealing with a large dataset (over 1.5 Million rows and 2 columns). I have to evaluate the Cosine Similarity of the field "features" beetween each row. The main problem is this iteration beetween the rows and finding an efficient and rapid method. I will have to use this method with another dataset of 42.5 Million rows and it would be a big computational problem if I won't find the most efficient way of doing it.
| post_id | features |
| -------- | -------- |
| Bkeur23 |[cat,dog,person] |
| Ksur312kd |[wine,snow,police] |
| BkGrtTeu3 |[] |
| Fwd2kd |[person,snow,cat] |
I've created an algorithm that evaluates this cosine similarity beetween each element of the i-th and j-th row but i've tried using lists or creating a spark DF / RDD for each result and merging them using the" union" function.
The function I've used to evaluate the cosineSimilarity is the following. It takes 2 lists in input ( the lists of the i-th and j-th rows) and returns the maximum value of the cosine similarity between each couple of elements in the lists. But this is not the problem.
def cosineSim(lista1,lista2,embed):
#embed = hub.KerasLayer(os.getcwd())
eps=sys.float_info.epsilon
if((lista1 is not None) and (lista2 is not None)):
if((len(lista1)>0) and (len(lista2)>0)):
risultati={}
for a in lista1:
tem = a
x = tf.constant([tem])
embeddings = embed(x)
x = np.asarray(embeddings)
x1 = x[0].tolist()
for b in lista2:
tem = b
x = tf.constant([tem])
embeddings = embed(x)
x = np.asarray(embeddings)
x2 = x[0].tolist()
sum = 0
suma1 = 0
sumb1 = 0
for i,j in zip(x1, x2):
suma1 += i * i
sumb1 += j*j
sum += i*j
cosine_sim = sum / ((sqrt(suma1))*(sqrt(sumb1))+eps)
risultati[a+'-'+b]=cosine_sim
cosine_sim=0
risultati=max(risultati.values())
return risultati
The function I'm using to iterate over the rows is the following one:
def iterazione(df,numero,embed):
a=1
k=1
emp_RDD = spark.sparkContext.emptyRDD()
columns1= StructType([StructField('Source', StringType(), False),
StructField('Destination', StringType(), False),
StructField('CosinSim',FloatType(),False)])
first_df = spark.createDataFrame(data=emp_RDD,
schema=columns1)
for i in df:
for j in islice(df, a, None):
r=cosineSim(i[1],j[1],embed)
if(r>0.45):
z=spark.createDataFrame(data=[(i[0],j[0],r)],schema=columns1)
first_df=first_df.union(z)
k=k+1
if(k==numero):
k=a+1
a=a+1
return first_df
The output I desire is something like this:
| Source | Dest | CosinSim |
| -------- | ---- | ------ |
| Bkeur23 | Ksur312kd | 0.93 |
| Bkeur23 | Fwd2kd | 0.673 |
| Ksur312kd | Fwd2kd | 0.76 |
But there is a problem in my "iterazione" function.
I ask you to help me finding the best way to iterate all over this rows. I was thinking also about copying the column "features" as "features2" and applying my function using WithColumn but I don't know how to do it and if it will work. I want to know if there's some method to do it directly in a spark dataframe, avoiding the creation of other datasets and merging them later, or if you know some method more rapid and efficient. Thank you!

How to solve this: two functions x = sin(x)/a

I tried to solve this equation in different ways but no luck:
Find the number of points of two functions.
f(x) = sin(x)
y = a
at a given a. In this case lets say a = 0.15
sin(x) = ax
= 0.15x
x = sin(x)/0.15
???
Can any one help with this question?
this is the question in the exact words:
Write a C program that would read in the value a and write out all the solutions (i.e. roots) of the equation sin(x) = a*x
NOTE: This equation has for sure one solution: x = 0. However, for low values of a, the line representing the equation y = a*x is sufficiently close to horizontal line to cross the sine wave several times. Your program should calculate and print the number of roots and their values.
HINT: Suppose you are to solve an equation f(x) = 0, where f(x) is a continuous function. Suppose further that you can find two values xlow and xhigh such that f(xlow) < 0 and f(xhigh) > 0 and the function f(x) crosses the x-axis only once somewhere between these values. You could proceed using the so called "binary search" technique, as follows:
Estimate the solution x as x = (xlow + xhigh) / 2.0
If |f(x)| < epsilon then print x as solution and exit; else
If f(x) < 0 then substitute xlow = x else substitute xhigh = x
Go to (1)
Use epsilon = 0.001
You could proceed using the so called "binary search" technique
This is the key to the solution. Actually it is the solution. Let me draw two functions:
f(x) g(x)
/ --
/ --
/ --
/ --
/--
-*
--/
-- /
-- /
^ ^
| |
xlow xhigh
You have xlow and xhigh as an estimate of where f(x) crosses g(x). In your question, f(x) = ax and g(x) = sin(x).
First, let's see why xlow and xhigh make a good estimate. If you notice, at xlow, we have f(x) < g(x) and at xhigh we have f(x) > g(x). Since the functions are continuous, then there is some point somewhere in between where f(x) == g(x).
Now let's look at the middle-point between xlow and xhigh:
f(x) g(x)
/ --
/ --
/ --
/ --
/--
-*
--/
-- /
-- /
^ ^ ^
| | |
xlow xmid xhigh
Now at xmid, we have f(x) > g(x) (in this example). So:
f(xhigh) > g(xhigh)
f(xmid) > g(xmid)
f(xlow) < g(xlow)
Since between xmid and xlow, the functions change bigger-ness-ship (in other words, f(x) - g(x) changes its sign), then the answer is for sure between xlow and xmid (note that there could still be an even number of solutions between xmid and xhigh, but we can't really tell at this point).
So, if we assign xhigh = xmid, we would have:
f(x) g(x)
/--
-*
--/
-- /
-- /
^ ^
| |
xlow xhigh
But this is the same problem as before! Except that we shrank the possible location of the solution by half. Repeating we have:
f(x) g(x)
/--
-*
--/
-- /
-- /
^ ^ ^
| | |
xlow xmid xhigh
f(xhigh) < g(xhigh)
f(xmid) > g(xmid)
f(xlow) > g(xlow)
This time, the sign of f(x) - g(x) changes between xmid and xhigh, so we would do xlow = xmid to cut away the first half of the range which we are not interested in. We get:
f(x) g(x)
/--
-*
--/
^ ^
| |
xlow xhigh
Again, the same problem, except we shrank the range where the solution could possibly be by half.
Repeating this in a while loop, there would be some point where |f(xmid) - g(xmid)| becomes almost zero (say smaller than 0.000001 (or 1e-6) (also note that absolute value)). In that case, we stop searching and we say that that particular xmid is the answer. (See here for why we don't check for equality, but closeness).
There is still one problem. With your particular functions, there could be many cross-sections. How do we find xlow and xhigh? Well, we want the range [xlow, xhigh] to contain only one solution. So we can incrementally find those ranges and find the cross-section in between them.
Assuming a > 0 (and solutions for x > 0), this is how the graph would look like:
----- f(x) = ax
_ _ __*__ _ g(x) = sin(x)
/ \ /_*___----- / \ /
/ *____---*- \ / \ /
|---- | | | | | |
| | | | | |
\ / \ / \ /
\_/ \_/ \_/
So let's see where the solutions can be. For sure, it's not where sin(x) < 0. Then on [2kπ, 2kπ + π/2] and [2kπ + π/2, 2kπ + π] there could be one solution each. The initial [0, π / 2] may or may not have a solution depending on a. So the safest way is to enumerate all such ranges, calculate f(x) - g(x) for both xlow and xhigh and look at their signs. If the sign doesn't change, there is no solution and we can move on. If it does change, we perform the binary search above.
When does the algorithm end? We know that g(x) = sin(x) <= 1 and we know that for a > 0, f(x) = ax is always increasing. So when you have f(xlow) > 1, then for sure there are no more solutions.
The algorithm would thus be:
Main Algorithm:
for k = 0,1,...
xlow = 2kπ
xhigh = 2kπ + π/2
binary_search(xlow, xhigh)
xlow = 2kπ + π/2
xhigh = 2kπ + π
binary_search(xlow, xhigh)
binary_search:
if axlow-sin(xlow) and axhigh-sin(xhigh) have the same sign
return no result in this range
do
xmid = (xhigh + xlow) / 2
diff = axmid - sin(axmid)
if diff and axlow-sin(xlow) have the same sign
xlow = xmid
else
xhigh = xmid
while abs(diff) > epsilon
return xmid
For the cases where a < 0, the solution is similar (except the ranges change to the other half of the sin cycle). By the way, for each x that you find above, -x is also a solution!
Obviously, there can only be solutions for
-1 <= ax <= 1
The monotonicity of the difference function h(x)=sin(x)-ax is the same on all intervals between the roots of the derivative h'(x)=cos(x)-a, and they are also the local maxima and minima of this function. So use the points
arccos(a), 2*pi-arccos(a), 2*pi+arccos(a), 4*pi-arccos(a), 4*pi+arccos(a), ...
as long as they are smaller than 1/abs(a) to define the search intervals. Use inside each of them, as per the answer of Shahbaz, the bisection method, or better the Illinois variant of the regula falsi method.
A brutefore approach without any optimization (therefore a naive approach) would to run a for loop on x, x running from 0 to 2*PI, PI = 22.0/7.0, and loop increment by say 0.001, if you get abs(sin(x) - a*x) < epsilon, where epsilon say equals to some kow value like 0.001 (depends on your granularity), you get it! This would solve your equation sin(x) = a*x

How to find out the intersection of two coplanar lines in C

I have two 3D lines which lie on the same plane. line1 is defined by a point (x1, y1, z1) and its direction vector (a1, b1, c1) while line2 is defined by a point (x2, y2, z2) and its direction vector (a2, b2, c2). Then the parametric equations for both lines are
x = x1 + a1*t; x = x2 + a2*s;
y = y1 + b1*t; y = y2 + b2*s;
z = z1 + c1*t; z = z2 + c2*s;
If both direction vectors are nonzeros, we can find out the location of intersection node easily by equating the right-hand-side of the equations above and solving t and s from either two of the three. However, it's possible that a1 b1 c1 a2 b2 c2 are not all nonzero so that I can't solve those equations in the same way. My current thought is to deal with this issue case by case, like
case1: a1 = 0, others are nonzero
case2: a2 = 0, others are nonzero
case3: b1 = 0, others are nonzero
...
However, there are so many cases in total and the implementation would become messy. Is there any good ways to tackle this problem? Any reference? Thanks a lot!
It is much more practical to see this as a vector equation. A dot . is a scalar product and A,n,B,m are vectors describing the lines. Point A is on the first line of direction n. Directions are normalized : n.n=1 and m.m=1. The point of intersection C is such that :
C=A+nt=B+ms
where t and s are scalar parameters to be computed.
Therefore (.n) :
A.n+ t=B.n+m.n s
t= (B-A).n+m.n s
And (.m):
A.m+n.m t=B.m+ s
A.m+n.m (B-A).n+(m.n)^2 s=B.m+ s
n.m(B-A).n+(A-B).m=(1-(m.n)^2).s
Since n.n=m.m=1 and n and m are not aligned, (m.n)^2<1 :
s=[n.m(B-A).n+(A-B).m]/[1-(m.n)^2]
t= (B-A).n+m.n s
You can solve this as a linear system:
| 1 0 0 -a1 0 | | x | | x1 |
| 0 1 0 -b1 0 | | y | | y1 |
| 0 0 1 -c1 0 | | z | = | z1 |
| 1 0 0 0 -a2 | | s | | x2 |
| 0 1 0 0 -b2 | | t | | y2 |
| 0 0 1 0 -c2 | | z2 |
x y z is the intersection point, and s t are the coefficients of the vectors. This solves the same equation that #francis wrote, with the advantage that it also obtains the solution that minimizes the error in case your data are not perfect.
This equation is usually expressed as Ax=b, and can be solved by doing x = A^(-1) * b, where A^(-1) is the pseudo-inverse of A. All the linear algebra libraries implement some function to solve systems like this, so don't worry.
It might be vital to remember that calculations are never exact, and small deviations in your constants and calculations can make your lines not exactly intersect.
Therefore, let's solve a more general problem - find the values of t and s for which the distance between the corresponding points in the lines is minimal. This is clearly a task for calculus, and it's easy (because linear functions are the easiest ones in calculus).
So the points are
[xyz1]+[abc1]*t
and
[xyz2]+[abc2]*s
(here [xyz1] is a 3-vector [x1, y1, z1] and so on)
The (square of) the distance between them:
([abc1]*t - [abc2]*s + [xyz1]-[xyz2])^2
(here ^2 is a scalar product of a 3-vector with itself)
Let's find a derivative of this with respect to t:
[abc1] * ([abc1]*t - [abc2]*s + [xyz1]-[xyz2]) (multiplied by 2, but this doesn't matter)
(here the first * is a scalar product, and the other *s are regular multiplications between a vector and a number)
The derivative should be equal to zero at the minimum point:
[abc1] * ([abc1]*t - [abc2]*s + [xyz1]-[xyz2]) = 0
Let's use the derivative with respect to s too - we want it to be zero too.
[abc1]*[abc1]*t - [abc1]*[abc2]*s = -[abc1]*([xyz1]-[xyz2])
-[abc2]*[abc1]*t + [abc2]*[abc2]*s = [abc2]*([xyz1]-[xyz2])
From here, let's find t and s.
Then, let's find the two points that correspond to these t and s. If all calculations were ideal, these points would coincide. However, at this point you are practically guaranteed to get some small deviations, so take and of these points as your result (intersection of the two lines).
It might be better to take the average of these points, to make the result symmetrical.

How to get all 24 rotations of a 3-dimensional array?

I have a 3-dimensional array. Think of it as a brick. There are 24 possible rotations of this brick (that keep its edges parallel to coordinate axes). How do I generate all corresponding 3-dimensional arrays?
A die (half a pair of dice) is handy for observing the 24 different orientations, and can suggest operation sequences to generate them. You will see that any of six faces can be uppermost, and the sides below can be rotated into four different cardinal directions. Let us denote two operations: “turn” and “roll”, where turn rotates the die about the z axis from one cardinal to the next, and roll rotates the die 90° away from you, so the away-face becomes the bottom face and the near face the top. These operations can be expressed using rotation matrices as mentioned in the answer of Felipe Lopes, or can be expressed as simple functions that when given (x,y,z) return (-y,x,z) or (x,z,-y), respectively.
Anyhow, if you place the die with 1 on the near face, 2 at right, and 3 on top, you will find that the following sequence of steps generates the twelve different orientations with 1, 2, or 3 spots on top: RTTTRTTTRTTT. Then the sequence RTR exposes 6, 4, 5 where 1, 2, 3 originally were, and a repeat of the sequence RTTTRTTTRTTT generates the twelve orientations with 4, 5, or 6 spots on top. The mentioned sequence is embedded in the following python code.
def roll(v): return (v[0],v[2],-v[1])
def turn(v): return (-v[1],v[0],v[2])
def sequence (v):
for cycle in range(2):
for step in range(3): # Yield RTTT 3 times
v = roll(v)
yield(v) # Yield R
for i in range(3): # Yield TTT
v = turn(v)
yield(v)
v = roll(turn(roll(v))) # Do RTR
p = sequence(( 1, 1, 1))
q = sequence((-1,-1, 1))
for i in sorted(zip(p,q)):
print i
The rationale for printing out a sorted list of transformed pairs of points is twofold: (i) any face orientation can be specified by the locations of two of its corners; (ii) it then is easy to check for uniqueness of each pair, eg by piping output to uniq.
Here is how the sorted output begins:
((-1, -1, -1), (-1, 1, 1))
((-1, -1, -1), (1, -1, 1))
((-1, -1, -1), (1, 1, -1))
((-1, -1, 1), (-1, 1, -1))
((-1, -1, 1), (1, -1, -1))
((-1, -1, 1), (1, 1, 1))
((-1, 1, -1), (-1, -1, 1))
((-1, 1, -1), (1, -1, -1))
((-1, 1, -1), (1, 1, 1))
Let X rotate 90 degrees around the X-axis and Y rotate 90 degrees around the Y-axis then the 24 possible unique combinations are (all possible combinations up to 5 rotations are given except those with four times the same rotation (eg XXXX, XXXXY XYYYY, etc):
1. I
2. X
3. Y
4. XX = YXXY
5. XY
6. YX
7. YY = XYYX
8. XXX = XYXXY = YXXYX = YXYXY = YYXYY
9. XXY = YXXYY = YYYXX
10. XYX = YXY
11. XYY = XXYYX = YYXXX
12. YXX = XXYYY = YYXXY
13. YYX = XXXYY = XYYXX
14. YYY = XXYXX = XYXYX = XYYXY = YXYYX
15. XXXY
16. XXYX = XYXY = YXYY
17. XXYY = YYXX
18. XYXX = YXYX = YYXY
19. XYYY
20. YXXX
21. YYYX
22. XXXYX = XXYXY = XYXYY = YXYYY
23. XYXXX = YXYXX = YYXYX = YYYXY
24. XYYYX = YXXXY
Of course you can use any two 90 degree rotations in place of the X and Y. For example, Y and Z.
Or, if you also use Z, a 90 degree rotation around the Z axis then 4 rotations suffice:
1. I
2. X = YXZ
3. Y = ZYX
4. Z = XZY
5. XX = XYXZ = YXXY = YXYZ = YXZX = YYZZ = YZXZ = ZXXZ = ZZYY
6. XY = YZ = ZX = XZYX = YXZY = ZYXZ
7. XZ = XXZY = YXZZ = YYYX = ZYYY
8. YX = XZZZ = YYXZ = ZYXX = ZZZY
9. YY = XXZZ = XYYX = YZYX = ZXYX = ZYXY = ZYYZ = ZYZX = ZZXX
10. ZY = XXXZ = XZYY = YXXX = ZZYX
11. ZZ = XXYY = XYZY = XZXY = XZYZ = XZZX = YYXX = YZZY = ZXZY
12. XXX
13. XXY = XYZ = XZX = YZZ = ZXZ
14. XXZ = ZYY
15. XYX = YXY = YYZ = YZX = ZXX
16. XYY = YZY = ZXY = ZYZ = ZZX
17. XZZ = YYX
18. YXX = ZZY
19. YYY
20. ZZZ
21. XXXY = XXYZ = XXZX = XYZZ = XZXZ = YZZZ = ZXZZ = ZYYX
22. XXYX = XYXY = XYYZ = XYZX = XZXX = YXYY = YYZY = YZXY = YZYZ = YZZX = ZXXY = ZXYZ = ZXZX = ZYZZ = ZZXZ
23. XYXX = XZZY = YXYX = YYXY = YYYZ = YYZX = YZXX = ZXXX
24. XYYY = YXXZ = YZYY = ZXYY = ZYZY = ZZXY = ZZYZ = ZZZX
These 24 matrices all exist of three column vectors that each exist of two zeroes and a minus one or plus one. On every row there are also exactly two zeroes. As such, they can easily be generated: the first column vector has six possibilities ((1,0,0), (-1,0,0), (0,-1,0), (0,1,0), (0,0,-1) and (0,0,1)), this corresponds to moving the positive X-axis to the positive or negative x, y or z axis. The second column vector only has four possibilities because it must contain a zero where the first column has a non-zero value. Finally the third column vector has only one place left where its plus or minus one can be. This gives 6 * 4 * 2 = 48 matrices, half of them mirror the original as well however (they are combination of a mirror and optionally a rotation). Hence only 24 are pure rotations. The matrices that are mirror operations will have a determinant equal to -1, the determinant of the pure rotations is 1.
James Waldby's answer is inspiring, and I want to add a slightly improved version with only two for-loops.
We know that there are 24 unique orientations. I calculated this by imagining a dice: there are 6 possible choices for the top face, and 4 possible rotations for each face on top.
What if we iterate with that idea? I thought. If we can figure out a way to travel all 6 faces of the dice, then we only need to observe the 4 rotations on each face, and we are done!
So I grabbed the nearest "brick" (in my case, a Vitasoy carton) and started rotating to see what would be the easiest pattern to visit all 6 faces. If we introduce an additional counter-clockwise turn, such that our operations are:
Roll (in a fixed direction, e.g. so that the face facing you is now rotated downwards)
Turn CW (along a fixed axis, e.g. so that the face facing you is turned clockwise, but still facing you)
Turn CCW (along the same axis as the last one)
Then we can visit all faces by doing:
Roll -> Turn CW -> Roll -> Turn CCW -> Roll -> Turn CW -> Roll -> Turn CCW -> Roll -> Turn CW -> Roll -> Turn CCW
With the last roll and turn, we are back to the original orientation. As you can see, it is a repeated sequence of roll + alternating CW turns and CCW turns.
Now, if we expand this to include all rotations of each face we visit, this becomes:
Roll -> 3x Turn CW -> Roll -> 3x Turn CCW -> Roll -> 3x Turn CW -> Roll -> 3x Turn CCW -> Roll -> 3x Turn CW -> Roll -> 3x Turn CCW
...and we are back to where we started! This can be translated into two for-loops (one fewer!):
def sequence(m):
for roll_index in range(6):
m = roll(m)
yield(m)
for turn_index in range(3):
m = turn_cw(m) if roll_index % 2 == 0 else turn_ccw(m)
yield(m)
You can use rotation matrices. Rotating a 3D array around the x-axis means that the element at position (i,j,k) will be mapped to position (i,-k,j). Of course, if your array is 0-indexed, you probably have to replace -k with size-1-k or something like that.
Similarly, rotating around the y-axis maps (i,j,k) to (k,j,-i). These two rotations can be represented as matrices. For the x-axis rotation:
|i'| |1 0 0| |i|
|j'| = |0 0 -1|*|j|
|k'| |0 1 0| |k|
And for the y-axis rotation:
|i'| |0 0 1| |i|
|j'| = |0 1 0|*|j|
|k'| |-1 0 0| |k|
Any general rotation can be described as a sequence of those two rotations. Applying two rotations consecutively is just multiplying the 3x3 matrices. So, if you find all possible products of them, you'd get 24 matrices (including the identity), each one corresponds to a valid rotation of your array. It's a little tricky to find all possible multiplications, because they don't commute.
I think you can just brute-force all products of the form (A^p)*(B^q)*(A^r)*(B^s), where A and B are the two matrices before and p,q,r,s are their powers, and range from 0 to 3 (exponentiating A or B to 4 will take them back to the identity matrix).
Doing it this way, you can generate all 24 valid rotation matrices, and rotate the 3D array using each one of them, taking the care to shift the negative indexes so that you don't access out of bounds.
import numpy as np
def rotations(array):
for x, y, z in permutations([0, 1, 2]):
for sx, sy, sz in itertools.product([-1, 1], repeat=3):
rotation_matrix = np.zeros((3, 3))
rotation_matrix[0, x] = sx
rotation_matrix[1, y] = sy
rotation_matrix[2, z] = sz
if np.linalg.det(rotation_matrix) == 1:
yield np.matmul(rotation_matrix, array)
all_rotations = list(rotations(np.array(array)))
Idea is to generate all coordinates relabelings with possible axis' direction changes, ex. (-z, y, x). The question that remains is whether all coordinates relabelings are obtainable from (x, y, z) axes using only rotations. Half of the 6 * (2^3) = 48 labelings aren't because they are rotations of a mirrored version of the (x, y, z) cooridnates (left-handed coordinates, https://en.wikipedia.org/wiki/Right-hand_rule).
Rows of the corresponding rotation matrix A of relabeling operation will have only one value in each row. The value determines which axis to select on that index, and whether to flip the axis.
A * (x, y, z) = (-z, y, x)
| 0, 0, -1 |
A = | 0, 1, 0 |
| 1, 0, 0 |
We keep only those rotations, whose det(A) == 1 meaning that only rotations were applied by the operation. det(A) == -1 means that it is a rotation with mirroring.

Fast Hypotenuse Algorithm for Embedded Processor?

Is there a clever/efficient algorithm for determining the hypotenuse of an angle (i.e. sqrt(a² + b²)), using fixed point math on an embedded processor without hardware multiply?
If the result doesn't have to be particularly accurate, you can get a crude
approximation quite simply:
Take absolute values of a and b, and swap if necessary so that you have a <= b. Then:
h = ((sqrt(2) - 1) * a) + b
To see intuitively how this works, consider the way that a shallow angled line is plotted on a pixel display (e.g. using Bresenham's algorithm). It looks something like this:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | | | | | | | | | | | | | | | |*|*|*| ^
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| | | | | | | | | | | | |*|*|*|*| | | | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| | | | | | | | |*|*|*|*| | | | | | | | a pixels
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| | | | |*|*|*|*| | | | | | | | | | | | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
|*|*|*|*| | | | | | | | | | | | | | | | v
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
<-------------- b pixels ----------->
For each step in the b direction, the next pixel to be plotted is either immediately to the right, or one pixel up and to the right.
The ideal line from one end to the other can be approximated by the path which joins the centre of each pixel to the centre of the adjacent one. This is a series of a segments of length sqrt(2), and b-a segments of length 1 (taking a pixel to be the unit of measurement). Hence the above formula.
This clearly gives an accurate answer for a == 0 and a == b; but gives an over-estimate for values in between.
The error depends on the ratio b/a; the maximum error occurs when b = (1 + sqrt(2)) * a and turns out to be 2/sqrt(2+sqrt(2)), or about 8.24% over the true value. That's not great, but if it's good enough for your application, this method has the advantage of being simple and fast. (The multiplication by a constant can be written as a sequence of shifts and adds.)
For the record, here are a few more approximations, listed in roughly
increasing order of complexity and accuracy. All these assume 0 ≤ a ≤ b.
h = b + 0.337 * a // max error ≈ 5.5 %
h = max(b, 0.918 * (b + (a>>1))) // max error ≈ 2.6 %
h = b + 0.428 * a * a / b // max error ≈ 1.04 %
Edit: to answer Ecir Hana's question, here is how I derived these
approximations.
First step. Approximating a function of two variables can be a
complex problem. Thus I first transformed this into the problem of
approximating a function of one variable. This can be done by choosing
the longest side as a “scale” factor, as follows:
h = √(b2 + a2)
= b √(1 + (a/b)2)
= b f(a/b) where f(x) = √(1+x2)
Adding the constraint 0 ≤ a ≤ b means we are only concerned with
approximating f(x) in the interval [0, 1].
Below is the plot of f(x) in the relevant interval, together with the
approximation given by Matthew Slattery (namely (√2−1)x + 1).
Second step. Next step is to stare at this plot, while asking
yourself the question “how can I approximate this function cheaply?”.
Since the curve looks roughly parabolic, my first idea was to use a
quadratic function (third approximation). But since this is still
relatively expensive, I also looked at linear and piecewise linear
approximations. Here are my three solutions:
The numerical constants (0.337, 0.918 and 0.428) were initially free
parameters. The particular values were chosen in order to minimize the
maximum absolute error of the approximations. The minimization could
certainly be done by some algorithm, but I just did it “by hand”,
plotting the absolute error and tuning the constant until it is
minimized. In practice this works quite fast. Writing the code to
automate this would have taken longer.
Third step is to come back to the initial problem of approximating a
function of two variables:
h ≈ b (1 + 0.337 (a/b)) = b + 0.337 a
h ≈ b max(1, 0.918 (1 + (a/b)/2)) = max(b, 0.918 (b + a/2))
h ≈ b (1 + 0.428 (a/b)2) = b + 0.428 a2/b
Consider using CORDIC methods. Dr. Dobb's has an article and associated library source here. Square-root, multiply and divide are dealt with at the end of the article.
One possibility looks like this:
#include <math.h>
/* Iterations Accuracy
* 2 6.5 digits
* 3 20 digits
* 4 62 digits
* assuming a numeric type able to maintain that degree of accuracy in
* the individual operations.
*/
#define ITER 3
double dist(double P, double Q) {
/* A reasonably robust method of calculating `sqrt(P*P + Q*Q)'
*
* Transliterated from _More Programming Pearls, Confessions of a Coder_
* by Jon Bentley, pg. 156.
*/
double R;
int i;
P = fabs(P);
Q = fabs(Q);
if (P<Q) {
R = P;
P = Q;
Q = R;
}
/* The book has this as:
* if P = 0.0 return Q; # in AWK
* However, this makes no sense to me - we've just insured that P>=Q, so
* P==0 only if Q==0; OTOH, if Q==0, then distance == P...
*/
if ( Q == 0.0 )
return P;
for (i=0;i<ITER;i++) {
R = Q / P;
R = R * R;
R = R / (4.0 + R);
P = P + 2.0 * R * P;
Q = Q * R;
}
return P;
}
This still does a couple of divides and four multiples per iteration, but you rarely need more than three iterations (and two is often adequate) per input. At least with most processors I've seen, that'll generally be faster than the sqrt would be on its own.
For the moment it's written for doubles, but assuming you've implemented the basic operations, converting it to work with fixed point shouldn't be terribly difficult.
Some doubts have been raised by the comment about "reasonably robust". At least as originally written, this was basically a rather backhanded way of saying that "it may not be perfect, but it's still at least quite a bit better than a direct implementation of the Pythagorean theorem."
In particular, when you square each input, you need roughly twice as many bits to represent the squared result as you did to represent the input value. After you add (which needs only one extra bit) you take the square root, which gets you back to needing roughly the same number of bits as the inputs. Unless you have a type with substantially greater precision than the inputs, it's easy for this to produce really poor results.
This algorithm doesn't square either input directly. It is still possible for an intermediate result to underflow, but it's designed so that when it does so, the result still comes out as well as the format in use supports. Basically, the situation in which it happens is that you have an extremely acute triangle (e.g., something like 90 degrees, 0.000001 degrees, and 89.99999 degrees). If it's close enough to 90, 0, 90, we may not be able to represent the difference between the two longer sides, so it'll compute the hypotenuse as being the same length as the other long side.
By contrast, when the Pythagorean theorem fails, the result will often be a NaN (i.e., tells us nothing) or, depending on the floating point format in use, quite possibly something that looks like a reasonable answer, but is actually wildly incorrect.
You can start by reevaluating if you need the sqrt at all. Many times you are calculating the hypotenuse just to compare it to another value - if you square the value you're comparing against you can eliminate the square root altogether.
Unless you're doing this at >1kHz, multiply even on a MCU without hardware MUL isn't terrible. What's much worse is the sqrt. I would try to modify my application so it doesn't need to calculate it at all.
Standard libraries would probably be best if you actually need it, but you could look at using Newton's method as a possible alternative. It would require several multiply/divide cycles to perform, however.
AVR resources
Atmel App note AVR200: Multiply and Divide Routines (pdf)
This sqrt function on AVR Freaks forum
Another AVR Freaks post
Maybe you could use some of Elm Chans Assembler Libraries and adapt the ihypot-function to your ATtiny. You would need to replace the MUL and maybe (i haven't checked) some other instructions.

Resources