Merge pull request #1 from 3b1b/master

This commit is contained in:
鹤翔万里 2021-02-03 07:48:13 +08:00 committed by GitHub
commit 37f1130de6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 376 additions and 273 deletions

View file

@ -1,14 +0,0 @@
### If this is a support request:
**Please attempt to solve the problem on your own before opening an issue.**
Between old issues, StackOverflow, and Google, you should be able to find
solutions to most of the common problems.
Include at least:
1. Steps to reproduce the issue (e.g. the command you ran)
2. The unexpected behavior that occurred (e.g. error messages or screenshots)
3. The environment (e.g. operating system and version of manim)
### If this is a feature request:
Include the motivation for making this change.

20
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,20 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
### Describe the bug
<!-- A clear and concise description of what the bug is. -->
**Code**:
<!-- The code you run which reflect the bug. -->
**Wrong display or Error traceback**:
<!-- the wrong display result of the code you run, or the error Traceback -->
### Additional context
<!-- Add any other context about the problem here. -->

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

@ -0,0 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: Ask A Question
url: https://github.com/3b1b/manim/discussions/categories/q-a
about: Please ask questions you encountered here.

View file

@ -0,0 +1,23 @@
---
name: Error when using
about: The error you encountered while using manim
title: ''
labels: ''
assignees: ''
---
### Describe the error
<!-- A clear and concise description of what you want to make. -->
### Code and Error
**Code**:
<!-- The code you run -->
**Error**:
<!-- The error traceback you get when run your code -->
### Environment
**OS System**:
**manim version**: master <!-- make sure you are using the latest version of master branch -->
**python version**:

View file

@ -1,9 +1,18 @@
Thanks for contributing to manim!
<!-- Thanks for contributing to manim!
Please ensure that your pull request works with the latest version of manim.
-->
**Please ensure that your pull request works with the latest version of manim.**
You should also include:
## Motivation
<!-- Outline your motivation: In what way do your changes improve the library? -->
1. The motivation for making this change (or link the relevant issues)
2. How you tested the new behavior (e.g. a minimal working example, before/after
screenshots, gifs, commands, etc.) This is rather informal at the moment, but
the goal is to show us how you know the pull request works as intended.
## Proposed changes
<!-- What you changed in those files -->
-
-
-
## Test
<!-- How do you test your changes -->
**Code**:
**Result**:

View file

@ -497,7 +497,8 @@ class ControlsExample(Scene):
self.add(MotionMobject(text))
self.textbox.set_value("Manim")
self.embed()
# self.wait(60)
# self.embed()
# See https://github.com/3b1b/videos for many, many more

View file

@ -0,0 +1,6 @@
from manimlib.event_handler.event_dispatcher import EventDispatcher
# This is supposed to be a Singleton
# i.e., during runtime there should be only one object of Event Dispatcher
EVENT_DISPATCHER = EventDispatcher()

View file

