Skip to content

Commit 9017dc9

Browse files
XorUnisonhuguesdevimeuxbehackl
authored
Direction methods (VMobject) (#647)
* Direction implemented * Update manim/utils/space_ops.py Co-authored-by: Hugues Devimeux <[email protected]> Co-authored-by: Benjamin Hackl <[email protected]>
1 parent 57d4f0e commit 9017dc9

File tree

2 files changed

+91
-0
lines changed

2 files changed

+91
-0
lines changed

manim/mobject/types/vectorized_mobject.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from ...utils.simple_functions import clip_in_place
3131
from ...utils.space_ops import rotate_vector
3232
from ...utils.space_ops import get_norm
33+
from ...utils.space_ops import shoelace_direction
3334

3435
# TODO
3536
# - Change cubic curve groups to have 4 points instead of 3
@@ -879,6 +880,68 @@ def get_subcurve(self, a, b):
879880
vmob.pointwise_become_partial(self, a, b)
880881
return vmob
881882

883+
def get_direction(self):
884+
"""Uses :func:`~.space_ops.shoelace_direction` to calculate the direction.
885+
The direction of points determines in which direction the
886+
object is drawn, clockwise or counterclockwise.
887+
888+
Examples
889+
--------
890+
The default direction of a :class:`~.Circle` is counterclockwise::
891+
892+
>>> from manim import Circle
893+
>>> Circle().get_direction()
894+
'CCW'
895+
896+
Returns
897+
-------
898+
:class:`str`
899+
Either `"CW"` or `"CCW"`.
900+
"""
901+
return shoelace_direction(self.get_start_anchors())
902+
903+
def reverse_direction(self):
904+
"""Reverts the point direction by inverting the point order.
905+
906+
Returns
907+
-------
908+
:class:`VMobject`
909+
Returns self.
910+
911+
Examples
912+
--------
913+
.. manim:: ChangeOfDirection
914+
915+
class ChangeOfDirection(Scene):
916+
def construct(self):
917+
ccw = RegularPolygon(5)
918+
ccw.shift(LEFT).rotate
919+
cw = RegularPolygon(5)
920+
cw.shift(RIGHT).reverse_direction()
921+
922+
self.play(ShowCreation(ccw), ShowCreation(cw),
923+
run_time=4)
924+
"""
925+
self.points = self.points[::-1]
926+
return self
927+
928+
def force_direction(self, target_direction):
929+
"""Makes sure that points are either directed clockwise or
930+
counterclockwise.
931+
932+
Parameters
933+
----------
934+
target_direction : :class:`str`
935+
Either ``"CW"`` or ``"CCW"``.
936+
"""
937+
if target_direction not in ("CW", "CCW"):
938+
raise ValueError('Invalid input for force_direction. Use "CW" or "CCW"')
939+
if self.get_direction() != target_direction:
940+
# Since we already assured the input is CW or CCW,
941+
# and the directions don't match, we just reverse
942+
self.reverse_direction()
943+
return self
944+
882945

883946
class VGroup(VMobject):
884947
def __init__(self, *vmobjects, **kwargs):

manim/utils/space_ops.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,3 +252,31 @@ def get_winding_number(points):
252252
d_angle = ((d_angle + PI) % TAU) - PI
253253
total_angle += d_angle
254254
return total_angle / TAU
255+
256+
257+
def shoelace(x_y):
258+
"""2D implementation of the shoelace formula.
259+
260+
Returns
261+
-------
262+
:class:`float`
263+
Returns signed area.
264+
"""
265+
x = x_y[:, 0]
266+
y = x_y[:, 1]
267+
area = 0.5 * np.array(np.dot(x, np.roll(y, 1)) - np.dot(y, np.roll(x, 1)))
268+
return area
269+
270+
271+
def shoelace_direction(x_y):
272+
"""
273+
Uses the area determined by the shoelace method to determine whether
274+
the input set of points is directed clockwise or counterclockwise.
275+
276+
Returns
277+
-------
278+
:class:`str`
279+
Either ``"CW"`` or ``"CCW"``.
280+
"""
281+
area = shoelace(x_y)
282+
return "CW" if area > 0 else "CCW"

0 commit comments

Comments
 (0)