I want to draw a circle with a specified angle of inclination in 3D space using Python. Similar to the image below:
Image
I can already draw circles in 2D. I modified my program by referring to the link below:
Masking a 3D numpy array with a tilted disc
import numpy as np
import matplotlib.pyplot as plt
r = 5.0
a, b, c = (0.0, 0.0, 0.0)
angle = np.pi / 6 # "tilt" of the circle
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.set_xlim(-10,10)
ax.set_ylim(-10,10)
ax.set_zlim(-10,10)
phirange = np.linspace(0, 2 * np.pi, 300) #to make a full circle
x = a + r * np.cos(phirange)
y = b + r * np.sin(phirange)
z= c
ax.plot(x, y, z )
plt.show()
Now I can draw the circle in 3D space, but I can't get the circle to tilt at the angle I want.
I tried to modify the code in the Z part, the circle can be tilted, but not the result I want.
z = c + r * np.cos(phirange) * np.sin(angle)
Result image:
Do the X and Y parts also need to be modified? What should I do?
update: the circle tilt with other axis
Let i = (1, 0, 0), j = (0, 1, 0). Those are the direction vectors of the x-axis and y-axis, respectively. Those two vectors form an orthonormal basis of the horizontal plane. Here "orthonormal" means the two vectors are orthogonal and both have length 1.
A circle on the horizontal plane with centre C and radius r consists in all points that can be written as C + r * (cos(theta) * i + sin(theta) * j), for all values of theta in range [0, 2 pi]. Note that this works with i and j, but it would have worked equally with any other orthonormal basis of the horizontal plane.
A circle in any other plane can be described exactly the same way, by replacing i and j with two vectors that form an orthonormal basis of that plane.
According to your image, the "tilted plane at angle tilt" has the following orthonormal basis:
a = (cos(tilt), 0, sin(tilt))
b = (0, 1, 0)
You can check that these are two vectors in your plane, that they are orthogonal and that they both have norm 1. Thus they are indeed an orthonormal basis of your plane.
Therefore a circle in your plane, with centre C and radius r, can be described as all the points C + r * (cos(theta) * a + sin(theta) * b), where theta is in range [0, 2 pi].
In terms of x,y,z, this translates into the following system of three parametric equations:
x = x_C + r * cos(theta) * x_a + r * sin(theta) * x_b
y = y_C + r * cos(theta) * y_a + r * sin(theta) * y_b
z = z_C + r * cos(theta) * z_a + r * sin(theta) * z_b
This simplifies a lot, because x_b, y_a, z_b are all equal to 0:
x = x_C + r * cos(theta) * x_a # + sin(theta) * x_b, but x_b == 0
y = y_C + r * sin(theta) * y_b # + cos(theta) * y_a, but y_a == 0
z = z_C + r * cos(theta) * z_a # + sin(theta) * z_b, but z_b == 0
Replacing x_a, y_b and z_a by their values:
x = x_C + r * cos(tilt) * cos(theta)
y = y_C + r * sin(theta)
z = z_C + r * sin(tilt) * cos(theta)
In python:
import numpy as np
import matplotlib.pyplot as plt
# parameters of circle
r = 5.0 # radius
x_C, y_C, z_C = (0.0, 0.0, 0.0) # centre
tilt = np.pi / 6 # tilt of plane around y-axis
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.set_xlim(-10,10)
ax.set_ylim(-10,10)
ax.set_zlim(-10,10)
theta = np.linspace(0, 2 * np.pi, 300) #to make a full circle
x = x_C + r * np.cos(tilt) * np.cos(theta)
y = y_C + r * np.sin(theta)
z = z_C + r * np.sin(tilt) * np.cos(theta)
ax.plot(x, y, z )
plt.show()
Related
Given center S, radius r and angle alpha, How can I find the coordinates of interceptions between Line-Circle (the line is given by a point(always center of the circle) and angle).The line is endless, so there are always 2 interceptions. It has to be a general formula/code.
The result are the coordinates.
How can I do that?
Imagine rotating the line angle =1 and getting all the points of a circle.
Circle-Line Intersection from https://mathworld.wolfram.com/ did not work for me because my line is defined by a point and an angle.
The solution I ended up with:
x1 = centerx + R * cos(alpha)
y1 = centery + R * sin(alpha)
x2 = centerx - R * cos(alpha)
y2 = centery - R * sin(alpha)
I saw few similar post to this question, but none that provided a concrete final working solution.
I'm working with OptiTrack with python, Motive 2.2.0, NatNet SDK 4.0 using the NatNetClient from the examples provided with the SDK.
The coordinates system is such that Y is Up, X is backward and Z is left.
I want to translate the quaternion to a coordinates system in which X is forward, Y is right (or left, the more simple one) and Z is up.
I'm getting the quaternion qx, qy, qz, qw values, I think that in this order but I'm not sure (if you can find it in the documentation of Motive/OptiTrack it also could help).
Now I'm trying by a plenty of similar ways that I think should work to get the Euler angles: pitch, roll and yaw, and then check for the three rotations in which I should get 0 to -180 and then to 180 and back to 0 (or vice versa), but it is always results in roll direction which goes from 0 to 90 then back to 0 (positive grow and then positive decrease) and then to -90 and then again back to 0 (negative decrease and then negative grow).
Correct me on this too, but I that is the result that serves as the sanity check for assurance the transformation to Euler was done correctly, right ?
First I take the pose and the quat and create an SO(3) matrix (just for convenience) :
def pos_quat2SE(quat_data):
# Assumed quat_data order is (pos, quat)
SO = R.from_quat(quat_data[3:7]).as_matrix()
SE = np.matrix(np.eye(4))
SE[0:3,0:3] = np.matrix(SO)
SE[0:3,3] = np.matrix(quat_data[0:3]).T
return SE_motive
where quat_data is a simple concatenation of pos (3 values) and quat (4 values) as mentioned.
I tried to use scipy function:
from scipy.spatial.transform import Rotation as R
euler_transformed = R.from_matrix(SE_motive[0:3, 0:3]).as_euler('zyx', degrees=False)
but I'm not sure what should be the right argument for as_euler.
Also tried to use the following approach using this auxiliary function:
def SE_motive2transoform(SE_motive):
T_Yup2NED_inv = np.array([[1, 0, 0, 0], [0, 0, -1, 0], [0, 1, 0, 0], [0, 0, 0, 1]])
T_Yup2NED = invert_SE(T_Yup2NED_inv)
SE_transformed = SE_motive # T_Yup2NED
return SE_transformed
The next two tries gave the same result:
using this functions which should be equivalent:
def euler_from_quaternion(x, y, z, w):
"""
Convert a quaternion into euler angles (roll, pitch, yaw)
roll is rotation around x in radians (counterclockwise)
pitch is rotation around y in radians (counterclockwise)
yaw is rotation around z in radians (counterclockwise)
"""
t0 = +2.0 * (w * x + y * z)
t1 = +1.0 - 2.0 * (x * x + y * y)
roll_x = math.atan2(t0, t1)
t2 = +2.0 * (w * y - z * x)
t2 = +1.0 if t2 > +1.0 else t2
t2 = -1.0 if t2 < -1.0 else t2
pitch_y = math.asin(t2)
t3 = +2.0 * (w * z + x * y)
t4 = +1.0 - 2.0 * (y * y + z * z)
yaw_z = math.atan2(t3, t4)
return roll_x, pitch_y, yaw_z # in radians
def quaternion_to_rotation_matrix(q):
"""Return a 3x3 rotation matrix representing the orientation specified by a quaternion in x,y,z,w format.
The matrix is a Python list of lists.
"""
x = q[0]
y = q[1]
z = q[2]
w = q[3]
return [[w * w + x * x - y * y - z * z, 2 * (x * y - w * z), 2 * (x * z + w * y)],
[2 * (x * y + w * z), w * w - x * x + y * y - z * z, 2 * (y * z - w * x)],
[2 * (x * z - w * y), 2 * (y * z + w * x), w * w - x * x - y * y + z * z]]
on the input new_quat = np.vstack([quato[0], -quato[2], quato[1], quato[3]]) where qauto is the returned quaternion from the motive system in its mentioned above coordinate system. As much as I understand rearrangement of the quaternion values in that way should give me them in an xyz coordinate system and then I should been able to use the above function or even as_euler with xyz argument and etc. but it didn't work.
What is the shortest, working and elegant way to achieve the transform with the sanity check working of course ?
can it be done in that fashion:
def SE_motive2transoform(SE_motive):
T_Yup2NED_inv = np.array([[1, 0, 0, 0], [0, 0, -1, 0], [0, 1, 0, 0], [0, 0, 0, 1]])
T_Yup2NED = invert_SE(T_Yup2NED_inv)
SE_transformed = SE_motive # T_Yup2NED
return SE_transformed
Thank you in advance.
I have a 3D planar (all vertices lie in some plane) polygon with vertices: [(x1, y1, z1) ... (x1, y1, z1)].
I would like to transform this polygon so that I'm viewing it orthographically (as if I'm looking at it straight on).
How can this be done in Python?
I assume you have no information except for vertex coordinates.
Take three non-collinear (perhaps consequent) vertices C, A, B. Calculate normalized vector (divide by vector length)
b = (B - A) / |B - A|
then normal vector (using vector/cross multiplication)
N = b.cross.(A-C) and normalize it
un = N / |N|
and another unit vector in polygon plane
v = b.cross.n
Now we want find such matrix of affine transformations, that transforms vertex A into point (0,0,0), edge AB will be collinear with OX axis, normal will be collinear with OZ axis, vector q will be collinear with OY axis. This all means that rotated polygon will lie in OXY plane.
Mathematically: points A, u=A+b, v=A+q, n=A+un should be transformed in quadruplet (0,0,0), (1,0,0), (0,1,0), (0,0,1). In matrix form
[Ax ux vx nx] [0 1 0 0]
M * [Ay uy vy ny] = [0 0 1 0]
[Az uz vz nz] [0 0 0 1]
[1 1 1 1 ] [1 1 1 1]
or
M * S = D
Using matrix inverse
M * S * Sinv = D * Sinv
and finally
M = D * Sinv
So calculate matrix M and multiply it with every vertex coordinates. New coordinates should have zero Z-component (or very small due to numerical errors).
You can perform all described operations with numpy library with a little code
Example with specific data
Quick-made implementation in plain Python for reference
import math
def calcMatrix(ax, bx, cx, ay, by, cy, az, bz, cz):
ux, uy, uz = bx - ax, by - ay, bz - az
mag = math.sqrt(ux*ux+uy*uy +uz*uz)
ux, uy, uz = ux / mag, uy / mag, uz / mag
Cx, Cy, Cz = ax - cx, ay - cy, az - cz
nx, ny, nz = uy * Cz - uz * Cy, uz * Cx - ux * Cz, ux * Cy - uy * Cx
mag = math.sqrt(nx*nx+ny*ny+nz*nz)
nx, ny, nz = nx / mag, ny / mag, nz / mag
vx, vy, vz = uy * nz - uz * ny, uz * nx - ux * nz, ux * ny - uy * nx
denom = 1.0 / (ux*ux+uy*uy + uz*uz)
M = [[0.0]*4 for _ in range(4)]
M[3][3] = 1.0
M[0][0] = denom*(ux)
M[0][1] = denom*(uy)
M[0][2] = denom*(uz)
M[0][3] = denom*(-ax*ux-ay*uy+az*uz)
M[1][0] = denom*(vx)
M[1][1] = denom*(vy)
M[1][2] = denom*(vz)
M[1][3] = -denom*(ax*vx-ay*vy+az*vz)
M[2][0] = denom*(nx)
M[2][1] = denom*(ny)
M[2][2] = denom*(nz)
M[2][3] = denom*(-ax*nx-ay*ny+az*nz)
return M
def mult(M, vec):
res = [0]*4
for k in range(4):
for i in range(4):
res[k] += M[k][i] * vec[i]
return res
#test corners and middle point
M = calcMatrix(1, 0, 0, 0, 1, 0, 0, 0, 1)
#print(M)
p = [1, 0, 0, 1]
print(mult(M, p))
p = [0, 1, 0, 1]
print(mult(M, p))
p = [0, 0, 1, 1]
print(mult(M, p))
p = [1/3, 1/3, 1/3, 1]
print(mult(M, p))
test results:
[0.0, 0.0, 0.0, 1.0]
[1.4142135623730951, 0.0, 0.0, 1.0]
[0.7071067811865476, 1.2247448713915892, 0.0, 1.0]
[0.7071067811865476, 0.4082482904638631, 1.1102230246251565e-16, 1.0]
Find a normal n to the polygon, by means of a cross-product between two non-parallel sides. Take the cross-product of n with a vertical vector, to get an horizontal vector u. Then take the cross product of n and u to get v, and normalize the vectors. u and v are parallel to the plane of the polygon and orthogonal to each other.
Finally, for every vertex p compute the 2D coordinates (p.u, p.v) which show you the polygon in its plane.
numpy supplies the cross and dot vector functions. Also linalg.norm (or sqrt(dot(v, v))).
Here's a robust approach using NumPy (project(); the rest is test code).
import numpy
import scipy.spatial
def project(x):
# Center the plane on the origin
x = x - numpy.mean(x, axis=0)
# Compute the Singular Value Decomposition
u, s, v = numpy.linalg.svd(x)
# Return the top two principal components
return u[:, :2] # numpy.diag(s[:2])
def test():
n = 10
x = (numpy.random.rand(n, 2) # numpy.random.rand(2, 3)) + numpy.random.rand(3)
y = project(x)
print(x.shape, y.shape)
print(
numpy.max(
numpy.abs(
scipy.spatial.distance_matrix(x, x)
- scipy.spatial.distance_matrix(y, y)
)
)
)
if __name__ == "__main__":
test()
Sample output:
(10, 3) (10, 2)
5.551115123125783e-16
I am coding a game in Unity that number of your soldiers are increasing/decreasing by some triggers. I want to position my soldier objects like a full circle, so they will always be near each other(like same distances) even if their number is increasing or decreasing. How can I manage this?
You can start with some simple relatively ordered distribution of positions and by applying a dynamical system approach/gradient decent type iteration, you can let the positions converge to a much more structured pattern. I wrote such implementation in python, it is in vectorized form, but I also added an equivalent function with for loops, to illustrate the structure of the function. The final ordered pattern is inspired by the stable equilibrium position that a bunch of discs of same radius r would form if they are hold by springs, one for every two of them. To ease up the computations, I squared the spring tensions, thus avoiding square roots, so not exactly like the typical physics model, but close to it.
import numpy as np
import matplotlib.pyplot as plt
def Grad(pos, r):
Dq = - pos[:, :, np.newaxis] + pos[:, np.newaxis, :]
D = Dq[0,:,:]*Dq[0,:,:] + Dq[1,:,:]*Dq[1,:,:] + np.identity(Dq.shape[1])
Dq = (1 - r**2 / D) * Dq
return - Dq.sum(axis=2)
def Grad_flow(q_, r, step):
Q = q_
n_iter = 0
while True:
n_iter = n_iter + 1 # you can count the number of iterations needed to reach the equilibrium
Q_prev = Q
Q = Q - step * Grad(Q, r)
if np.sum(np.abs((Q.T).dot(Q) - (Q_prev.T).dot(Q_prev))) < 1e-5:
return Q
'''
Test:
'''
p = np.array([[-3, 3], [-1, 3], [1,3], [3,3],
[-3, 1], [-1, 1], [1,1], [3,1],
[-3,-1], [-1,-1], [1,-1], [3,-1],
[-3,-3], [-1, -3], [1, -3], [3,-3],
[-2, 1], [-1,2],[2,-2], [-2,-2],
[2,2], [2,0]]).T
r = 0.5
step = 0.01
q = Grad_flow(p, r, step)
'''
Plot:
'''
fig, axs = plt.subplots(1,1)
axs.set_aspect('equal')
axs.plot(q[0,:], q[1,:], 'ro')
axs.plot(p[0,:], p[1,:], 'bo')
plt.grid()
plt.show()
You start from the blue positions and you make them converge to the red positions:
Here is the loop version of the Grad function:
def Grad(pos, r):
grad = np.zeros(pos.shape, dtype=float)
for i in range(pos.shape[1]):
for j in range(pos.shape[1]):
if not i==j:
d_pos_0 = pos[0, i] - pos[0, j]
d_pos_1 = pos[1, i] - pos[1, j]
m = d_pos_0*d_pos_0 + d_pos_1*d_pos_1
m = 1 - r*r / m
grad[0, i] = grad[0, i] + m * d_pos_0
grad[1, i] = grad[1, i] + m * d_pos_1
return grad
Of course, all of this is a bit heuristic and I cannot promise full generality, so you have to play and select the parameters r which is half-distance between positions, iteration step-size step, the initial position p and so on.
Supposing you are working in the horizontal plane, you can define you much rotation for each of your soldiers, and the find that point in the plane converting cartesian coordinates (x, y) into polar ones (R, fi), add theta to fi and then convert back to cartesian:
// Rotate B around A by angle theta
private (float x, float y) Rotate(
(float x, float y) A,
(float x, float y) B,
float theta) {
float fi = Math.Atan2(B.y - A.y, B.x - A.x) + theta;
float R = Math.Sqrt((A.y - B.y) * (A.y - B.y) + (A.x - B.x) * (A.x - B.x));
return (A.x + R * Math.Cos(fi), A.y + R * Math.Sin(fi));
}
Another option that does exactly the same thing, but not using polar coords:
// Rotate B around A by angle theta clockwise
private (float x, float y) Rotate(
(float x, float y) A,
(float x, float y) B,
float theta)
{
float s = Math.Sin(theta);
float c = Math.Cos(theta);
// translate point back to origin:
B.x -= A.x;
B.y -= A.y;
// rotate point clockwise
float xnew = B.x * c - B.y * s;
float ynew = B.x * s + B.y * c;
// translate point back:
B.x = xnew + A.x;
B.y = ynew + A.y;
return B;
}
If you want your soldiers equally distributed in a circle you would need to calcualte the rotation angle of each just with float angle = 360 / numSoldiers;.
If your game is in 3d and you are working in the floor plane (XZ) you can change the .ys by .zs in the code.
You can also check how the algorithms work in a simple unity project cubes or in a console c# app to understand them and to check how they just perform the rotation of a vector's end point around its origin to return the rotated point. I think that is what you would need to find the points of interest for the position of your soldiers.
I have a surface in a 3D space, and I need to calculate the coordinates of the center of that surface. The surface is a polygon.
I found this formula:
X1 += SUM[(xi + xi+1 ) * (xi* yi+1 - xi+1 * yi )]/6/area
Y1 += SUM[(yi + yi+1) * (xi* yi+1 - xi+1 * yi )]/6/area
But it only works for 2D. It gives x and y values. In my case I need 3 coordinates, x y z.
How can I do that ?
I need the center of the surfaces selected, but they may have any shape as they are a polygon.
Thanks a lot.
It's easy to prove that the centroid of the projection of a polygon on any plane is the projection of the centroid of that polygon on that plane.
So just calculate the centroid of the projections of the polygon on x-y plane and y-z plane, and you'll get the coordinate of the centroid of that polygon.
#!/usr/bin/env ruby
Point = Struct.new(:x, :y, :z)
def centroid(vertices, dimensions: [:x, :y])
area = 0
centroid_x, centroid_y = 0, 0
vertices.count.times do |i|
v1, v2 = vertices[i, 2]
v2 ||= vertices[0]
x1, y1 = dimensions.map{|d| v1.send(d)}
x2, y2 = dimensions.map{|d| v2.send(d)}
a = x1 * y2 - x2 * y1
area += a
centroid_x += (x1 + x2) * a
centroid_y += (y1 + y2) * a
end
area *= 0.5
centroid_x /= (6.0 * area)
centroid_y /= (6.0 * area)
[centroid_x, centroid_y]
end
vertices = [
Point.new(1, 0, 0),
Point.new(0, 2, 0),
Point.new(0, 0, 3)
]
p centroid(vertices, dimensions: [:x, :y])
p centroid(vertices, dimensions: [:y, :z])
p centroid(vertices, dimensions: [:z, :x])
prints
[0.3333333333333333, 0.6666666666666666]
[0.6666666666666666, 1.0]
[1.0, 0.3333333333333333]
#Aetherus it didn't work for my example:
X1 = 0
Y1 = 0
Y11 = 0
Z1 = 0
for i in 0..d.vertices.size-2
X1 += (d.vertices[i].position[0] + d.vertices[i+1].position[0]) * (d.vertices[i].position[0] * d.vertices[i+1].position[1] - d.vertices[i+1].position[0] * d.vertices[i].position[1])
Y1 += (d.vertices[i].position[1] + d.vertices[i+1].position[1]) * (d.vertices[i].position[0] * d.vertices[i+1].position[1] - d.vertices[i+1].position[0] * d.vertices[i].position[1])
Y11 += (d.vertices[i].position[1] + d.vertices[i+1].position[1]) * (d.vertices[i].position[1] * d.vertices[i+1].position[2] - d.vertices[i+1].position[1] * d.vertices[i].position[2])
Z1 += (d.vertices[i].position[2] + d.vertices[i+1].position[2]) * (d.vertices[i].position[1] * d.vertices[i+1].position[2] - d.vertices[i+1].position[1] * d.vertices[i].position[2])
end
x=X1/(6.0*(d.area))
y=Y1/(6.0*(d.area))
y1=Y11/(6.0*(d.area))
z=Z1/(6.0*(d.area))
UI.messagebox("x1 #{x} Y1 #{y} \n y11 #{y1} z1 #{z}")
With "d" is my polygon the xy is for x-y projection and y1z for y-z projection.