Skip to content
This repository was archived by the owner on Feb 3, 2023. It is now read-only.

Commit 7c41292

Browse files
authored
Surface normals, and pull in shapes from lace (#7)
1 parent c96461d commit 7c41292

File tree

3 files changed

+153
-0
lines changed

3 files changed

+153
-0
lines changed

blmath/geometry/shapes.py

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import numpy as np
2+
3+
4+
def _maybe_flatten(vertices, faces, ret_unique_vertices_and_faces):
5+
if ret_unique_vertices_and_faces:
6+
return vertices, faces
7+
else:
8+
return vertices[faces]
9+
10+
11+
def create_rectangular_prism(origin, size, ret_unique_vertices_and_faces=False):
12+
'''
13+
Return vertices (or unique verties and faces) of an axis-aligned
14+
rectangular prism. One vertex is `origin`; the diametrically opposite
15+
vertex is `origin + size`.
16+
17+
size: 3x1 array.
18+
19+
'''
20+
from lace.topology import quads_to_tris
21+
22+
lower_base_plane = np.array([
23+
# Lower base plane
24+
origin,
25+
origin + np.array([size[0], 0, 0]),
26+
origin + np.array([size[0], 0, size[2]]),
27+
origin + np.array([0, 0, size[2]]),
28+
])
29+
upper_base_plane = lower_base_plane + np.array([0, size[1], 0])
30+
31+
vertices = np.vstack([lower_base_plane, upper_base_plane])
32+
33+
faces = quads_to_tris(np.array([
34+
[0, 1, 2, 3], # lower base (-y)
35+
[7, 6, 5, 4], # upper base (+y)
36+
[4, 5, 1, 0], # +z face
37+
[5, 6, 2, 1], # +x face
38+
[6, 7, 3, 2], # -z face
39+
[3, 7, 4, 0], # -x face
40+
]))
41+
42+
return _maybe_flatten(vertices, faces, ret_unique_vertices_and_faces)
43+
44+
45+
def create_cube(origin, size, ret_unique_vertices_and_faces=False):
46+
'''
47+
Return vertices (or unique verties and faces) with an axis-aligned cube.
48+
One vertex is `origin`; the diametrically opposite vertex is `size` units
49+
along +x, +y, and +z.
50+
51+
size: int or float.
52+
53+
'''
54+
return create_rectangular_prism(
55+
origin,
56+
np.repeat(size, 3),
57+
ret_unique_vertices_and_faces=ret_unique_vertices_and_faces)
58+
59+
60+
def create_triangular_prism(p1, p2, p3, height, ret_unique_vertices_and_faces=False):
61+
"""
62+
Return vertices (or unique verties and faces) of a triangular prism whose
63+
base is the triangle p1, p2, p3. If the vertices are oriented in a
64+
counterclockwise direction, the prism extends from behind them.
65+
66+
Imported from lace.
67+
"""
68+
from . import Plane
69+
70+
base_plane = Plane.from_points(p1, p2, p3)
71+
lower_base_to_upper_base = height * -base_plane.normal # pylint: disable=invalid-unary-operand-type
72+
vertices = np.vstack(([p1, p2, p3], [p1, p2, p3] + lower_base_to_upper_base))
73+
74+
faces = np.array([
75+
[0, 1, 2], # base
76+
[0, 3, 4], [0, 4, 1], # side 0, 3, 4, 1
77+
[1, 4, 5], [1, 5, 2], # side 1, 4, 5, 2
78+
[2, 5, 3], [2, 3, 0], # side 2, 5, 3, 0
79+
[5, 4, 3], # base
80+
])
81+
82+
return _maybe_flatten(vertices, faces, ret_unique_vertices_and_faces)
83+
84+
85+
def create_horizontal_plane(ret_unique_vertices_and_faces=False):
86+
'''
87+
Creates a horizontal plane.
88+
'''
89+
vertices = np.array([
90+
[1., 0., 0.],
91+
[-1., 0., 0.],
92+
[0., 0., 1.],
93+
[0., 0., -1.]
94+
])
95+
faces = [[0, 1, 2], [3, 1, 0]]
96+
return _maybe_flatten(vertices, faces, ret_unique_vertices_and_faces)

blmath/geometry/surface_normals.py

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
def surface_normal(points, normalize=True):
2+
"""
3+
Compute the surface normal of a triangle.
4+
5+
Can provide three points or an array of points.
6+
"""
7+
import numpy as np
8+
from blmath.numerics import vx
9+
p1 = points[..., 0, :]
10+
p2 = points[..., 1, :]
11+
p3 = points[..., 2, :]
12+
normal = np.cross(p2 - p1, p3 - p1)
13+
return vx.normalize(normal) if normalize else normal
+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import unittest
2+
import numpy as np
3+
from ..numerics import vx
4+
from .surface_normals import surface_normal
5+
6+
7+
class TestSurfaceNormals(unittest.TestCase):
8+
def test_surface_normal_single(self):
9+
points = np.array([
10+
[3.0, 0.0, 0.0],
11+
[0.0, 3.0, 0.0],
12+
[0.0, 0.0, 3.0],
13+
])
14+
15+
np.testing.assert_allclose(
16+
surface_normal(points),
17+
np.array([3 ** -0.5, 3 ** -0.5, 3 ** -0.5]))
18+
19+
np.testing.assert_allclose(
20+
surface_normal(points, normalize=False),
21+
np.array([9.0, 9.0, 9.0]))
22+
23+
def test_surface_normal_vectorized(self):
24+
from .shapes import create_triangular_prism
25+
26+
p1 = np.array([3.0, 0.0, 0.0])
27+
p2 = np.array([0.0, 3.0, 0.0])
28+
p3 = np.array([0.0, 0.0, 3.0])
29+
vertices = create_triangular_prism(p1, p2, p3, 1.0)
30+
31+
expected_normals = np.array([
32+
vx.normalize(np.array([1.0, 1.0, 1.0])),
33+
vx.normalize(np.array([1.0, 1.0, -2.0])),
34+
vx.normalize(np.array([1.0, 1.0, -2.0])),
35+
vx.normalize(np.array([-2.0, 1.0, 1.0])),
36+
vx.normalize(np.array([-2.0, 1.0, 1.0])),
37+
vx.normalize(np.array([1.0, -2.0, 1.0])),
38+
vx.normalize(np.array([1.0, -2.0, 1.0])),
39+
vx.normalize(np.array([-1.0, -1.0, -1.0])),
40+
])
41+
42+
np.testing.assert_allclose(
43+
surface_normal(vertices),
44+
expected_normals)

0 commit comments

Comments
 (0)