@ -0,0 +1,92 @@
import numpy as np
from manimlib.event_handler.event_type import EventType
from manimlib.event_handler.event_listner import EventListner
class EventDispatcher(object):
def __init__(self):
self.event_listners = {
event_type: []
for event_type in EventType
}
self.mouse_point = np.array((0., 0., 0.))
self.mouse_drag_point = np.array((0., 0., 0.))
self.pressed_keys = set()
self.draggable_object_listners = []
def add_listner(self, event_listner):
assert(isinstance(event_listner, EventListner))
self.event_listners[event_listner.event_type].append(event_listner)
return self
def remove_listner(self, event_listner):
assert(isinstance(event_listner, EventListner))
try:
while event_listner in self.event_listners[event_listner.event_type]:
self.event_listners[event_listner.event_type].remove(event_listner)
except:
# raise ValueError("Handler is not handling this event, so cannot remove it.")
pass
return self
def dispatch(self, event_type, **event_data):
if event_type == EventType.MouseMotionEvent:
self.mouse_point = event_data["point"]
elif event_type == EventType.MouseDragEvent:
self.mouse_drag_point = event_data["point"]
elif event_type == EventType.KeyPressEvent:
self.pressed_keys.add(event_data["symbol"]) # Modifiers?
elif event_type == EventType.KeyReleaseEvent:
self.pressed_keys.difference_update({event_data["symbol"]}) # Modifiers?
elif event_type == EventType.MousePressEvent:
self.draggable_object_listners = [
listner
for listner in self.event_listners[EventType.MouseDragEvent]
if listner.mobject.is_point_touching(self.mouse_point)
]
elif event_type == EventType.MouseReleaseEvent:
self.draggable_object_listners = []
propagate_event = None
if event_type == EventType.MouseDragEvent:
for listner in self.draggable_object_listners:
assert(isinstance(listner, EventListner))
propagate_event = listner.callback(listner.mobject, event_data)
if propagate_event is not None and propagate_event is False:
return propagate_event
elif event_type.value.startswith('mouse'):
for listner in self.event_listners[event_type]:
if listner.mobject.is_point_touching(self.mouse_point):
propagate_event = listner.callback(
listner.mobject, event_data)
if propagate_event is not None and propagate_event is False:
return propagate_event
elif event_type.value.startswith('key'):
for listner in self.event_listners[event_type]:
propagate_event = listner.callback(listner.mobject, event_data)
if propagate_event is not None and propagate_event is False:
return propagate_event
return propagate_event
def get_listners_count(self):
return sum([len(value) for key, value in self.event_listners.items()])
def get_mouse_point(self):
return self.mouse_point
def get_mouse_drag_point(self):
return self.mouse_drag_point
def is_key_pressed(self, symbol):
return (symbol in self.pressed_keys)
__iadd__ = add_listner
__isub__ = remove_listner
__call__ = dispatch
__len__ = get_listners_count

View file

@ -0,0 +1,15 @@
class EventListner(object):
def __init__(self, mobject, event_type, event_callback):
self.mobject = mobject
self.event_type = event_type
self.callback = event_callback
def __eq__(self, o: object) -> bool:
return_val = False
try:
return_val = self.callback == o.callback \
and self.mobject == o.mobject \
and self.event_type == o.event_type
except:
pass
return return_val

View file

@ -0,0 +1,11 @@
from enum import Enum
class EventType(Enum):
MouseMotionEvent = 'mouse_motion_event'
MousePressEvent = 'mouse_press_event'
MouseReleaseEvent = 'mouse_release_event'
MouseDragEvent = 'mouse_drag_event'
MouseScrollEvent = 'mouse_scroll_event'
KeyPressEvent = 'key_press_event'
KeyReleaseEvent = 'key_release_event'

View file

