def coplanar_points_are_on_same_side_of_line(a, b, p1, p2, atol=1e-8): """ Using "same-side technique" from http://blackpawn.com/texts/pointinpoly/default.html """ along_line = b - a return vg.dot(vg.cross(along_line, p1 - a), vg.cross(along_line, p2 - a)) >= -atol
def test_cross_mixed(): v1 = np.array([[1.0, 0.0, -1.0], [1.0, 2.0, 3.0]]) v2 = np.array([4.0, 5.0, 6.0]) expected = np.array([[5.0, -10.0, 5.0], [-3.0, 6.0, -3.0]]) np.testing.assert_array_almost_equal(vg.cross(v1, v2), expected) np.testing.assert_array_almost_equal(vg.cross(v2, v1), -expected)
def test_cross_error(): v1 = np.array([[1.0, 0.0, -1.0], [1.0, 2.0, 3.0]]) v2 = np.array([[4.0, 5.0, 6.0]]) with pytest.raises( ValueError, match="v2 must be an array with shape \\(2, 3\\); got \\(1, 3\\)"): vg.cross(v1, v2) v1 = np.array([[1.0, 0.0, -1.0], [1.0, 2.0, 3.0]]) v2 = np.array([[[4.0, 5.0, 6.0]]]) with pytest.raises( ValueError, match="Not sure what to do with 2 dimensions and 3 dimensions"): vg.cross(v1, v2)
def coplanar_points_are_on_same_side_of_line(a, b, p1, p2): """ Test if the given points are on the same side of the given line. Args: a (np.arraylike): The first 3D point of interest. b (np.arraylike): The second 3D point of interest. p1 (np.arraylike): A first point which lies on the line of interest. p2 (np.arraylike): A second point which lies on the line of interest. Returns: bool: `True` when `a` and `b` are on the same side of the line defined by `p1` and `p2`. """ check_shape_any(a, (3,), (-1, 3), name="a") vg.shape.check(locals(), "b", a.shape) vg.shape.check(locals(), "p1", a.shape) vg.shape.check(locals(), "p2", a.shape) # Uses "same-side technique" from http://blackpawn.com/texts/pointinpoly/default.html along_line = b - a return vg.dot(vg.cross(along_line, p1 - a), vg.cross(along_line, p2 - a)) >= 0
def world_to_view(position, target, up=vg.basis.y, inverse=False): """ Create a transform matrix which sends world-space coordinates to view-space coordinates. Args: position (np.ndarray): The camera's position in world coordinates. target (np.ndarray): The camera's target in world coordinates. `target - position` is the "look at" vector. up (np.ndarray): The approximate up direction, in world coordinates. inverse (bool): When `True`, return the inverse transform instead. Returns: np.ndarray: The `4x4` transformation matrix, which can be used with `polliwog.transform.apply_transform()`. See also: https://cseweb.ucsd.edu/classes/wi18/cse167-a/lec4.pdf http://www.songho.ca/opengl/gl_camera.html """ vg.shape.check(locals(), "position", (3, )) vg.shape.check(locals(), "target", (3, )) look = vg.normalize(target - position) left = vg.normalize(vg.cross(look, up)) recomputed_up = vg.cross(left, look) rotation = transform_matrix_for_rotation( np.array([left, recomputed_up, look])) if inverse: inverse_rotation = rotation.T inverse_translation = transform_matrix_for_translation(position) return compose_transforms(inverse_rotation, inverse_translation) else: translation = transform_matrix_for_translation(-position) return compose_transforms(translation, rotation)
def plane_normal_from_points(points): """ Given a set of three points, compute the normal of the plane which passes through them. Also works on stacked inputs (i.e. many sets of three points). """ points, _, transform_result = columnize(points, (-1, 3, 3), name="points") p1s = points[:, 0] p2s = points[:, 1] p3s = points[:, 2] v1s = p2s - p1s v2s = p3s - p1s unit_normals = vg.normalize(vg.cross(v1s, v2s)) return transform_result(unit_normals)
def surface_normals(points, normalize=True): """ Compute the surface normal of a triangle. The direction of the normal follows conventional counter-clockwise winding and the right-hand rule. Also works on stacked inputs (i.e. many sets of three points). """ points, _, transform_result = columnize(points, (-1, 3, 3), name="points") p1s = points[:, 0] p2s = points[:, 1] p3s = points[:, 2] v1s = p2s - p1s v2s = p3s - p1s normals = vg.cross(v1s, v2s) if normalize: normals = vg.normalize(normals) return transform_result(normals)