From 9db0348acb7ae5911594d3f848c347afa20db60b Mon Sep 17 00:00:00 2001 From: andersct <andersct@umich.edu> Date: Sat, 24 Oct 2020 20:22:37 -0400 Subject: [PATCH] Add asteroids for space examples - add display for asteroid - generate random convex asteroids by sampling normals - add functions to fix surface triangulations - add figure setting for space background --- examples/display_asteroid.py | 50 +++++++++++++++++++++++ examples/display_shuttle_model.py | 7 +++- rigid_body_models/shuttle.py | 1 + rigid_body_models/triangulated_surface.py | 39 ++++++++++++++++++ scene_elements_3d/__init__.py | 0 scene_elements_3d/asteroid.py | 45 ++++++++++++++++++++ vis/triangulated_surface.py | 6 ++- vis/xyz_utils.py | 9 ++++ 8 files changed, 154 insertions(+), 3 deletions(-) create mode 100644 examples/display_asteroid.py create mode 100644 scene_elements_3d/__init__.py create mode 100644 scene_elements_3d/asteroid.py diff --git a/examples/display_asteroid.py b/examples/display_asteroid.py new file mode 100644 index 0000000..2666f78 --- /dev/null +++ b/examples/display_asteroid.py @@ -0,0 +1,50 @@ +import numpy as np +import plotly.graph_objects as go +import scene_elements_3d.asteroid as ast +from vis import triangulated_surface as vtrs +from vis.xyz_utils import set_space_background + + +def main(is_surface_normals=False): + seed = np.random.randint(1000) + print('seed: {}'.format(seed)) + # seed = 0 + np.random.seed(seed) + mu = np.array([ + [0., 0, 0], + [2, 0, 1], + ]) + sigma = np.array([ + [ + [2., 0, 0], + [0, 1, 0], + [0, 0, 1], + ], + [ + [1., 0, .2], + [0, 1, 0], + [.2, 0, 1], + ], + ]) + model = ast.generate_convex_asteroid(mu, sigma, 100) + + fig = go.Figure() + fig.add_traces([vtrs.make_model_mesh(model)]) + if is_surface_normals: + fig.add_traces(vtrs.make_surface_normals( + model.vertices, model.triangles)) + title_str = 'Asteroid' + fig.update_layout( + title=title_str, autosize=False, + width=1200, height=600, + scene=dict( + xaxis=dict(range=[-8, 8]), + yaxis=dict(range=[-6, 6]), + zaxis=dict(range=[-6, 6]), + ), showlegend=False) + set_space_background(fig) + fig.show() + + +if __name__ == '__main__': + main(is_surface_normals=False) diff --git a/examples/display_shuttle_model.py b/examples/display_shuttle_model.py index 829955e..b4366fe 100644 --- a/examples/display_shuttle_model.py +++ b/examples/display_shuttle_model.py @@ -2,9 +2,10 @@ import plotly.graph_objects as go from rigid_body_models.shuttle import Shuttle from vis import triangulated_surface as vtrs from vis import triangulated_thruster_model as vttm +from vis.xyz_utils import set_space_background -def main_display_mesh(is_thrusters=True): +def main_display_mesh(is_thrusters=True, is_space_bg=True): model = Shuttle(rho=1e6) fig = go.Figure() fig.add_traces([vtrs.make_model_mesh(model)]) @@ -23,8 +24,10 @@ def main_display_mesh(is_thrusters=True): yaxis=dict(range=[-6, 6]), zaxis=dict(range=[-6, 6]), ), showlegend=is_thrusters) + if is_space_bg: + set_space_background(fig) fig.show() if __name__ == '__main__': - main_display_mesh(is_thrusters=True) + main_display_mesh(is_thrusters=True, is_space_bg=True) diff --git a/rigid_body_models/shuttle.py b/rigid_body_models/shuttle.py index 24e8a3b..4a44bbe 100644 --- a/rigid_body_models/shuttle.py +++ b/rigid_body_models/shuttle.py @@ -86,3 +86,4 @@ class Shuttle(trs.TriangulatedSurface): _, _, im = trs.calculate_mass_properties( self.vertices, self.triangles, rho) self.im = im + self.color = 'white' diff --git a/rigid_body_models/triangulated_surface.py b/rigid_body_models/triangulated_surface.py index 3a9a0e7..010089d 100644 --- a/rigid_body_models/triangulated_surface.py +++ b/rigid_body_models/triangulated_surface.py @@ -69,3 +69,42 @@ def calculate_mass_properties(vertices, triangles, rho): [-s[6], -s[5], s[7] + s[8]], ]) return m, center, im + + +def remove_unused_vertices(vertices, triangles): + """ + Remove vertices that do not form a triangle, and relabel triangle vertices + to their new indices. + :param vertices: n_points, 3 | includes all points forming triangle faces + :param triangles: n_faces, 3 | indices of vertices that form each triangle + :return: vertices, triangles + """ + used_inds = np.unique(triangles.ravel()) + vertices_new = vertices[used_inds] + triangles_new = triangles.copy() + for i in range(used_inds.size): + triangles_new[triangles_new == used_inds[i]] = i + return vertices_new, triangles_new + + +def make_convex_shape_triangles_consistent(vertices, triangles): + """ + For a convex shape described by a triangulated surface, ensure + each triangle's normal faces outward. + :param vertices: n_points, 3 | includes all points forming triangle faces + :param triangles: n_faces, 3 | indices of vertices that form each triangle + :return: triangles: n_faces, 3 | triangles made consistent + """ + used_inds = np.unique(triangles.ravel()) + center = vertices[used_inds].mean(axis=0) + triangle_centers = np.array([ + vertices[triangles[i]].mean(axis=0) for i in range(len(triangles)) + ]) + outward_vec = triangle_centers - center + triangle_normals = np.cross( + vertices[triangles[:, 1]] - vertices[triangles[:, 0]], + vertices[triangles[:, 2]] - vertices[triangles[:, 0]], + ) + is_mismatch = np.einsum('ij,ij->i', outward_vec, triangle_normals) < 0 + triangles[is_mismatch, :] = triangles[is_mismatch, :][:, [0, 2, 1]] + return triangles diff --git a/scene_elements_3d/__init__.py b/scene_elements_3d/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/scene_elements_3d/asteroid.py b/scene_elements_3d/asteroid.py new file mode 100644 index 0000000..ed1b98f --- /dev/null +++ b/scene_elements_3d/asteroid.py @@ -0,0 +1,45 @@ +import numpy as np +from rigid_body_models import triangulated_surface as trs +from scipy.spatial import ConvexHull + + +def generate_convex_asteroid(mu_list, sigma_list, n_samples_per, **kwargs): + """ + Generate asteroid from the convex hull of samples taken + from a list of normals. + :param mu_list: n, 3 | means of multivariate normals + :param sigma_list: n, 3, 3 | covariance matrix of multivariate normals + :param n_samples_per: number of samples per normal + :return: + vertices: + triangles: + """ + samples = [] + for mu, sigma in zip(mu_list, sigma_list): + L = np.linalg.cholesky(sigma) + samples_i = (L.dot(np.random.randn(3, n_samples_per))).T + mu + samples.append(samples_i) + samples = np.vstack(samples) # n*n_samples_per, 3 + hull = ConvexHull(samples) + triangles = hull.simplices + vertices = samples + vertices, triangles = trs.remove_unused_vertices(vertices, triangles) + triangles = trs.make_convex_shape_triangles_consistent(vertices, triangles) + asteroid = Asteroid(vertices, triangles, **kwargs) + return asteroid + + +class Asteroid(trs.TriangulatedSurface): + + def __init__(self, vertices, triangles, rho=2000.): + super(Asteroid, self).__init__(rho, vertices, triangles) + m, center, im = trs.calculate_mass_properties( + self.vertices, self.triangles, rho) + self.m = m + self.im = im + self.center = 0 * center + self.vertices -= center + _, _, im = trs.calculate_mass_properties( + self.vertices, self.triangles, rho) + self.im = im + self.color = 'grey' diff --git a/vis/triangulated_surface.py b/vis/triangulated_surface.py index 3f48e81..87acdd3 100644 --- a/vis/triangulated_surface.py +++ b/vis/triangulated_surface.py @@ -15,10 +15,14 @@ def make_model_mesh(model, Rt=()): Rt = Rt_3d() xyz = Rt[:3, :3].dot(model.vertices.T).T + Rt[:3, -1] ijk = model.triangles + try: + color = model.color + except AttributeError: + color = None mesh = go.Mesh3d( x=xyz[:, 0], y=xyz[:, 1], z=xyz[:, 2], i=ijk[:, 0], j=ijk[:, 1], k=ijk[:, 2], - color='white', flatshading=True + color=color, flatshading=True ) return mesh diff --git a/vis/xyz_utils.py b/vis/xyz_utils.py index cdc9b97..dc538ee 100644 --- a/vis/xyz_utils.py +++ b/vis/xyz_utils.py @@ -29,3 +29,12 @@ def make_3d_vector(root, vector, scale=1., name=''): name=name, ) return line_obj, arrow_head_obj + + +def set_space_background(fig): + """ + :param fig: plotly figure + :return: + """ + fig.update_layout(paper_bgcolor='rgb(10,10,10)') + fig.update_scenes(xaxis_visible=False, yaxis_visible=False, zaxis_visible=False) -- GitLab