Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rotation of meshes around different origins #186

Closed
AndrewAnnex opened this issue Jun 22, 2020 · 13 comments
Closed

rotation of meshes around different origins #186

AndrewAnnex opened this issue Jun 22, 2020 · 13 comments

Comments

@AndrewAnnex
Copy link

AndrewAnnex commented Jun 22, 2020

Description

I am trying to figure out how to rotate a mesh around an arbitrary center point (ideally the center point of the mesh) given a normal vector. Specifically I calculate a scalar field in a narrow column that is straight up and down relative to the xyz axes, I then use pyvista to get the gradient of the scalar field for that mesh and I want to rotate the mesh such that it points in the same direction as the gradient of the scalar field.

Example Data

p = pv.Plotter()
n = np.array([2.38485370e-06, 2.41350030e-08, 1.14723631e-04])
c = np.array([-1213.76492667191, 524216.7597523882, -2476.815087890625])
b = np.array([-1214.26492667191,
 -1213.26492667191,
 524216.2597523882,
 524217.2597523882,
 -2496.715087890625,
 -2456.915087890625])
p.add_mesh(pv.Cube(bounds=b), opacity=0.4, color='red') # origional mesh
p.add_mesh(pv.Plane(c, n, j_size=40, i_size=40), color='orange') # perpendicular plane
p.add_mesh(pv.Arrow(c, n, scale=12), color='blue') # normal direction I want the cube to be rotated to be parrelel to (perpendicular to plane)
p.view_xz()
p.show(height=800)
@adeak
Copy link
Member

adeak commented Jun 22, 2020

u is undefined in your example. Is it supposed to be n? And it's probably easier to see if a solution is spot-on if you take two directions that are a bit more further apart. Say, 30 degrees?

@AndrewAnnex
Copy link
Author

yes, sorry u was unit vector version on n so they are the same, unfortunately I need this case to work but I am unsure of the proper conversion of an arbitrary angle to a normal vector. I don't see this as a blocker for finding a solution to this question so the provided angle should be fine.

@adeak
Copy link
Member

adeak commented Jun 22, 2020

Yes, it was more of a debugging suggestion. Like how debugging linear algebra is easier if you use 2d arrays with non-equal dimensions, because then it's obvious when some sizes don't match up so mistakes are louder.

@AndrewAnnex
Copy link
Author

I'll review some of my linear algebra, I think given a normal vector and a vector defining the typical coordinate space there are ways of determining the rotation matrix between them, I think the "base" vector would just be 0,0,1 ... maybe one of these will work https://math.stackexchange.com/questions/180418/calculate-rotation-matrix-to-align-vector-a-to-vector-b-in-3d

@adeak
Copy link
Member

adeak commented Jun 22, 2020

Personally, I usually use Rodrigues' rotation formula for contructing rotation matrices. Then it's a matter of finding the two vectors (one for the normal, one for the dataset, and rotating one into the other around the cross product of these two vectors).

As for the translation, if you have a 3d rotation matrix R and you want to rotate any point around point O you have to shift O into the origin of the coordinate system, transform the point and shift the origin back to O, i.e.

P -> R @ (P - O) + O

is the transformed point (which is the transformation you have to apply to each point).

That being said, this is just the pure math background, and odds are PyVista already has high-level functionality that helps with these tasks (.transform on your dataset, for instance).

@AndrewAnnex
Copy link
Author

The following is close... I used this implementation for the formula https://github.com/robEllenberg/comps-plugins/blob/master/python/rodrigues.py, but it looks like opencv also has it implemented

from numpy import sin,cos,dot,eye
from numpy.linalg import norm

def rodrigues(r):
    def S(n):
        Sn = np.array([[0,-n[2],n[1]],[n[2],0,-n[0]],[-n[1],n[0],0]])
        return Sn
    theta = norm(r)
    if theta > 1e-30:
        n = r/theta
        Sn = S(n)
        R = eye(3) + sin(theta)*Sn + (1-cos(theta))*dot(Sn,Sn)
    else:
        Sr = S(r)
        theta2 = theta**2
        R = eye(3) + (1-theta2/6.)*Sr + (.5-theta2/24.)*dot(Sr,Sr)
    return R


# above from https://github.com/robEllenberg/comps-plugins/blob/master/python/rodrigues.py, but I removed the mat call