@ -22,50 +22,43 @@ class MotionMobject(Mobject):
You could hold and drag this object to any position
"""
CONFIG = {
"listen_to_events": True
}
def __init__(self, mobject, **kwargs):
super().__init__(**kwargs)
assert(isinstance(mobject, Mobject))
self.mobject = mobject
self.mobject.add_mouse_drag_listner(self.mob_on_mouse_drag)
# To avoid locking it as static mobject
self.mobject.add_updater(lambda mob: None)
self.add(mobject)
def on_mouse_drag(self, point, d_point, buttons, modifiers):
if self.mobject.is_point_touching(point):
self.mobject.move_to(point)
return False
def mob_on_mouse_drag(self, mob, event_data):
mob.move_to(event_data["point"])
return False
class Button(Mobject):
"""
Pass any mobject and register an on_click method
"""
CONFIG = {
"listen_to_events": True
}
The on_click method takes mobject as argument like updater
"""
def __init__(self, mobject, on_click, **kwargs):
super().__init__(**kwargs)
self.mobject, self.on_click = mobject, on_click
assert(isinstance(mobject, Mobject))
self.on_click = on_click
self.mobject = mobject
self.mobject.add_mouse_press_listner(self.mob_on_mouse_press)
self.add(self.mobject)
def on_mouse_press(self, point, button, mods):
if self.mobject.is_point_touching(point):
self.on_click()
return False
def mob_on_mouse_press(self, mob, event_data):
self.on_click(mob)
return False
# Controls
class ContolMobject(ValueTracker):
CONFIG = {
"listen_to_events": True
}
class ControlMobject(ValueTracker):
def __init__(self, value, *mobjects, **kwargs):
super().__init__(value=value, **kwargs)
self.add(*mobjects)
@ -88,7 +81,7 @@ class ContolMobject(ValueTracker):
pass
class EnableDisableButton(ContolMobject):
class EnableDisableButton(ControlMobject):
CONFIG = {
"value_type": np.dtype(bool),
"rect_kwargs": {
@ -104,6 +97,7 @@ class EnableDisableButton(ContolMobject):
digest_config(self, kwargs)
self.box = Rectangle(**self.rect_kwargs)
super().__init__(value, self.box, **kwargs)
self.add_mouse_press_listner(self.on_mouse_press)
def assert_value(self, value):
assert(isinstance(value, bool))
@ -117,12 +111,12 @@ class EnableDisableButton(ContolMobject):
def toggle_value(self):
super().set_value(not self.get_value())
def on_mouse_press(self, point, button, mods):
self.toggle_value()
def on_mouse_press(self, mob, event_data):
mob.toggle_value()
return False
class Checkbox(ContolMobject):
class Checkbox(ControlMobject):
CONFIG = {
"value_type": np.dtype(bool),
"rect_kwargs": {
@ -147,6 +141,7 @@ class Checkbox(ContolMobject):
self.box = Rectangle(**self.rect_kwargs)
self.box_content = self.get_checkmark() if value else self.get_cross()
super().__init__(value, self.box, self.box_content, **kwargs)
self.add_mouse_press_listner(self.on_mouse_press)
def assert_value(self, value):
assert(isinstance(value, bool))
@ -160,8 +155,8 @@ class Checkbox(ContolMobject):
else:
self.box_content.become(self.get_cross())
def on_mouse_press(self, point, button, mods):
self.toggle_value()
def on_mouse_press(self, mob, event_data):
mob.toggle_value()
return False
# Helper methods
@ -191,7 +186,7 @@ class Checkbox(ContolMobject):
return cross
class LinearNumberSlider(ContolMobject):
class LinearNumberSlider(ControlMobject):
CONFIG = {
"value_type": np.float64,
"min_value": -10.0,
@ -222,6 +217,8 @@ class LinearNumberSlider(ContolMobject):
self.slider_axis.set_opacity(0.0)
self.slider.move_to(self.slider_axis)
self.slider.add_mouse_drag_listner(self.slider_on_mouse_drag)
super().__init__(value, self.bar, self.slider, self.slider_axis, ** kwargs)
def assert_value(self, value):
@ -231,10 +228,9 @@ class LinearNumberSlider(ContolMobject):
prop = (value - self.min_value) / (self.max_value - self.min_value)
self.slider.move_to(self.slider_axis.point_from_proportion(prop))
def on_mouse_drag(self, point, d_point, buttons, modifiers):
if self.slider.is_point_touching(point):
self.set_value(self.get_value_from_point(point))
return False
def slider_on_mouse_drag(self, mob, event_data):
self.set_value(self.get_value_from_point(event_data["point"]))
return False
# Helper Methods
@ -348,7 +344,7 @@ class ColorSliders(Group):
return rgba[3]
class Textbox(ContolMobject):
class Textbox(ControlMobject):
CONFIG = {
"value_type": np.dtype(object),
@ -371,10 +367,12 @@ class Textbox(ContolMobject):
digest_config(self, kwargs)
self.isActive = self.isInitiallyActive
self.box = Rectangle(**self.box_kwargs)
self.box.add_mouse_press_listner(self.box_on_mouse_press)
self.text = Text(value, **self.text_kwargs)
super().__init__(value, self.box, self.text, **kwargs)
self.update_text(value)
self.active_anim(self.isActive)
self.add_key_press_listner(self.on_key_press)
def set_value_anim(self, value):
self.update_text(value)
@ -397,16 +395,17 @@ class Textbox(ContolMobject):
else:
self.box.set_stroke(self.deactive_color)
def on_mouse_press(self, point, button, mods):
if self.box.is_point_touching(point):
self.isActive = not self.isActive
self.active_anim(self.isActive)
return False
def box_on_mouse_press(self, mob, event_data):
self.isActive = not self.isActive
self.active_anim(self.isActive)
return False
def on_key_press(self, symbol, modifiers):
def on_key_press(self, mob, event_data):
symbol = event_data["symbol"]
modifiers = event_data["modifiers"]
char = chr(symbol)
if self.isActive:
old_value = self.get_value()
if mob.isActive:
old_value = mob.get_value()
new_value = old_value
if char.isalnum():
if (modifiers & PygletWindowKeys.MOD_SHIFT) or (modifiers & PygletWindowKeys.MOD_CAPSLOCK):
@ -419,13 +418,12 @@ class Textbox(ContolMobject):
new_value = old_value + '\t'
elif symbol == PygletWindowKeys.BACKSPACE:
new_value = old_value[:-1] or ''
self.set_value(new_value)
mob.set_value(new_value)
return False
class ControlPanel(Group):
CONFIG = {
"listen_to_events": True,
"panel_kwargs": {
"width": FRAME_WIDTH / 4,
"height": MED_SMALL_BUFF + FRAME_HEIGHT,
@ -451,6 +449,7 @@ class ControlPanel(Group):
self.panel = Rectangle(**self.panel_kwargs)
self.panel.to_corner(UP + LEFT, buff=0)
self.panel.shift(self.panel.get_height() * UP)
self.panel.add_mouse_scroll_listner(self.panel_on_mouse_scroll)
self.panel_opener_rect = Rectangle(**self.opener_kwargs)
self.panel_info_text = Text(**self.opener_text_kwargs)
@ -458,6 +457,7 @@ class ControlPanel(Group):
self.panel_opener = Group(self.panel_opener_rect, self.panel_info_text)
self.panel_opener.next_to(self.panel, DOWN, aligned_edge=DOWN)
self.panel_opener.add_mouse_drag_listner(self.panel_opener_on_mouse_drag)
self.controls = Group(*controls)
self.controls.arrange(DOWN, center=False, aligned_edge=ORIGIN)
@ -510,14 +510,14 @@ class ControlPanel(Group):
self.move_panel_and_controls_to_panel_opener()
return self
def on_mouse_drag(self, point, d_point, buttons, modifiers):
if self.panel_opener.is_point_touching(point):
self.panel_opener.match_y(Dot(point))
self.move_panel_and_controls_to_panel_opener()
return False
def panel_opener_on_mouse_drag(self, mob, event_data):
point = event_data["point"]
self.panel_opener.match_y(Dot(point))
self.move_panel_and_controls_to_panel_opener()
return False
def on_mouse_scroll(self, point, offset):
if self.panel.is_point_touching(point):
factor = 10 * offset[1]
self.controls.set_y(self.controls.get_y() + factor)
return False
def panel_on_mouse_scroll(self, mob, event_data):
offset = event_data["offset"]
factor = 10 * offset[1]
self.controls.set_y(self.controls.get_y() + factor)
return False

View file

@ -27,6 +27,9 @@ from manimlib.utils.space_ops import get_norm
from manimlib.utils.space_ops import rotation_matrix_transpose
from manimlib.shader_wrapper import ShaderWrapper
from manimlib.shader_wrapper import get_colormap_code
from manimlib.event_handler import EVENT_DISPATCHER
from manimlib.event_handler.event_listner import EventListner
from manimlib.event_handler.event_type import EventType
class Mobject(object):
@ -68,6 +71,7 @@ class Mobject(object):
self.init_data()
self.init_uniforms()
self.init_updaters()
self.init_event_listners()
self.init_points()
self.init_colors()
self.init_shader_data()
@ -1440,35 +1444,81 @@ class Mobject(object):
Event handling follows the Event Bubbling model of DOM in javascript.
Return false to stop the event bubbling.
To learn more visit https://www.quirksmode.org/js/events_order.html
Event Callback Argument is a callable function taking two arguments:
1. Mobject
2. EventData
"""
def on_mouse_motion(self, point, d_point):
# To be implemented in subclasses
pass
def init_event_listners(self):
self.event_listners = []
def on_mouse_drag(self, point, d_point, buttons, modifiers):
# To be implemented in subclasses
pass
def add_event_listner(self, event_type, event_callback):
event_listner = EventListner(self, event_type, event_callback)
self.event_listners.append(event_listner)
EVENT_DISPATCHER.add_listner(event_listner)
return self
def on_mouse_press(self, point, button, mods):
# To be implemented in subclasses
pass
def remove_event_listner(self, event_type, event_callback):
event_listner = EventListner(self, event_type, event_callback)
while event_listner in self.event_listners:
self.event_listners.remove(event_listner)
EVENT_DISPATCHER.remove_listner(event_listner)
return self
def on_mouse_release(self, point, button, mods):
# To be implemented in subclasses
pass
def clear_event_listners(self, recurse=True):
self.event_listners = []
if recurse:
for submob in self.submobjects:
submob.clear_event_listners(recurse=recurse)
return self
def on_mouse_scroll(self, point, offset):
# To be implemented in subclasses
pass
def get_event_listners(self):
return self.event_listners
def get_family_event_listners(self):
return list(it.chain(*[sm.get_event_listners() for sm in self.get_family()]))
def on_key_release(self, symbol, modifiers):
# To be implemented in subclasses
pass
def get_has_event_listner(self):
return any(
mob.get_event_listners()
for mob in self.get_family()
)
def on_key_press(self, symbol, modifiers):
# To be implemented in subclasses
pass
def add_mouse_motion_listner(self, callback):
self.add_event_listner(EventType.MouseMotionEvent, callback)
def remove_mouse_motion_listner(self, callback):
self.remove_event_listner(EventType.MouseMotionEvent, callback)
def add_mouse_press_listner(self, callback):
self.add_event_listner(EventType.MousePressEvent, callback)
def remove_mouse_press_listner(self, callback):
self.remove_event_listner(EventType.MousePressEvent, callback)
def add_mouse_release_listner(self, callback):
self.add_event_listner(EventType.MouseReleaseEvent, callback)
def remove_mouse_release_listner(self, callback):
self.remove_event_listner(EventType.MouseReleaseEvent, callback)
def add_mouse_drag_listner(self, callback):
self.add_event_listner(EventType.MouseDragEvent, callback)
def remove_mouse_drag_listner(self, callback):
self.remove_event_listner(EventType.MouseDragEvent, callback)
def add_mouse_scroll_listner(self, callback):
self.add_event_listner(EventType.MouseScrollEvent, callback)
def remove_mouse_scroll_listner(self, callback):
self.remove_event_listner(EventType.MouseScrollEvent, callback)
def add_key_press_listner(self, callback):
self.add_event_listner(EventType.KeyPressEvent, callback)
def remove_key_press_listner(self, callback):
self.remove_event_listner(EventType.KeyPressEvent, callback)
def add_key_release_listner(self, callback):
self.add_event_listner(EventType.KeyReleaseEvent, callback)
def remove_key_release_listner(self, callback):
self.remove_event_listner(EventType.KeyReleaseEvent, callback)
# Errors

View file

@ -23,35 +23,6 @@ from manimlib.utils.directories import get_mobject_data_dir
from manimlib.utils.images import get_full_vector_image_path
def check_and_fix_percent_bug(sym):
# This is an ugly patch addressing something which should be
# addressed at a deeper level.
# The svg path for percent symbols have a known bug, so this
# checks if the symbol is (probably) a percentage sign, and
# splits it so that it's displayed properly.
if len(sym.get_points()) not in [315, 324, 372, 468, 483] or len(sym.get_subpaths()) != 4:
return
sym = sym.family_members_with_points()[0]
new_sym = VMobject()
path_lengths = [len(path) for path in sym.get_subpaths()]
sym_points = sym.get_points()
if len(sym_points) in [315, 324, 372]:
n = sum(path_lengths[:2])
p1 = sym_points[:n]
p2 = sym_points[n:]
elif len(sym_points) in [468, 483]:
p1 = np.vstack([
sym_points[:path_lengths[0]],
sym_points[-path_lengths[3]:]
])
p2 = sym_points[path_lengths[0]:sum(path_lengths[:3])]
sym.set_points(p1)
new_sym.set_points(p2)
sym.add(new_sym)
sym.refresh_triangulation()
def string_to_numbers(num_string):
num_string = num_string.replace("-", ",-")
num_string = num_string.replace("e,-", "e-")
@ -379,7 +350,6 @@ class VMobjectFromSVGPathstring(VMobject):
self.stretch(-1, 1, about_point=ORIGIN)
# Save to a file for future use
np.save(points_filepath, self.get_points())
check_and_fix_percent_bug(self)
def get_commands_and_coord_strings(self):
all_commands = list(self.get_command_to_function_map().keys())

View file

@ -19,6 +19,8 @@ from manimlib.scene.scene_file_writer import SceneFileWriter
from manimlib.utils.config_ops import digest_config
from manimlib.utils.family_ops import extract_mobject_family_members
from manimlib.utils.family_ops import restructure_list_to_exclude_certain_family_members
from manimlib.event_handler.event_type import EventType
from manimlib.event_handler import EVENT_DISPATCHER
from manimlib.window import Window
@ -513,25 +515,14 @@ class Scene(object):
self.mobjects = mobjects
# Event handling
def get_event_listeners_mobjects(self):
"""
This method returns all the mobjects that listen to events
in reversed order. So the top most mobject's event is called first.
This helps in event bubbling.
"""
return filter(
lambda mob: mob.listen_to_events,
reversed(self.get_mobject_family_members())
)
def on_mouse_motion(self, point, d_point):
self.mouse_point.move_to(point)
for mob_listener in self.get_event_listeners_mobjects():
if mob_listener.is_point_touching(point):
propagate_event = mob_listener.on_mouse_motion(point, d_point)
if propagate_event is not None and propagate_event is False:
return
event_data = {"point": point, "d_point": d_point}
propagate_event = EVENT_DISPATCHER.dispatch(EventType.MouseMotionEvent, **event_data)
if propagate_event is not None and propagate_event is False:
return
frame = self.camera.frame
if self.window.is_key_pressed(ord("d")):
@ -548,32 +539,28 @@ class Scene(object):
def on_mouse_drag(self, point, d_point, buttons, modifiers):
self.mouse_drag_point.move_to(point)
for mob_listener in self.get_event_listeners_mobjects():
if mob_listener.is_point_touching(point):
propagate_event = mob_listener.on_mouse_drag(point, d_point, buttons, modifiers)
if propagate_event is not None and propagate_event is False:
return
event_data = {"point": point, "d_point": d_point, "buttons": buttons, "modifiers": modifiers}
propagate_event = EVENT_DISPATCHER.dispatch(EventType.MouseDragEvent, **event_data)
if propagate_event is not None and propagate_event is False:
return
def on_mouse_press(self, point, button, mods):
for mob_listener in self.get_event_listeners_mobjects():
if mob_listener.is_point_touching(point):
propagate_event = mob_listener.on_mouse_press(point, button, mods)
if propagate_event is not None and propagate_event is False:
return
event_data = {"point": point, "button": button, "mods": mods}
propagate_event = EVENT_DISPATCHER.dispatch(EventType.MousePressEvent, **event_data)
if propagate_event is not None and propagate_event is False:
return
def on_mouse_release(self, point, button, mods):
for mob_listener in self.get_event_listeners_mobjects():
if mob_listener.is_point_touching(point):
propagate_event = mob_listener.on_mouse_release(point, button, mods)
if propagate_event is not None and propagate_event is False:
return
event_data = {"point": point, "button": button, "mods": mods}
propagate_event = EVENT_DISPATCHER.dispatch(EventType.MouseReleaseEvent, **event_data)
if propagate_event is not None and propagate_event is False:
return
def on_mouse_scroll(self, point, offset):
for mob_listener in self.get_event_listeners_mobjects():
if mob_listener.is_point_touching(point):
propagate_event = mob_listener.on_mouse_scroll(point, offset)
if propagate_event is not None and propagate_event is False:
return
event_data = {"point": point, "offset": offset}
propagate_event = EVENT_DISPATCHER.dispatch(EventType.MouseScrollEvent, **event_data)
if propagate_event is not None and propagate_event is False:
return
frame = self.camera.frame
if self.window.is_key_pressed(ord("z")):
@ -585,10 +572,10 @@ class Scene(object):
frame.shift(-20.0 * shift)
def on_key_release(self, symbol, modifiers):
for mob_listener in self.get_event_listeners_mobjects():
propagate_event = mob_listener.on_key_release(symbol, modifiers)
if propagate_event is not None and propagate_event is False:
return
event_data = {"symbol": symbol, "modifiers": modifiers}
propagate_event = EVENT_DISPATCHER.dispatch(EventType.KeyReleaseEvent, **event_data)
if propagate_event is not None and propagate_event is False:
return
def on_key_press(self, symbol, modifiers):
try:
@ -597,10 +584,10 @@ class Scene(object):
print(" Warning: The value of the pressed key is too large.")
return
for mob_listener in self.get_event_listeners_mobjects():
propagate_event = mob_listener.on_key_press(symbol, modifiers)
if propagate_event is not None and propagate_event is False:
return
event_data = {"symbol": symbol, "modifiers": modifiers}
propagate_event = EVENT_DISPATCHER.dispatch(EventType.KeyPressEvent, **event_data)
if propagate_event is not None and propagate_event is False:
return
if char == "r":
self.camera.frame.to_default_state()

View file

@ -51,7 +51,7 @@ float sdf(){
float sgn = orientation * sign(v2);
float Fp = (p.x * p.x - p.y);
if(sgn * Fp < 0){
return 0.0;
return 0;
}else{
return min_dist_to_curve(uv_coords, uv_b2, bezier_degree);
}

View file

@ -66,7 +66,7 @@ void flatten_points(in vec3[3] points, out vec2[3] flat_points){
float angle_between_vectors(vec2 v1, vec2 v2){
float v1_norm = length(v1);
float v2_norm = length(v2);
if(v1_norm == 0 || v2_norm == 0) return 0.0;
if(v1_norm == 0 || v2_norm == 0) return 0;
float dp = dot(v1, v2) / (v1_norm * v2_norm);
float angle = acos(clamp(dp, -1.0, 1.0));
float sn = sign(cross2d(v1, v2));
@ -269,4 +269,4 @@ void main() {
EmitVertex();
}
EndPrimitive();
}
}

View file

@ -349,29 +349,41 @@ def norm_squared(v):
# TODO, fails for polygons drawn over themselves
def earclip_triangulation(verts, rings):
"""
Returns a list of indices giving a triangulation
of a polygon, potentially with holes
- verts is an NxM numpy array of points with M > 2
- rings is a list of indices indicating where
the ends of new paths are
"""
n = len(verts)
# Establish where loop indices should be connected
loop_connections = dict()
for e0, e1 in zip(rings, rings[1:]):
temp_i = e0
# Find closet point in the first ring (j) to
# the first index of this ring (i)
norms = np.array([
[j, norm_squared(verts[temp_i] - verts[j])]
for j in range(0, rings[0])
if j not in loop_connections
])
j = int(norms[norms[:, 1].argmin()][0])
# Find i closest to this j
norms = np.array([
[i, norm_squared(verts[i] - verts[j])]
for i in range(e0, e1)
# for e0, e1 in zip(rings, rings[1:]):
e0 = rings[0]
for e1 in rings[1:]:
# Find closet pair of points with the first
# coming from the current ring, and the second
# coming from the next ring
index_pairs = [
(i, j)
for i in range(0, e0)
for j in range(e0, e1)
if i not in loop_connections
])
i = int(norms[norms[:, 1].argmin()][0])
if j not in loop_connections
]
i, j = index_pairs[np.argmin([
norm_squared(verts[i] - verts[j])
for i, j in index_pairs
])]
# Connect the polygon at these points so that
# it's treated as a single highly-convex ring
loop_connections[i] = j
loop_connections[j] = i
e0 = e1
# Setup linked list
after = []
@ -397,87 +409,3 @@ def earclip_triangulation(verts, rings):
meta_indices = earcut(verts[indices, :2], [len(indices)])
return [indices[mi] for mi in meta_indices]
def old_earclip_triangulation(verts, rings, orientation):
n = len(verts)
assert(n in rings)
result = []
# Establish where loop indices should be connected
loop_connections = dict()
e0 = 0
for e1 in rings:
norms = np.array([
[i, j, get_norm(verts[i] - verts[j])]
for i in range(e0, e1)
for j in it.chain(range(0, e0), range(e1, n))
])
if len(norms) == 0:
continue
i, j = norms[np.argmin(norms[:, 2])][:2].astype(int)
loop_connections[i] = j
loop_connections[j] = i
e0 = e1
# Setup bidirectional linked list
before = []
after = []
e0 = 0
for e1 in rings:
after += [*range(e0 + 1, e1), e0]
before += [e1 - 1, *range(e0, e1 - 1)]
e0 = e1
# Initialize edge triangles
edge_tris = []
i = 0
starting = True
while (i != 0 or starting):
starting = False
if i in loop_connections:
j = loop_connections[i]
edge_tris.append([before[i], i, j])
edge_tris.append([i, j, after[j]])
i = after[j]
else:
edge_tris.append([before[i], i, after[i]])
i = after[i]
# Set up a test for whether or not three indices
# form an ear of the polygon, meaning a convex corner
# which doesn't contain any other vertices
indices = list(range(n))
def is_ear(*tri_indices):
tri = [verts[i] for i in tri_indices]
v1 = tri[1] - tri[0]
v2 = tri[2] - tri[1]
cross = v1[0] * v2[1] - v2[0] * v1[1]
if orientation * cross < 0:
return False
for j in indices:
if j in tri_indices:
continue
elif is_inside_triangle(verts[j], *tri):
return False
return True
# Loop through and clip off all the ears
n_failures = 0
i = 0
while n_failures < len(edge_tris):
n = len(edge_tris)
edge_tri = edge_tris[i % n]
if is_ear(*edge_tri):
result.extend(edge_tri)
edge_tris[(i - 1) % n][2] = edge_tri[2]
edge_tris[(i + 1) % n][0] = edge_tri[0]
if edge_tri[1] in indices:
indices.remove(edge_tri[1])
edge_tris.remove(edge_tri)
n_failures = 0
else:
n_failures += 1
i += 1
return result