p = pv.Plotter()
n = np.array([2.38485370e-06, 2.41350030e-08, 1.14723631e-04])
u = n /np.linalg.norm(n)
c = np.array([-1213.76492667191, 524216.7597523882, -2476.815087890625])
b = np.array([-1214.26492667191,
 -1213.26492667191,
 524216.2597523882,
 524217.2597523882,
 -2496.715087890625,
 -2456.915087890625])
d= pv.Cube(bounds=b)
p.add_mesh(d, opacity=0.4, color='red') # origional mesh
RM = rodrigues(u)
RM
f = pv.PolyData((RM @ (d.points-c).T).T+c)
p.add_mesh(f.delaunay_3d(), opacity=0.4, color='lightgreen') # new mesh I want to be correct
p.add_mesh(pv.Plane(c, u, j_size=40, i_size=40), color='orange') # perpendicular plane
p.add_mesh(pv.Arrow(c, u, scale=12), color='blue') # normal direction I want the cube to be rotated to be parrelel to (perpendicular to plane)
p.view_xz()
p.show(height=800)

here is a screen shot:
image

@adeak
Copy link
Member

adeak commented Jun 22, 2020

Since you mentioned opencv, I'd sooner use scipy. You could construct a Rotation from the vector and angle, and transform the points with that. Or use the .align_vectors method to construct the rotation (but this will raise a warning in case of a one-to-one vector mapping).

@AndrewAnnex
Copy link
Author

that seems to work well/even better, for the align_vectors case since I have the full scalar field I could also pass in the vector for each point on the A side, with a corresponding [0,0,1] for the B side, I just tried it and it works, but I don't know if it is necessary or even smart to do yet I suspect that the scalar field gradient is mostly the same in my grid, so I think it should be safe to do... if it makes sense mathematically I don't know.

from scipy.spatial.transform import Rotation

p = pv.Plotter()
n = np.array([2.38485370e-06, 2.41350030e-08, 1.14723631e-04])
u = n /np.linalg.norm(n)
c = np.array([-1213.76492667191, 524216.7597523882, -2476.815087890625])
b = np.array([-1214.26492667191,
 -1213.26492667191,
 524216.2597523882,
 524217.2597523882,
 -2496.715087890625,
 -2456.915087890625])
d= pv.Cube(bounds=b)
p.add_mesh(d, opacity=0.4, color='red') # original mesh
R, _ = Rotation.align_vectors([u], [[0,0,1]])
f = pv.PolyData((R.as_matrix() @ (d.points-c).T).T+c)
p.add_mesh(f.delaunay_3d(), opacity=0.4, color='lightgreen') # new mesh I want to be correct
p.add_mesh(pv.Plane(c, u, j_size=40, i_size=40), color='orange') # perpendicular plane
p.add_mesh(pv.Arrow(c, u, scale=12), color='blue') # normal direction I want the cube to be rotated to be parallel to (perpendicular to plane)
p.view_xz()
p.show(height=800)

@adeak
Copy link
Member

adeak commented Jun 22, 2020

I should emphasize that I'm not familiar with the PyVista aspect of the problem, so I'm just musing about the brute-force solution.

Depends on what you mean by "passing in the full scalar field". Hearing that I'd think you're passing in a set of spatial points that correspond to your long rectangle (or something close enough), in which case passing those for an aligning rotation probably makes sense (assuming the point mesh is shifted to the origin first). Anyway, one point of the Rotation class is that you don't need to convert it to a matrix and matmul yourself: R.apply(d.points - c) + c should work.

@banesullivan
Copy link
Member

So.. the problem here boils down to:

how to rotate a mesh around an arbitrary center point (ideally the center point of the mesh) given a normal vector.

I did something like this when implementing the CylinderStructured mesh generator:

https://github.com/pyvista/pyvista/blob/9cdc6f953a62b5f6a834d92d85f2be471d89cd83/pyvista/utilities/geometric_objects.py#L161-L173

also, there is pv.core.common.axis_rotation() which isn't documented.

HTH... maybe?

@banesullivan
Copy link
Member

Also, #149

@AndrewAnnex
Copy link
Author

I think for axis_rotation, which I didn't know about as it is not listed in the docs, I would need to know the angles in degrees which I didn't have in hand (but I am sure is possible to find). I like the scipy solution (as I also get an error metric which is always fun to have just in case), maybe it makes sense to add the solution from the cylinderstructured/#149 into a new filter

@AndrewAnnex
Copy link
Author

I'll close this issue for now as I think It is now working for me

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants