131 Commits

Author SHA1 Message Date
TonyCrane
b61f1473a5 release: ready to release v1.3.0 2021-12-14 13:41:44 +08:00
TonyCrane
e3d5b49a55 docs: remove deprecated config usage example 2021-12-14 13:35:50 +08:00
TonyCrane
4d6a0db1e1 docs: update changelog for #1691 and #1678 2021-12-14 13:31:44 +08:00
TonyCrane
0af46e149d add metavar LINENO for --embed option 2021-12-14 12:14:04 +08:00
TonyCrane
896b011d76 docs: update changelog for #1688 2021-12-14 12:11:25 +08:00
Grant Sanderson
3adaf8e325 Merge pull request #1678 from YishiMichael/master
Construct `MTex`
2021-12-13 16:09:55 -08:00
Grant Sanderson
8762177df5 Merge pull request #1691 from 3b1b/video-work
Video work
2021-12-13 16:07:42 -08:00
Grant Sanderson
a1d51474ea Add GlowDot 2021-12-13 16:03:57 -08:00
Grant Sanderson
83841ae415 Add Dodecahedron 2021-12-13 16:03:46 -08:00
Grant Sanderson
b81f244c3c Inserted "self.embed" line should match passed in line number 2021-12-13 16:03:36 -08:00
Grant Sanderson
7023548ec6 Fix TransformMatchingParts bug 2021-12-13 16:03:12 -08:00
Grant Sanderson
758f329a06 Use array copy when checking need for refreshing triangulation 2021-12-13 16:02:47 -08:00
Grant Sanderson
8f1dfabff0 VectorizedPoint should call __init__ for both super classes 2021-12-13 16:02:10 -08:00
Grant Sanderson
7fa01d5de8 Small formatting change 2021-12-13 16:01:54 -08:00
Michael W
0de303d5e0 Some refactors
- Split out `_TexParser` class
- Replace `math_mode` parameter with `tex_environment`
- Fix the bug that braces following even number of backslashes aren't matched
2021-12-13 21:01:27 +08:00
Michael W
155839bde9 Add unbreakable_commands parameter 2021-12-13 12:46:29 +08:00
Grant Sanderson
3a1e5e1bcf Remove old implementation for SurfaceMesh 2021-12-07 10:07:49 -08:00
Grant Sanderson
264f7b1172 Add Circle.get_radius 2021-12-07 10:07:25 -08:00
Grant Sanderson
85e90a1488 Don't print info for pre-run scene 2021-12-07 10:07:15 -08:00
Grant Sanderson
f8e6e7df3c Update progress display for full scene render 2021-12-07 10:06:48 -08:00
Grant Sanderson
5dd7cce67f Have Scene.wait only go through full progression during skipping when there are time-based updaters 2021-12-07 10:05:33 -08:00
Grant Sanderson
f21a4a4696 Only stop skipping if the scene wasn't originally meant to be 2021-12-07 10:04:28 -08:00
Grant Sanderson
98b0d266d2 Make sure skip_animations and start_at_animation_number play well together 2021-12-07 10:03:10 -08:00
Michael W
6821a7c20e Handle empty strings 2021-12-07 14:12:08 +08:00
Michael W
00f72da493 Some small refactor 2021-12-07 13:17:48 +08:00
Michael W
744916507c Add a debugging method 2021-12-07 12:55:52 +08:00
Michael W
88d863c1d7 Support get_tex() for submobjects of MTex 2021-12-07 00:34:07 +08:00
Michael W
d7dcc9d76f Recover file 2021-12-07 00:32:12 +08:00
Michael W
4631508b7d Add get_tex() method 2021-12-06 13:48:17 +08:00
Michael W
8803088121 Fix bugs concerned with child environments 2021-12-06 09:44:59 +08:00
Michael W
1d466cb299 Add Exception for indices_of_part() 2021-12-05 22:17:09 +08:00
Michael W
5a1f00b1cb Add TransformMatchingMTex 2021-12-05 11:46:15 +08:00
Michael W
17d31045b2 Add TransformMatchingMTex 2021-12-05 11:45:42 +08:00
Michael W
950466c1da Some refactors 2021-12-05 10:21:55 +08:00
Michael W
62151e52f1 Merge branch '3b1b:master' into master 2021-12-01 08:42:17 +08:00
Grant Sanderson
b4ce0b910c Merge pull request #1688 from 3b1b/video-work
Video work
2021-11-30 11:45:57 -08:00
Grant Sanderson
9dd1f47dab Create single progress display for full scene render
When a scene is written to file, it will now do a preliminary run of a copy of the scene with skip_animations turned on to count the total frames, which has the added benefit of catching runtime errors early, and allowing an quicker preview of the last frame to be sure everything will render as expected.

The Progress display bars for individual animations are replaced with a more global progress display bar showing the full render time for the scene.

This has the downside that all the non-rendering computations in a scene are run twice, so any scene with slow computations unrelated to rendering will take longer. But those are rarer, so the benefits seem worth it.
2021-11-30 11:41:33 -08:00
Grant Sanderson
49743daf32 Add Mobject.insert_submobject method 2021-11-30 11:30:50 -08:00
Grant Sanderson
ba23fbe71e Make sure Mobject.is_fixed_in_frame stays updated with uniforms 2021-11-30 11:30:34 -08:00
Grant Sanderson
ee1594a3cb Match fix_in_frame status for FlashAround mobject 2021-11-30 11:29:12 -08:00
Grant Sanderson
e9afb0ee33 Fix tiny PEP errors 2021-11-30 11:28:26 -08:00
Michael W
8b1715379d Some small refactors 2021-11-29 09:48:00 +08:00
Michael W
2501fac32f Some small refactors 2021-11-29 09:38:48 +08:00
Michael W
1aec0462ec Some small refactors 2021-11-29 01:43:48 +08:00
Michael W
83c70a59d8 Sort superscripts and subscripts in submobjects 2021-11-29 01:15:38 +08:00
Michael W
9b8a6e7ff8 Merge branch '3b1b:master' into master 2021-11-28 23:38:23 +08:00
Michael W
758f2ec236 Some small refactor 2021-11-28 23:38:12 +08:00
TonyCrane
d9cac38618 update changelog 2021-11-28 18:50:57 +08:00
Michael W
e8ebfa312b Prevent infinite loops from unexpected inputs 2021-11-28 13:26:54 +08:00
Michael W
dae24891fa Add get_all_isolated_substrings method 2021-11-28 13:03:33 +08:00
Michael W
a4f9de1ca1 Fix bugs concerned with coloring 2021-11-28 12:14:29 +08:00
Michael W
697028cd4c Add slicing and indexing methods 2021-11-27 23:07:46 +08:00
Michael W
c84acc0023 Remove disabled methods 2021-11-27 19:53:52 +08:00
Michael W
b1d869cd11 Update __init__.py to include mtex_mobject 2021-11-27 16:21:06 +08:00
Michael W
13a5f6d6ff Add MTex 2021-11-27 16:19:01 +08:00
Michael W
e3f87d835b Recover files 2021-11-27 16:17:22 +08:00
Michael W
7ffab788b7 Recover numbers.py 2021-11-27 16:16:18 +08:00
Grant Sanderson
bcd09906be Fix bug in ShowSubmobjectsOneByOne 2021-11-22 08:05:59 -08:00
Grant Sanderson
407c53f97c Have rotation_between_vectors handle identical/similar vectors 2021-11-18 17:52:48 -08:00
Grant Sanderson
eea3c6b294 Better align SurfaceMesh to the corresponding surface polygons 2021-11-18 17:52:17 -08:00
Grant Sanderson
d2182b9112 Make sure set_length returns self 2021-11-18 17:51:56 -08:00
Grant Sanderson
fbc329d7ce Small bug fix for angle_between_vectors 2021-11-17 12:49:53 -08:00
Grant Sanderson
25045143a1 Have mobject uniforms supercede camera uniforms 2021-11-17 12:49:08 -08:00
Grant Sanderson
e899604a2d Add getter methods for specific euler angles 2021-11-17 12:48:17 -08:00
Grant Sanderson
0b898a5594 Add always_sort_to_camera for surfaces 2021-11-16 17:38:43 -08:00
Grant Sanderson
ee2f68cd49 Exchange gloss for reflectiveness 2021-11-16 17:38:30 -08:00
Grant Sanderson
2cce4ccdd7 Exchange gloss for reflectiveness 2021-11-16 17:38:08 -08:00
Grant Sanderson
f3ecebee43 Remove unnecessary import 2021-11-16 17:37:45 -08:00
Grant Sanderson
e764da3c3a use quick_point_from_proportion for graph points 2021-11-16 17:37:27 -08:00
Grant Sanderson
fbbea47d11 Change temp embed file name 2021-11-16 17:37:01 -08:00
Grant Sanderson
781a9934fd Add shortcut for setting black background stroke 2021-11-16 17:29:24 -08:00
Grant Sanderson
a7173142bf Fix VMobject.fade 2021-11-16 17:29:10 -08:00
Grant Sanderson
0e78027186 Improve point_from_proportion to account for arc length 2021-11-16 17:28:48 -08:00
Grant Sanderson
82bd02d21f Fix angle_between_vectors, add rotation_between_vectors 2021-11-16 17:08:35 -08:00
Grant Sanderson
d065e1973d Add option to insert embed line from the command line (mildly hacky) 2021-11-14 12:31:56 -08:00
Grant Sanderson
7070777408 Tiny formatting change 2021-11-12 15:47:23 -08:00
TonyCrane
5c2a9f2129 style: change CRLF to LF
Change the line ending characters from CRLF to LF
2021-11-12 21:49:56 +08:00
Michael W
1b695e1c19 Refactor Tex 2021-11-12 21:22:42 +08:00
Michael W
da1cc44d90 Remove SingleStringTex 2021-11-12 21:21:44 +08:00
Grant Sanderson
3bbb759112 Merge branch 'master' of github.com:3b1b/manim into video-work 2021-11-09 09:18:56 -08:00
Grant Sanderson
41c6cbcb59 Merge pull request #1675 from YishiMichael/master
Add boolean operations for mobjects
2021-11-09 09:18:38 -08:00
Grant Sanderson
5930e6a176 Refresh unit normal when reversing points 2021-11-09 09:15:15 -08:00
Grant Sanderson
8f3ff91165 Add reflectiveness to style and default to fill for VMobject.get_color 2021-11-09 09:15:00 -08:00
Michael W
b12677bc1a Add files via upload 2021-11-10 00:35:09 +08:00
Michael W
cdec64e3f1 Add boolean operations for mobjects 2021-11-10 00:23:40 +08:00
Michael W
2dc8bc9b9c Add boolean operations for mobjects 2021-11-10 00:23:04 +08:00
Michael W
94f0bf557a Add skia-pathops package 2021-11-10 00:21:04 +08:00
Grant Sanderson
e20690b7c1 Don't necessarily remove anti_alias on ThreeDScene 2021-11-08 21:48:42 -08:00
Grant Sanderson
2c7689ed9e Enable glow_factor on dots 2021-11-08 21:47:48 -08:00
Grant Sanderson
c73d507c76 Fix SurfaceMesh to be evenly spaced 2021-11-08 21:47:26 -08:00
Grant Sanderson
317a5d6226 Make it possible to set full screen preview as a default 2021-11-08 21:47:02 -08:00
Grant Sanderson
4339f97c56 Small refactor and added functionality 2021-11-08 21:46:35 -08:00
Grant Sanderson
81c3ae3037 Have separate notions of gloss and reflectiveness 2021-11-08 21:46:09 -08:00
Grant Sanderson
61b04079f5 Merge branch 'master' of github.com:3b1b/manim into video-work 2021-11-01 13:18:09 -07:00
Grant Sanderson
5a0e5a16ea Merge pull request #1667 from TurkeyBilly/master
Overridden add operations for mobjects
2021-11-01 13:17:16 -07:00
Grant Sanderson
f0b5181694 Update manimlib/mobject/mobject.py
Small bug fix to Mobject.__add__
2021-11-01 13:16:50 -07:00
Grant Sanderson
185782a2e7 Remove stray brace 2021-11-01 13:05:13 -07:00
Grant Sanderson
8ab95ebe9d Change where unit_normal data gets updated 2021-11-01 13:04:53 -07:00
Bill Xi
77159eea2e Update mobject.py 2021-11-01 10:19:06 +08:00
Bill Xi
6766e459f2 Update vectorized_mobject.py 2021-10-31 20:03:04 +08:00
Bill Xi
01f4ef3e5d Create mobject.py 2021-10-31 20:02:30 +08:00
Bill Xi
b531c82bc4 Update mobject.py 2021-10-31 20:01:16 +08:00
Bill Xi
5d942d5ac0 Update vectorized_mobject.py 2021-10-31 18:42:14 +08:00
Bill Xi
b285ca7c22 Update vectorized_mobject.py 2021-10-31 18:38:23 +08:00
Bill Xi
82540edae9 Update mobject.py 2021-10-31 18:37:12 +08:00
Bill Xi
f9a6fa7036 Update mobject.py 2021-10-31 18:35:28 +08:00
Bill Xi
4eabaecfc8 Update mobject.py 2021-10-31 18:34:23 +08:00
Grant Sanderson
b881e55fca Merge branch 'master' of github.com:3b1b/manim into video-work 2021-10-24 09:48:56 -07:00
Grant Sanderson
f1c50640a3 Merge pull request #1662 from YishiMichael/master
Refactor command handling in `svg_mobject.py`
2021-10-24 09:47:54 -07:00
Grant Sanderson
deb1311e48 Fix VideoIcon 2021-10-24 09:28:52 -07:00
Grant Sanderson
82fa6ab125 Temporary hack to fix a bug I don't understand 2021-10-24 09:28:39 -07:00
Michael W
4d91ff3f2f Update balance_braces method 2021-10-24 23:21:49 +08:00
Michael W
b6f9da87d0 Refactor command handling in svg_mobject.py 2021-10-24 22:30:18 +08:00
BillyLikesHacking
c60e97ebf9 Update vectorized_mobject.py 2021-10-22 20:58:19 +08:00
BillyLikesHacking
b1ed16e81a Update mobject.py 2021-10-22 20:46:47 +08:00
BillyLikesHacking
c94ebaa260 Update vectorized_mobject.py 2021-10-22 20:03:58 +08:00
BillyLikesHacking
030fb52018 Update vectorized_mobject.py 2021-10-22 20:03:05 +08:00
BillyLikesHacking
487f582302 Update mobject.py 2021-10-22 20:02:05 +08:00
BillyLikesHacking
6d0c55d2ba Update mobject.py 2021-10-22 20:00:27 +08:00
鹤翔万里
c82f60e29e Merge pull request #1658 from 050644zf/master
Update the link of the Chinese Ver. Docs
2021-10-20 17:25:28 +08:00
Nightsky
c03279d626 Update the link of the Chinese Ver. Docs 2021-10-20 17:20:52 +08:00
Grant Sanderson
7b72fa8ca1 Merge branch 'master' of github.com:3b1b/manim into video-work 2021-10-18 07:12:10 -07:00
Grant Sanderson
8b454fbe93 Slight tweaks to how saturation_factor works on newton-fractal 2021-10-18 07:12:05 -07:00
Grant Sanderson
f77e25ff86 Merge pull request #1655 from widcardw/master
Fix the bug of rotating camera
2021-10-18 07:01:58 -07:00
widcardw
872ef67cf7 Fix bug of rotating camera 2021-10-18 21:00:25 +08:00
widcardw
305ca72ebe Fix the bug of rotating camera 2021-10-18 19:05:05 +08:00
TonyCrane
4d81d3678b update changelog 2021-10-16 21:07:53 +08:00
鹤翔万里
55e968e174 Merge pull request #1653 from YishiMichael/master
Fix parameter typo
2021-10-16 21:06:32 +08:00
TonyCrane
97d1609849 update changelog 2021-10-16 21:03:36 +08:00
TonyCrane
e10f850d0d add cli flag to specify log level 2021-10-16 21:01:39 +08:00
Michael W
b8584fe5ab Fix parameter typo 2021-10-16 20:59:31 +08:00
45 changed files with 1309 additions and 295 deletions

View File

@@ -1,6 +1,70 @@
Changelog
=========
v1.3.0
------
Fixed bugs
^^^^^^^^^^
- `#1653 <https://github.com/3b1b/manim/pull/1653>`__: Fixed ``Mobject.stretch_to_fit_depth``
- `#1655 <https://github.com/3b1b/manim/pull/1655>`__: Fixed the bug of rotating camera
- `c73d507 <https://github.com/3b1b/manim/pull/1688/commits/c73d507c76af5c8602d4118bc7538ba04c03ebae>`__: Fixed ``SurfaceMesh`` to be evenly spaced
- `82bd02d <https://github.com/3b1b/manim/pull/1688/commits/82bd02d21fbd89b71baa21e077e143f440df9014>`__: Fixed ``angle_between_vectors`` add ``rotation_between_vectors``
- `a717314 <https://github.com/3b1b/manim/pull/1688/commits/a7173142bf93fd309def0cc10f3c56f5e6972332>`__: Fixed ``VMobject.fade``
- `fbc329d <https://github.com/3b1b/manim/pull/1688/commits/fbc329d7ce3b11821d47adf6052d932f7eff724a>`__: Fixed ``angle_between_vectors``
- `bcd0990 <https://github.com/3b1b/manim/pull/1688/commits/bcd09906bea5eaaa5352e7bee8f3153f434cf606>`__: Fixed bug in ``ShowSubmobjectsOneByOne``
- `7023548 <https://github.com/3b1b/manim/pull/1691/commits/7023548ec62c4adb2f371aab6a8c7f62deb7c33c>`__: Fixed bug in ``TransformMatchingParts``
New Features
^^^^^^^^^^^^
- `e10f850 <https://github.com/3b1b/manim/commit/e10f850d0d9f971931cc85d44befe67dc842af6d>`__: Added CLI flag ``--log-level`` to specify log level
- `#1667 <https://github.com/3b1b/manim/pull/1667>`__: Added operations (``+`` and ``*``) for ``Mobject``
- `#1675 <https://github.com/3b1b/manim/pull/1675>`__: Added 4 boolean operations for ``VMobject`` in ``manimlib/mobject/boolean_ops.py``
- ``Union(*vmobjects, **kwargs)``
- ``Difference(subject, clip, **kwargs)``
- ``Intersection(*vmobjects, **kwargs)``
- ``Exclusion(*vmobjects, **kwargs)``
- `81c3ae3 <https://github.com/3b1b/manim/pull/1688/commits/81c3ae30372e288dc772633dbd17def6e603753e>`__: Added reflectiveness
- `2c7689e <https://github.com/3b1b/manim/pull/1688/commits/2c7689ed9e81229ce87c648f97f26267956c0bc9>`__: Enabled ``glow_factor`` on ``DotCloud``
- `d065e19 <https://github.com/3b1b/manim/pull/1688/commits/d065e1973d1d6ebd2bece81ce4bdf0c2fff7c772>`__: Added option ``-e`` to insert embed line from the command line
- `0e78027 <https://github.com/3b1b/manim/pull/1688/commits/0e78027186a976f7e5fa8d586f586bf6e6baab8d>`__: Improved ``point_from_proportion`` to account for arc length
- `781a993 <https://github.com/3b1b/manim/pull/1688/commits/781a9934fda6ba11f22ba32e8ccddcb3ba78592e>`__: Added shortcut ``set_backstroke`` for setting black background stroke
- `0b898a5 <https://github.com/3b1b/manim/pull/1688/commits/0b898a5594203668ed9cad38b490ab49ba233bd4>`__: Added ``Suface.always_sort_to_camera``
- `e899604 <https://github.com/3b1b/manim/pull/1688/commits/e899604a2d05f78202fcb3b9824ec34647237eae>`__: Added getter methods for specific euler angles
- `407c53f <https://github.com/3b1b/manim/pull/1688/commits/407c53f97c061bfd8a53beacd88af4c786f9e9ee>`__: Hade ``rotation_between_vectors`` handle identical/similar vectors
- `49743da <https://github.com/3b1b/manim/pull/1688/commits/49743daf3244bfa11a427040bdde8e2bb79589e8>`__: Added ``Mobject.insert_submobject`` method
- `9dd1f47 <https://github.com/3b1b/manim/pull/1688/commits/9dd1f47dabca1580d6102e34e44574b0cba556e7>`__: Created single progress display for full scene render
- `264f7b1 <https://github.com/3b1b/manim/pull/1691/commits/264f7b11726e9e736f0fe472f66e38539f74e848>`__: Added ``Circle.get_radius``
- `83841ae <https://github.com/3b1b/manim/pull/1691/commits/83841ae41568a9c9dff44cd163106c19a74ac281>`__: Added ``Dodecahedron``
- `a1d5147 <https://github.com/3b1b/manim/pull/1691/commits/a1d51474ea1ce3b7aa3efbe4c5e221be70ee2f5b>`__: Added ``GlowDot``
- `#1678 <https://github.com/3b1b/manim/pull/1678>`__: Added ``MTex`` , see `#1678 <https://github.com/3b1b/manim/pull/1678>`__ for details
Refactor
^^^^^^^^
- `#1662 <https://github.com/3b1b/manim/pull/1662>`__: Refactored support for command ``A`` in path of SVG
- `#1662 <https://github.com/3b1b/manim/pull/1662>`__: Refactored ``SingleStringTex.balance_braces``
- `8b454fb <https://github.com/3b1b/manim/pull/1688/commits/8b454fbe9335a7011e947093230b07a74ba9c653>`__: Slight tweaks to how saturation_factor works on newton-fractal
- `317a5d6 <https://github.com/3b1b/manim/pull/1688/commits/317a5d6226475b6b54a78db7116c373ef84ea923>`__: Made it possible to set full screen preview as a default
- `e764da3 <https://github.com/3b1b/manim/pull/1688/commits/e764da3c3adc5ae2a4ce877b340d2b6abcddc2fc>`__: Used ``quick_point_from_proportion`` for graph points
- `d2182b9 <https://github.com/3b1b/manim/pull/1688/commits/d2182b9112300558b6c074cefd685f97c10b3898>`__: Made sure ``Line.set_length`` returns self
- `eea3c6b <https://github.com/3b1b/manim/pull/1688/commits/eea3c6b29438f9e9325329c4355e76b9f635e97a>`__: Better align ``SurfaceMesh`` to the corresponding surface polygons
- `ee1594a <https://github.com/3b1b/manim/pull/1688/commits/ee1594a3cb7a79b8fc361e4c4397a88c7d20c7e3>`__: Match ``fix_in_frame`` status for ``FlashAround`` mobject
- `ba23fbe <https://github.com/3b1b/manim/pull/1688/commits/ba23fbe71e4a038201cd7df1d200514ed1c13bc2>`__: Made sure ``Mobject.is_fixed_in_frame`` stays updated with uniforms
- `98b0d26 <https://github.com/3b1b/manim/pull/1691/commits/98b0d266d2475926a606331923cca3dc1dea97ad>`__: Made sure ``skip_animations`` and ``start_at_animation_number`` play well together
- `f8e6e7d <https://github.com/3b1b/manim/pull/1691/commits/f8e6e7df3ceb6f3d845ced4b690a85b35e0b8d00>`__: Updated progress display for full scene render
- `8f1dfab <https://github.com/3b1b/manim/pull/1691/commits/8f1dfabff04a8456f5c4df75b0f97d50b2755003>`__: ``VectorizedPoint`` should call ``__init__`` for both super classes
- `758f329 <https://github.com/3b1b/manim/pull/1691/commits/758f329a06a0c198b27a48c577575d94554305bf>`__: Used array copy when checking need for refreshing triangulation
Dependencies
^^^^^^^^^^^^
- `#1675 <https://github.com/3b1b/manim/pull/1675>`__: Added dependency on python packages `skia-pathops <https://github.com/fonttools/skia-pathops>`__
v1.2.0
------

View File

@@ -83,22 +83,3 @@ Its value is a dictionary, passed in as ``kwargs`` when initializing the ``Camer
to modify the value of the properties of the ``Camera`` class.
So the nesting of the ``CONFIG`` dictionary **essentially** passes in the value as ``kwargs``.
Common usage
------------
When writing a class by yourself, you can add attributes or modify the attributes
of the parent class through ``CONFIG``.
The most commonly used is to modify the properties of the camera when writing a ``Scene``:
.. code-block:: python
class YourScene(Scene):
CONFIG = {
"camera_config": {
"background_color": WHITE,
},
}
For example, the above dictionary will change the background color to white, etc.

View File

@@ -53,6 +53,7 @@ flag abbr function
``--config`` Guide for automatic configuration
``--file_name FILE_NAME`` Name for the movie or image file
``--start_at_animation_number START_AT_ANIMATION_NUMBER`` ``-n`` Start rendering not from the first animation, but from another, specified by its index. If you passing two comma separated values, e.g. "3,6", it will end the rendering at the second value.
``--embed LINENO`` ``-e`` Takes a line number as an argument, and results in the scene being called as if the line ``self.embed()`` was inserted into the scene code at that line number
``--resolution RESOLUTION`` ``-r`` Resolution, passed as "WxH", e.g. "1920x1080"
``--frame_rate FRAME_RATE`` Frame rate, as an integer
``--color COLOR`` ``-c`` Background color

View File

@@ -6,7 +6,7 @@ Manim's documentation
Manim is an animation engine for explanatory math videos. It's used to create precise animations programmatically, as seen in the videos
at `3Blue1Brown <https://www.3blue1brown.com/>`_.
And here is a Chinese version of this documentation: https://docs.manim.org.cn/shaders
And here is a Chinese version of this documentation: https://docs.manim.org.cn/
.. toctree::
:maxdepth: 2

View File

@@ -22,6 +22,7 @@ from manimlib.camera.camera import *
from manimlib.window import *
from manimlib.mobject.boolean_ops import *
from manimlib.mobject.coordinate_systems import *
from manimlib.mobject.changing import *
from manimlib.mobject.frame import *
@@ -36,6 +37,7 @@ from manimlib.mobject.probability import *
from manimlib.mobject.shape_matchers import *
from manimlib.mobject.svg.brace import *
from manimlib.mobject.svg.drawings import *
from manimlib.mobject.svg.mtex_mobject import *
from manimlib.mobject.svg.svg_mobject import *
from manimlib.mobject.svg.tex_mobject import *
from manimlib.mobject.svg.text_mobject import *

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env python
import manimlib.config
import manimlib.logger
import manimlib.extract_scene
import manimlib.utils.init_config
from manimlib import __version__
@@ -9,8 +10,10 @@ def main():
print(f"ManimGL \033[32mv{__version__}\033[0m")
args = manimlib.config.parse_cli()
if args.version and args.file == None:
if args.version and args.file is None:
return
if args.log_level:
manimlib.logger.log.setLevel(args.log_level)
if args.config:
manimlib.utils.init_config.init_customization()
@@ -21,5 +24,6 @@ def main():
for scene in scenes:
scene.run()
if __name__ == "__main__":
main()

View File

@@ -174,16 +174,12 @@ class ShowSubmobjectsOneByOne(ShowIncreasingSubsets):
"int_func": np.ceil,
}
def __init__(self, group, **kwargs):
new_group = Group(*group)
super().__init__(new_group, **kwargs)
def update_submobject_list(self, index):
# N = len(self.all_submobs)
if index == 0:
self.mobject.set_submobjects([])
else:
self.mobject.set_submobjects(self.all_submobs[index - 1])
self.mobject.set_submobjects([self.all_submobs[index - 1]])
# TODO, this is broken...

View File

@@ -224,6 +224,8 @@ class FlashAround(VShowPassingFlash):
def __init__(self, mobject, **kwargs):
digest_config(self, kwargs)
path = self.get_path(mobject)
if mobject.is_fixed_in_frame:
path.fix_in_frame()
path.insert_n_curves(self.n_inserted_curves)
path.set_points(path.get_points_without_null_curves())
path.set_stroke(self.color, self.stroke_width)

View File

@@ -68,10 +68,10 @@ class TransformMatchingParts(AnimationGroup):
anims.append(FadeTransformPieces(fade_source, fade_target, **kwargs))
else:
anims.append(FadeOutToPoint(
fade_source, fade_target.get_center(), **kwargs
fade_source, target_mobject.get_center(), **kwargs
))
anims.append(FadeInFromPoint(
fade_target.copy(), fade_source.get_center(), **kwargs
fade_target.copy(), mobject.get_center(), **kwargs
))
super().__init__(*anims)

View File

@@ -1,4 +1,5 @@
import moderngl
import math
from colour import Color
import OpenGL.GL as gl
@@ -67,7 +68,7 @@ class CameraFrame(Mobject):
added_rot_T = rotation_matrix_transpose(angle, axis)
new_rot_T = np.dot(curr_rot_T, added_rot_T)
Fz = new_rot_T[2]
phi = np.arccos(Fz[2])
phi = np.arccos(clip(Fz[2], -1, 1))
theta = angle_of_vector(Fz[:2]) + PI / 2
partial_rot_T = np.dot(
rotation_matrix_transpose(phi, RIGHT),
@@ -121,6 +122,15 @@ class CameraFrame(Mobject):
self.refresh_rotation_matrix()
return self
def get_theta(self):
return self.data["euler_angles"][0]
def get_phi(self):
return self.data["euler_angles"][1]
def get_gamma(self):
return self.data["euler_angles"][2]
def get_shape(self):
return (self.get_width(), self.get_height())
@@ -139,6 +149,16 @@ class CameraFrame(Mobject):
def get_focal_distance(self):
return self.focal_distance * self.get_height()
def get_implied_camera_location(self):
theta, phi, gamma = self.get_euler_angles()
dist = self.get_focal_distance()
x, y, z = self.get_center()
return (
x + dist * math.sin(theta) * math.sin(phi),
y - dist * math.cos(theta) * math.sin(phi),
z + dist * math.cos(phi)
)
def interpolate(self, *args, **kwargs):
super().interpolate(*args, **kwargs)
self.refresh_rotation_matrix()
@@ -194,20 +214,30 @@ class Camera(object):
fbo = self.get_fbo(ctx, 0)
else:
fbo = ctx.detect_framebuffer()
self.ctx = ctx
self.fbo = fbo
self.set_ctx_blending()
# For multisample antialiasing
fbo_msaa = self.get_fbo(ctx, self.samples)
fbo_msaa.use()
self.fbo_msaa = fbo_msaa
ctx.enable(moderngl.BLEND)
ctx.blend_func = (
def set_ctx_blending(self, enable=True):
if enable:
self.ctx.enable(moderngl.BLEND)
else:
self.ctx.disable(moderngl.BLEND)
self.ctx.blend_func = (
moderngl.SRC_ALPHA, moderngl.ONE_MINUS_SRC_ALPHA,
moderngl.ONE, moderngl.ONE
# moderngl.ONE, moderngl.ONE
)
self.ctx = ctx
self.fbo = fbo
self.fbo_msaa = fbo_msaa
def set_ctx_depth_test(self, enable=True):
if enable:
self.ctx.enable(moderngl.DEPTH_TEST)
else:
self.ctx.disable(moderngl.DEPTH_TEST)
def init_light_source(self):
self.light_source = Point(self.light_source_position)
@@ -297,6 +327,9 @@ class Camera(object):
def get_frame_center(self):
return self.frame.get_center()
def get_location(self):
return self.frame.get_implied_camera_location()
def resize_frame_shape(self, fixed_dimension=0):
"""
Changes frame_shape to match the aspect ratio
@@ -327,17 +360,11 @@ class Camera(object):
shader_wrapper = render_group["shader_wrapper"]
shader_program = render_group["prog"]
self.set_shader_uniforms(shader_program, shader_wrapper)
self.update_depth_test(shader_wrapper)
self.set_ctx_depth_test(shader_wrapper.depth_test)
render_group["vao"].render(int(shader_wrapper.render_primitive))
if render_group["single_use"]:
self.release_render_group(render_group)
def update_depth_test(self, shader_wrapper):
if shader_wrapper.depth_test:
self.ctx.enable(moderngl.DEPTH_TEST)
else:
self.ctx.disable(moderngl.DEPTH_TEST)
def get_render_group_list(self, mobject):
try:
return self.static_mobject_to_render_group_list[id(mobject)]
@@ -410,7 +437,7 @@ class Camera(object):
for name, path in shader_wrapper.texture_paths.items():
tid = self.get_texture_id(path)
shader[name].value = tid
for name, value in it.chain(shader_wrapper.uniforms.items(), self.perspective_uniforms.items()):
for name, value in it.chain(self.perspective_uniforms.items(), shader_wrapper.uniforms.items()):
try:
if isinstance(value, np.ndarray):
value = tuple(value)
@@ -427,14 +454,18 @@ class Camera(object):
anti_alias_width = self.anti_alias_width / (ph / fh)
# Orient light
rotation = frame.get_inverse_camera_rotation_matrix()
light_pos = self.light_source.get_location()
light_pos = np.dot(rotation, light_pos)
offset = frame.get_center()
light_pos = np.dot(
rotation, self.light_source.get_location() + offset
)
cam_pos = self.frame.get_implied_camera_location() # TODO
self.perspective_uniforms = {
"frame_shape": frame.get_shape(),
"anti_alias_width": anti_alias_width,
"camera_center": tuple(frame.get_center()),
"camera_offset": tuple(offset),
"camera_rotation": tuple(np.array(rotation).T.flatten()),
"camera_position": tuple(cam_pos),
"light_source_position": tuple(light_pos),
"focal_distance": frame.get_focal_distance(),
}
@@ -445,6 +476,8 @@ class Camera(object):
def get_texture_id(self, path):
if path not in self.path_to_texture:
if self.n_textures == 15: # I have no clue why this is needed
self.n_textures += 1
tid = self.n_textures
self.n_textures += 1
im = Image.open(path).convert("RGBA")
@@ -468,4 +501,5 @@ class Camera(object):
class ThreeDCamera(Camera):
CONFIG = {
"samples": 4,
"anti_alias_width": 0,
}

View File

@@ -5,6 +5,7 @@ import importlib
import os
import sys
import yaml
from contextlib import contextmanager
from screeninfo import get_monitors
from manimlib.utils.config_ops import merge_dicts_recursively
@@ -12,6 +13,9 @@ from manimlib.utils.init_config import init_customization
from manimlib.logger import log
__config_file__ = "custom_config.yml"
def parse_cli():
try:
parser = argparse.ArgumentParser()
@@ -112,6 +116,12 @@ def parse_cli():
"in two comma separated values, e.g. \"3,6\", it will end"
"the rendering at the second value",
)
parser.add_argument(
"-e", "--embed", metavar="LINENO",
help="Takes a line number as an argument, and results"
"in the scene being called as if the line `self.embed()`"
"was inserted into the scene code at that line number."
)
parser.add_argument(
"-r", "--resolution",
help="Resolution, passed as \"WxH\", e.g. \"1920x1080\"",
@@ -142,6 +152,10 @@ def parse_cli():
action="store_true",
help="Display the version of manimgl"
)
parser.add_argument(
"--log-level",
help="Level of messages to Display, can be DEBUG / INFO / WARNING / ERROR / CRITICAL"
)
args = parser.parse_args()
return args
except argparse.ArgumentError as err:
@@ -158,14 +172,30 @@ def get_manim_dir():
def get_module(file_name):
if file_name is None:
return None
else:
module_name = file_name.replace(os.sep, ".").replace(".py", "")
spec = importlib.util.spec_from_file_location(module_name, file_name)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
module_name = file_name.replace(os.sep, ".").replace(".py", "")
spec = importlib.util.spec_from_file_location(module_name, file_name)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
@contextmanager
def insert_embed_line(file_name, lineno):
with open(file_name, 'r') as fp:
lines = fp.readlines()
line = lines[lineno - 1]
n_spaces = len(line) - len(line.lstrip())
lines.insert(lineno, " " * n_spaces + "self.embed()\n")
alt_file = file_name.replace(".py", "_inserted_embed.py")
with open(alt_file, 'w') as fp:
fp.writelines(lines)
try:
yield alt_file
finally:
os.remove(alt_file)
__config_file__ = "custom_config.yml"
def get_custom_config():
global __config_file__
@@ -193,9 +223,11 @@ def get_custom_config():
def check_temporary_storage(config):
if config["directories"]["temporary_storage"] == "" and sys.platform == "win32":
log.warning("You may be using Windows platform and have not specified the path of"
log.warning(
"You may be using Windows platform and have not specified the path of"
" `temporary_storage`, which may cause OSError. So it is recommended"
" to specify the `temporary_storage` in the config file (.yml)")
" to specify the `temporary_storage` in the config file (.yml)"
)
def get_configuration(args):
@@ -225,8 +257,10 @@ def get_configuration(args):
elif not os.path.exists(__config_file__):
log.info(f"Using the default configuration file, which you can modify in `{global_defaults_file}`")
log.info("If you want to create a local configuration file, you can create a file named"
f" `{__config_file__}`, or run `manimgl --config`")
log.info(
"If you want to create a local configuration file, you can create a file named"
f" `{__config_file__}`, or run `manimgl --config`"
)
custom_config = get_custom_config()
check_temporary_storage(custom_config)
@@ -256,16 +290,22 @@ def get_configuration(args):
"quiet": args.quiet,
}
module = get_module(args.file)
if args.embed is None:
module = get_module(args.file)
else:
with insert_embed_line(args.file, int(args.embed)) as alt_file:
module = get_module(alt_file)
config = {
"module": module,
"scene_names": args.scene_names,
"file_writer_config": file_writer_config,
"quiet": args.quiet or args.write_all,
"write_all": args.write_all,
"skip_animations": args.skip_animations,
"start_at_animation_number": args.start_at_animation_number,
"preview": not write_file,
"end_at_animation_number": None,
"preview": not write_file,
"leave_progress_bars": args.leave_progress_bars,
}
@@ -278,7 +318,7 @@ def get_configuration(args):
mon_index = custom_config["window_monitor"]
monitor = monitors[min(mon_index, len(monitors) - 1)]
window_width = monitor.width
if not args.full_screen:
if not (args.full_screen or custom_config["full_screen"]):
window_width //= 2
window_height = window_width * 9 // 16
config["window_config"] = {
@@ -295,10 +335,6 @@ def get_configuration(args):
else:
config["start_at_animation_number"] = int(stan)
config["skip_animations"] = any([
args.skip_animations,
args.start_at_animation_number,
])
return config

View File

@@ -34,6 +34,7 @@ style:
# the window on the monitor, e.g. "960,540"
window_position: UR
window_monitor: 0
full_screen: False
# If break_into_partial_movies is set to True, then many small
# files will be written corresponding to each Scene.play and
# Scene.wait call, and these files will then be combined

View File

@@ -1,5 +1,6 @@
import inspect
import sys
import copy
from manimlib.scene.scene import Scene
from manimlib.config import get_custom_config
@@ -38,7 +39,7 @@ def prompt_user_for_choice(scene_classes):
"\nScene Name or Number: "
)
return [
name_to_class[split_str] if not split_str.isnumeric() else scene_classes[int(split_str)-1]
name_to_class[split_str] if not split_str.isnumeric() else scene_classes[int(split_str) - 1]
for split_str in user_input.replace(" ", "").split(",")
]
except IndexError:
@@ -67,6 +68,26 @@ def get_scene_config(config):
])
def compute_total_frames(scene_class, scene_config):
"""
When a scene is being written to file, a copy of the scene is run with
skip_animations set to true so as to count how many frames it will require.
This allows for a total progress bar on rendering, and also allows runtime
errors to be exposed preemptively for long running scenes. The final frame
is saved by default, so that one can more quickly check that the last frame
looks as expected.
"""
pre_config = copy.deepcopy(scene_config)
pre_config["file_writer_config"]["write_to_movie"] = False
pre_config["file_writer_config"]["save_last_frame"] = True
pre_config["file_writer_config"]["quiet"] = True
pre_config["skip_animations"] = True
pre_scene = scene_class(**pre_config)
pre_scene.run()
total_time = pre_scene.time - pre_scene.skip_time
return int(total_time * scene_config["camera_config"]["frame_rate"])
def get_scenes_to_render(scene_classes, scene_config, config):
if config["write_all"]:
return [sc(**scene_config) for sc in scene_classes]
@@ -76,6 +97,9 @@ def get_scenes_to_render(scene_classes, scene_config, config):
found = False
for scene_class in scene_classes:
if scene_class.__name__ == scene_name:
fw_config = scene_config["file_writer_config"]
if fw_config["write_to_movie"]:
fw_config["total_frames"] = compute_total_frames(scene_class, scene_config)
scene = scene_class(**scene_config)
result.append(scene)
found = True

View File

@@ -6,7 +6,8 @@ __all__ = ["log"]
FORMAT = "%(message)s"
logging.basicConfig(
level="NOTSET", format=FORMAT, datefmt="[%X]", handlers=[RichHandler()]
level=logging.WARNING, format=FORMAT, datefmt="[%X]", handlers=[RichHandler()]
)
log = logging.getLogger("rich")
log = logging.getLogger("manimgl")
log.setLevel("DEBUG")

View File

@@ -0,0 +1,116 @@
import numpy as np
import pathops
from manimlib.mobject.types.vectorized_mobject import VMobject
# Boolean operations between 2D mobjects
# Borrowed from from https://github.com/ManimCommunity/manim/
def _convert_vmobject_to_skia_path(vmobject):
path = pathops.Path()
subpaths = vmobject.get_subpaths_from_points(vmobject.get_all_points())
for subpath in subpaths:
quads = vmobject.get_bezier_tuples_from_points(subpath)
start = subpath[0]
path.moveTo(*start[:2])
for p0, p1, p2 in quads:
path.quadTo(*p1[:2], *p2[:2])
if vmobject.consider_points_equals(subpath[0], subpath[-1]):
path.close()
return path
def _convert_skia_path_to_vmobject(path, vmobject):
PathVerb = pathops.PathVerb
current_path_start = np.array([0.0, 0.0, 0.0])
for path_verb, points in path:
if path_verb == PathVerb.CLOSE:
vmobject.add_line_to(current_path_start)
else:
points = np.hstack((np.array(points), np.zeros((len(points), 1))))
if path_verb == PathVerb.MOVE:
for point in points:
current_path_start = point
vmobject.start_new_path(point)
elif path_verb == PathVerb.CUBIC:
vmobject.add_cubic_bezier_curve_to(*points)
elif path_verb == PathVerb.LINE:
vmobject.add_line_to(points[0])
elif path_verb == PathVerb.QUAD:
vmobject.add_quadratic_bezier_curve_to(*points)
else:
raise Exception(f"Unsupported: {path_verb}")
return vmobject
class Union(VMobject):
def __init__(self, *vmobjects, **kwargs):
if len(vmobjects) < 2:
raise ValueError("At least 2 mobjects needed for Union.")
super().__init__(**kwargs)
outpen = pathops.Path()
paths = [
_convert_vmobject_to_skia_path(vmobject)
for vmobject in vmobjects
]
pathops.union(paths, outpen.getPen())
_convert_skia_path_to_vmobject(outpen, self)
class Difference(VMobject):
def __init__(self, subject, clip, **kwargs):
super().__init__(**kwargs)
outpen = pathops.Path()
pathops.difference(
[_convert_vmobject_to_skia_path(subject)],
[_convert_vmobject_to_skia_path(clip)],
outpen.getPen(),
)
_convert_skia_path_to_vmobject(outpen, self)
class Intersection(VMobject):
def __init__(self, *vmobjects, **kwargs):
if len(vmobjects) < 2:
raise ValueError("At least 2 mobjects needed for Intersection.")
super().__init__(**kwargs)
outpen = pathops.Path()
pathops.intersection(
[_convert_vmobject_to_skia_path(vmobjects[0])],
[_convert_vmobject_to_skia_path(vmobjects[1])],
outpen.getPen(),
)
new_outpen = outpen
for _i in range(2, len(vmobjects)):
new_outpen = pathops.Path()
pathops.intersection(
[outpen],
[_convert_vmobject_to_skia_path(vmobjects[_i])],
new_outpen.getPen(),
)
outpen = new_outpen
_convert_skia_path_to_vmobject(outpen, self)
class Exclusion(VMobject):
def __init__(self, *vmobjects, **kwargs):
if len(vmobjects) < 2:
raise ValueError("At least 2 mobjects needed for Exclusion.")
super().__init__(**kwargs)
outpen = pathops.Path()
pathops.xor(
[_convert_vmobject_to_skia_path(vmobjects[0])],
[_convert_vmobject_to_skia_path(vmobjects[1])],
outpen.getPen(),
)
new_outpen = outpen
for _i in range(2, len(vmobjects)):
new_outpen = pathops.Path()
pathops.xor(
[outpen],
[_convert_vmobject_to_skia_path(vmobjects[_i])],
new_outpen.getPen(),
)
outpen = new_outpen
_convert_skia_path_to_vmobject(outpen, self)

View File

@@ -151,14 +151,14 @@ class CoordinateSystem():
else:
alpha = binary_search(
function=lambda a: self.point_to_coords(
graph.point_from_proportion(a)
graph.quick_point_from_proportion(a)
)[0],
target=x,
lower_bound=self.x_range[0],
upper_bound=self.x_range[1],
)
if alpha is not None:
return graph.point_from_proportion(alpha)
return graph.quick_point_from_proportion(alpha)
else:
return None

View File

@@ -305,6 +305,9 @@ class Circle(Arc):
(angle - start_angle) / TAU
)
def get_radius(self):
return get_norm(self.get_start() - self.get_center())
class Dot(Circle):
CONFIG = {
@@ -507,6 +510,7 @@ class Line(TipableVMobject):
def set_length(self, length, **kwargs):
self.scale(length / self.get_length(), **kwargs)
return self
class DashedLine(Line):

View File

@@ -43,10 +43,13 @@ class Mobject(object):
"opacity": 1,
"dim": 3, # TODO, get rid of this
# Lighting parameters
# Positive gloss up to 1 makes it reflect the light.
"gloss": 0.0,
# Positive shadow up to 1 makes a side opposite the light darker
# ...
# Larger reflectiveness makes things brighter when facing the light
"reflectiveness": 0.0,
# Larger shadow makes faces opposite the light darker
"shadow": 0.0,
# Makes parts bright where light gets reflected toward the camera
"gloss": 0.0,
# For shaders
"shader_folder": "",
"render_primitive": moderngl.TRIANGLE_STRIP,
@@ -82,6 +85,14 @@ class Mobject(object):
def __str__(self):
return self.__class__.__name__
def __add__(self, other: 'Mobject') -> 'Mobject':
assert(isinstance(other, Mobject))
return self.get_group_class()(self, other)
def __mul__(self, other: 'int') -> 'Mobject':
assert(isinstance(other, int))
return self.replicate(other)
def init_data(self):
self.data = {
"points": np.zeros((0, 3)),
@@ -94,6 +105,7 @@ class Mobject(object):
"is_fixed_in_frame": float(self.is_fixed_in_frame),
"gloss": self.gloss,
"shadow": self.shadow,
"reflectiveness": self.reflectiveness,
}
def init_colors(self):
@@ -145,6 +157,7 @@ class Mobject(object):
for mob in self.get_family():
for key in mob.data:
mob.data[key] = mob.data[key][::-1]
self.refresh_unit_normal()
return self
def apply_points_function(self, func, about_point=None, about_edge=ORIGIN, works_on_bounding_box=False):
@@ -300,6 +313,11 @@ class Mobject(object):
self.assemble_family()
return self
def insert_submobject(self, index, new_submob):
self.submobjects.insert(index, new_submob)
self.assemble_family()
return self
def set_submobjects(self, submobject_list):
self.remove(*self.submobjects)
self.add(*submobject_list)
@@ -386,6 +404,7 @@ class Mobject(object):
self.submobjects.sort(key=submob_func)
else:
self.submobjects.sort(key=lambda m: point_to_num_func(m.get_center()))
self.assemble_family()
return self
def shuffle(self, recurse=False):
@@ -393,6 +412,7 @@ class Mobject(object):
for submob in self.submobjects:
submob.shuffle(recurse=True)
random.shuffle(self.submobjects)
self.assemble_family()
return self
# Copying
@@ -767,7 +787,7 @@ class Mobject(object):
return self.rescale_to_fit(height, 1, stretch=True, **kwargs)
def stretch_to_fit_depth(self, depth, **kwargs):
return self.rescale_to_fit(depth, 1, stretch=True, **kwargs)
return self.rescale_to_fit(depth, 2, stretch=True, **kwargs)
def set_width(self, width, stretch=False, **kwargs):
return self.rescale_to_fit(width, 0, stretch=stretch, **kwargs)
@@ -966,12 +986,12 @@ class Mobject(object):
def fade(self, darkness=0.5, recurse=True):
self.set_opacity(1.0 - darkness, recurse=recurse)
def get_gloss(self):
return self.uniforms["gloss"]
def get_reflectiveness(self):
return self.uniforms["reflectiveness"]
def set_gloss(self, gloss, recurse=True):
def set_reflectiveness(self, reflectiveness, recurse=True):
for mob in self.get_family(recurse):
mob.uniforms["gloss"] = gloss
mob.uniforms["reflectiveness"] = reflectiveness
return self
def get_shadow(self):
@@ -982,6 +1002,14 @@ class Mobject(object):
mob.uniforms["shadow"] = shadow
return self
def get_gloss(self):
return self.uniforms["gloss"]
def set_gloss(self, gloss, recurse=True):
for mob in self.get_family(recurse):
mob.uniforms["gloss"] = gloss
return self
# Background rectangle
def add_background_rectangle(self, color=None, opacity=0.75, **kwargs):
@@ -1363,11 +1391,13 @@ class Mobject(object):
@affects_shader_info_id
def fix_in_frame(self):
self.uniforms["is_fixed_in_frame"] = 1.0
self.is_fixed_in_frame = True
return self
@affects_shader_info_id
def unfix_from_frame(self):
self.uniforms["is_fixed_in_frame"] = 0.0
self.is_fixed_in_frame = False
return self
@affects_shader_info_id
@@ -1610,6 +1640,10 @@ class Group(Mobject):
Mobject.__init__(self, **kwargs)
self.add(*mobjects)
def __add__(self, other: 'Mobject' or 'Group'):
assert(isinstance(other, Mobject))
return self.add(other)
class Point(Mobject):
CONFIG = {

View File

@@ -200,12 +200,11 @@ class Laptop(VGroup):
class VideoIcon(SVGMobject):
CONFIG = {
"file_name": "video_icon",
"width": FRAME_WIDTH / 12.,
}
def __init__(self, **kwargs):
SVGMobject.__init__(self, **kwargs)
super().__init__(file_name="video_icon", **kwargs)
self.center()
self.set_width(self.width)
self.set_stroke(color=WHITE, width=0)

View File

@@ -0,0 +1,521 @@
import itertools as it
import re
from types import MethodType
from manimlib.mobject.svg.svg_mobject import SVGMobject
from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.utils.iterables import adjacent_pairs
from manimlib.utils.iterables import remove_list_redundancies
from manimlib.utils.tex_file_writing import tex_to_svg_file
from manimlib.utils.tex_file_writing import get_tex_config
from manimlib.utils.tex_file_writing import display_during_execution
SCALE_FACTOR_PER_FONT_POINT = 0.001
tex_hash_to_mob_map = {}
def _get_neighbouring_pairs(iterable):
return list(adjacent_pairs(iterable))[:-1]
class _LabelledTex(SVGMobject):
CONFIG = {
"height": None,
"path_string_config": {
"should_subdivide_sharp_curves": True,
"should_remove_null_curves": True,
},
}
@staticmethod
def color_str_to_label(color_str):
if len(color_str) == 4:
# "#RGB" => "#RRGGBB"
color_str = "#" + "".join([c * 2 for c in color_str[1:]])
return int(color_str[1:], 16) - 1
def get_mobjects_from(self, element):
result = super().get_mobjects_from(element)
for mob in result:
if not hasattr(mob, "glyph_label"):
mob.glyph_label = -1
try:
color_str = element.getAttribute("fill")
if color_str:
glyph_label = _LabelledTex.color_str_to_label(color_str)
for mob in result:
mob.glyph_label = glyph_label
except:
pass
return result
class _TexSpan(object):
def __init__(self, script_type, label):
# script_type: 0 for normal, 1 for subscript, 2 for superscript.
# Only those spans with `script_type == 0` will be colored.
self.script_type = script_type
self.label = label
self.containing_labels = []
def __repr__(self):
return "_TexSpan(" + ", ".join([
attrib_name + "=" + str(getattr(self, attrib_name))
for attrib_name in ["script_type", "label", "containing_labels"]
]) + ")"
class _TexParser(object):
def __init__(self, mtex):
self.tex_string = mtex.tex_string
strings_to_break_up = remove_list_redundancies([
*mtex.isolate, *mtex.tex_to_color_map.keys(), mtex.tex_string
])
if "" in strings_to_break_up:
strings_to_break_up.remove("")
unbreakable_commands = mtex.unbreakable_commands
self.tex_spans_dict = {}
self.current_label = 0
self.break_up_by_braces()
self.break_up_by_scripts()
self.break_up_by_additional_strings(strings_to_break_up)
self.merge_unbreakable_commands(unbreakable_commands)
self.analyse_containing_labels()
@staticmethod
def label_to_color_tuple(n):
# Get a unique color different from black,
# or the svg file will not include the color information.
rgb = n + 1
rg, b = divmod(rgb, 256)
r, g = divmod(rg, 256)
return r, g, b
@staticmethod
def contains(span_0, span_1):
return span_0[0] <= span_1[0] and span_1[1] <= span_0[1]
def add_tex_span(self, span_tuple, script_type=0, label=-1):
if script_type == 0:
# Should be additionally labelled.
label = self.current_label
self.current_label += 1
tex_span = _TexSpan(script_type, label)
self.tex_spans_dict[span_tuple] = tex_span
def break_up_by_braces(self):
tex_string = self.tex_string
span_tuples = []
left_brace_indices = []
for match_obj in re.finditer(r"(\\*)(\{|\})", tex_string):
# Braces following even numbers of backslashes are counted.
if len(match_obj.group(1)) % 2 == 1:
continue
if match_obj.group(2) == "{":
left_brace_index = match_obj.span(2)[0]
left_brace_indices.append(left_brace_index)
else:
left_brace_index = left_brace_indices.pop()
right_brace_index = match_obj.span(2)[1]
span_tuples.append((left_brace_index, right_brace_index))
if left_brace_indices:
self.raise_tex_parsing_error()
self.paired_braces_tuples = span_tuples
for span_tuple in span_tuples:
self.add_tex_span(span_tuple)
def break_up_by_scripts(self):
tex_string = self.tex_string
brace_indices_dict = dict(self.tex_spans_dict.keys())
for match_obj in re.finditer(r"((?<!\\)(_|\^)\s*)|(\s+(_|\^)\s*)", tex_string):
script_type = 1 if "_" in match_obj.group() else 2
token_begin, token_end = match_obj.span()
if token_end in brace_indices_dict:
content_span = (token_end, brace_indices_dict[token_end])
else:
content_match_obj = re.match(r"\w|\\[a-zA-Z]+", tex_string[token_end:])
if not content_match_obj:
self.raise_tex_parsing_error()
content_span = tuple([
index + token_end for index in content_match_obj.span()
])
self.add_tex_span(content_span)
label = self.tex_spans_dict[content_span].label
self.add_tex_span(
(token_begin, content_span[1]),
script_type=script_type,
label=label
)
def break_up_by_additional_strings(self, strings_to_break_up):
tex_string = self.tex_string
all_span_tuples = []
for string in strings_to_break_up:
# Only matches non-crossing strings.
for match_obj in re.finditer(re.escape(string), tex_string):
all_span_tuples.append(match_obj.span())
script_spans_dict = dict([
span_tuple[::-1]
for span_tuple, tex_span in self.tex_spans_dict.items()
if tex_span.script_type != 0
])
for span_begin, span_end in all_span_tuples:
if span_end in script_spans_dict.values():
# Deconstruct spans with subscripts & superscripts.
while span_end in script_spans_dict:
span_end = script_spans_dict[span_end]
if span_begin >= span_end:
continue
span_tuple = (span_begin, span_end)
if span_tuple not in self.tex_spans_dict:
self.add_tex_span(span_tuple)
def merge_unbreakable_commands(self, unbreakable_commands):
tex_string = self.tex_string
command_merge_spans = []
brace_indices_dict = dict(self.paired_braces_tuples)
# Braces leading by `unbreakable_commands` shouldn't be marked.
for command in unbreakable_commands:
for match_obj in re.finditer(re.escape(command), tex_string):
merge_begin_index = match_obj.span()[1]
merge_end_index = merge_begin_index
if merge_end_index not in brace_indices_dict:
continue
while merge_end_index in brace_indices_dict:
merge_end_index = brace_indices_dict[merge_end_index]
command_merge_spans.append((merge_begin_index, merge_end_index))
self.tex_spans_dict = {
span_tuple: tex_span
for span_tuple, tex_span in self.tex_spans_dict.items()
if all([
not _TexParser.contains(merge_span, span_tuple)
for merge_span in command_merge_spans
])
}
def analyse_containing_labels(self):
for span_0, tex_span_0 in self.tex_spans_dict.items():
if tex_span_0.script_type != 0:
continue
for span_1, tex_span_1 in self.tex_spans_dict.items():
if _TexParser.contains(span_1, span_0):
tex_span_1.containing_labels.append(tex_span_0.label)
def get_labelled_expression(self):
tex_string = self.tex_string
if not self.tex_spans_dict:
return tex_string
indices_with_labels = sorted([
(span_tuple[i], i, span_tuple[1 - i], tex_span.label)
for span_tuple, tex_span in self.tex_spans_dict.items()
if tex_span.script_type == 0
for i in range(2)
], key=lambda t: (t[0], -t[1], -t[2]))
# Add one more item to ensure all the substrings are joined.
indices_with_labels.append((len(tex_string), 0, 0, 0))
result = tex_string[: indices_with_labels[0][0]]
index_with_label_pairs = _get_neighbouring_pairs(indices_with_labels)
for index_with_label, next_index_with_label in index_with_label_pairs:
index, flag, _, label = index_with_label
next_index, *_ = next_index_with_label
# Adding one more pair of braces will help maintain the glyghs of tex file...
if flag == 0:
color_tuple = _TexParser.label_to_color_tuple(label)
result += "".join([
"{{",
"\\color[RGB]",
"{",
",".join(map(str, color_tuple)),
"}"
])
else:
result += "}}"
result += tex_string[index : next_index]
return result
def raise_tex_parsing_error(self):
raise ValueError(f"Failed to parse tex: \"{self.tex_string}\"")
class MTex(VMobject):
CONFIG = {
"fill_opacity": 1.0,
"stroke_width": 0,
"should_center": True,
"font_size": 48,
"height": None,
"organize_left_to_right": False,
"alignment": "\\centering",
"tex_environment": "align*",
"isolate": [],
"unbreakable_commands": ["\\begin", "\\end"],
"tex_to_color_map": {},
}
def __init__(self, tex_string, **kwargs):
super().__init__(**kwargs)
self.tex_string = MTex.modify_tex_string(tex_string)
tex_parser = _TexParser(self)
self.tex_spans_dict = tex_parser.tex_spans_dict
new_tex = tex_parser.get_labelled_expression()
full_tex = self.get_tex_file_body(new_tex)
hash_val = hash(full_tex)
if hash_val not in tex_hash_to_mob_map:
with display_during_execution(f"Writing \"{tex_string}\""):
filename = tex_to_svg_file(full_tex)
svg_mob = _LabelledTex(filename)
tex_hash_to_mob_map[hash_val] = svg_mob
self.add(*[
submob.copy()
for submob in tex_hash_to_mob_map[hash_val]
])
self.build_submobjects()
self.init_colors()
self.set_color_by_tex_to_color_map(self.tex_to_color_map)
if self.height is None:
self.scale(SCALE_FACTOR_PER_FONT_POINT * self.font_size)
if self.organize_left_to_right:
self.organize_submobjects_left_to_right()
@staticmethod
def modify_tex_string(tex_string):
result = tex_string.strip("\n")
# Prevent from passing an empty string.
if not result:
result = "\\quad"
return result
def get_tex_file_body(self, new_tex):
if self.tex_environment:
new_tex = "\n".join([
f"\\begin{{{self.tex_environment}}}",
new_tex,
f"\\end{{{self.tex_environment}}}"
])
if self.alignment:
new_tex = "\n".join([self.alignment, new_tex])
tex_config = get_tex_config()
return tex_config["tex_body"].replace(
tex_config["text_to_replace"],
new_tex
)
def build_submobjects(self):
if not self.submobjects:
return
self.group_submobjects()
self.sort_scripts_in_tex_order()
self.assign_submob_tex_strings()
def group_submobjects(self):
# Simply pack together adjacent mobjects with the same label.
new_submobjects = []
def append_new_submobject(glyphs):
if glyphs:
submobject = VGroup(*glyphs)
submobject.submob_label = glyphs[0].glyph_label
new_submobjects.append(submobject)
new_glyphs = []
current_glyph_label = -1
for submob in self.submobjects:
if submob.glyph_label == current_glyph_label:
new_glyphs.append(submob)
else:
append_new_submobject(new_glyphs)
new_glyphs = [submob]
current_glyph_label = submob.glyph_label
append_new_submobject(new_glyphs)
self.set_submobjects(new_submobjects)
def sort_scripts_in_tex_order(self):
# LaTeX always puts superscripts before subscripts.
# This function sorts the submobjects of scripts in the order of tex given.
index_and_span_list = sorted([
(index, span_tuple)
for span_tuple, tex_span in self.tex_spans_dict.items()
if tex_span.script_type != 0
for index in span_tuple
])
index_and_span_pair = _get_neighbouring_pairs(index_and_span_list)
for index_and_span_0, index_and_span_1 in index_and_span_pair:
index_0, span_tuple_0 = index_and_span_0
index_1, span_tuple_1 = index_and_span_1
if index_0 != index_1:
continue
if not all([
self.tex_spans_dict[span_tuple_0].script_type == 1,
self.tex_spans_dict[span_tuple_1].script_type == 2
]):
continue
submob_slice_0 = self.slice_of_part(
self.get_part_by_span_tuples([span_tuple_0])
)
submob_slice_1 = self.slice_of_part(
self.get_part_by_span_tuples([span_tuple_1])
)
submobs = self.submobjects
self.set_submobjects([
*submobs[: submob_slice_1.start],
*submobs[submob_slice_0],
*submobs[submob_slice_1.stop : submob_slice_0.start],
*submobs[submob_slice_1],
*submobs[submob_slice_0.stop :]
])
def assign_submob_tex_strings(self):
# Not sure whether this is the best practice...
# Just a temporary hack for supporting `TransformMatchingTex`.
tex_string = self.tex_string
# Use tex strings including "_", "^".
label_dict = {}
for span_tuple, tex_span in self.tex_spans_dict.items():
if tex_span.script_type != 0:
label_dict[tex_span.label] = span_tuple
else:
if tex_span.label not in label_dict:
label_dict[tex_span.label] = span_tuple
curr_labels = [submob.submob_label for submob in self.submobjects]
prev_labels = [curr_labels[-1], *curr_labels[:-1]]
next_labels = [*curr_labels[1:], curr_labels[0]]
tex_string_spans = []
for curr_label, prev_label, next_label in zip(
curr_labels, prev_labels, next_labels
):
curr_span_tuple = label_dict[curr_label]
prev_span_tuple = label_dict[prev_label]
next_span_tuple = label_dict[next_label]
containing_labels = self.tex_spans_dict[curr_span_tuple].containing_labels
tex_string_spans.append([
prev_span_tuple[1] if prev_label in containing_labels else curr_span_tuple[0],
next_span_tuple[0] if next_label in containing_labels else curr_span_tuple[1]
])
tex_string_spans[0][0] = label_dict[curr_labels[0]][0]
tex_string_spans[-1][1] = label_dict[curr_labels[-1]][1]
for submob, tex_string_span in zip(self.submobjects, tex_string_spans):
submob.tex_string = tex_string[slice(*tex_string_span)]
# Support `get_tex()` method here.
submob.get_tex = MethodType(lambda inst: inst.tex_string, submob)
def get_part_by_span_tuples(self, span_tuples):
labels = remove_list_redundancies(list(it.chain(*[
self.tex_spans_dict[span_tuple].containing_labels
for span_tuple in span_tuples
])))
return VGroup(*filter(
lambda submob: submob.submob_label in labels,
self.submobjects
))
def find_span_components_of_custom_span(self, custom_span_tuple, partial_result=[]):
span_begin, span_end = custom_span_tuple
if span_begin == span_end:
return partial_result
next_begin_choices = sorted([
span_tuple[1]
for span_tuple in self.tex_spans_dict.keys()
if span_tuple[0] == span_begin and span_tuple[1] <= span_end
], reverse=True)
for next_begin in next_begin_choices:
result = self.find_span_components_of_custom_span(
(next_begin, span_end), [*partial_result, (span_begin, next_begin)]
)
if result is not None:
return result
return None
def get_part_by_custom_span_tuple(self, custom_span_tuple):
span_tuples = self.find_span_components_of_custom_span(custom_span_tuple)
if span_tuples is None:
tex = self.tex_string[slice(*custom_span_tuple)]
raise ValueError(f"Failed to get span of tex: \"{tex}\"")
return self.get_part_by_span_tuples(span_tuples)
def get_parts_by_tex(self, tex):
return VGroup(*[
self.get_part_by_custom_span_tuple(match_obj.span())
for match_obj in re.finditer(re.escape(tex), self.tex_string)
])
def get_part_by_tex(self, tex, index=0):
all_parts = self.get_parts_by_tex(tex)
return all_parts[index]
def set_color_by_tex(self, tex, color):
self.get_parts_by_tex(tex).set_color(color)
return self
def set_color_by_tex_to_color_map(self, tex_to_color_map):
for tex, color in list(tex_to_color_map.items()):
self.set_color_by_tex(tex, color)
return self
def indices_of_part(self, part):
indices = [
i for i, submob in enumerate(self.submobjects)
if submob in part
]
if not indices:
raise ValueError("Failed to find part in tex")
return indices
def indices_of_part_by_tex(self, tex, index=0):
part = self.get_part_by_tex(tex, index=index)
return self.indices_of_part(part)
def slice_of_part(self, part):
indices = self.indices_of_part(part)
return slice(indices[0], indices[-1] + 1)
def slice_of_part_by_tex(self, tex, index=0):
part = self.get_part_by_tex(tex, index=index)
return self.slice_of_part(part)
def index_of_part(self, part):
return self.indices_of_part(part)[0]
def index_of_part_by_tex(self, tex, index=0):
part = self.get_part_by_tex(tex, index=index)
return self.index_of_part(part)
def get_tex(self):
return self.tex_string
def get_all_isolated_substrings(self):
tex_string = self.tex_string
return remove_list_redundancies([
tex_string[slice(*span_tuple)]
for span_tuple in self.tex_spans_dict.keys()
])
def print_tex_strings_of_submobjects(self):
# For debugging
# Work with `index_labels()`
print("\n")
print(f"Submobjects of \"{self.get_tex()}\":")
for i, submob in enumerate(self.submobjects):
print(f"{i}: \"{submob.get_tex()}\"")
print("\n")
class MTexText(MTex):
CONFIG = {
"tex_environment": None,
}

View File

@@ -345,10 +345,7 @@ class VMobjectFromSVGPathstring(VMobject):
self.triangulation = np.load(tris_filepath)
self.needs_new_triangulation = False
else:
self.relative_point = np.array(ORIGIN)
for command, coord_string in self.get_commands_and_coord_strings():
new_points = self.string_to_points(command, coord_string)
self.handle_command(command, new_points)
self.handle_commands()
if self.should_subdivide_sharp_curves:
# For a healthy triangulation later
self.subdivide_sharp_curves()
@@ -370,64 +367,40 @@ class VMobjectFromSVGPathstring(VMobject):
re.split(pattern, self.path_string)[1:]
)
def handle_command(self, command, new_points):
if command.islower():
# Treat it as a relative command
if command == "a":
# Only the last `self.dim` columns refer to points
new_points[:, -self.dim:] += self.relative_point
else:
new_points += self.relative_point
def handle_commands(self):
relative_point = ORIGIN
for command, coord_string in self.get_commands_and_coord_strings():
func, number_types_str = self.command_to_function(command)
upper_command = command.upper()
if upper_command == "Z":
func() # `close_path` takes no arguments
continue
func, n_points = self.command_to_function(command)
command_points = new_points[:n_points]
if command.upper() == "A":
func(*command_points[0][:-self.dim], np.array(command_points[0][-self.dim:]))
else:
func(*command_points)
leftover_points = new_points[n_points:]
number_types = np.array(list(number_types_str))
n_numbers = len(number_types_str)
number_groups = np.array(string_to_numbers(coord_string)).reshape((-1, n_numbers))
# Recursively handle the rest of the points
if len(leftover_points) > 0:
if command.upper() == "M":
# Treat following points as relative line coordinates
command = "l"
if command.islower():
if command == "a":
leftover_points[:, -self.dim:] -= self.relative_point
for numbers in number_groups:
if command.islower():
# Treat it as a relative command
numbers[number_types == "x"] += relative_point[0]
numbers[number_types == "y"] += relative_point[1]
if upper_command == "A":
args = [*numbers[:5], np.array([*numbers[5:7], 0.0])]
elif upper_command == "H":
args = [np.array([numbers[0], relative_point[1], 0.0])]
elif upper_command == "V":
args = [np.array([relative_point[0], numbers[0], 0.0])]
else:
leftover_points -= self.relative_point
self.relative_point = self.get_last_point()
self.handle_command(command, leftover_points)
else:
# Command is over, reset for future relative commands
self.relative_point = self.get_last_point()
args = list(np.hstack((
numbers.reshape((-1, 2)), np.zeros((n_numbers // 2, 1))
)))
func(*args)
relative_point = self.get_last_point()
def string_to_points(self, command, coord_string):
numbers = string_to_numbers(coord_string)
if command.upper() == "A":
# Only the last `self.dim` columns refer to points
# Each "point" returned here has a size of `(5 + self.dim)`
params = np.array(numbers).reshape((-1, 7))
result = np.zeros((params.shape[0], 5 + self.dim))
result[:, :7] = params
return result
if command.upper() in ["H", "V"]:
i = {"H": 0, "V": 1}[command.upper()]
xy = np.zeros((len(numbers), 2))
xy[:, i] = numbers
if command.isupper():
xy[:, 1 - i] = self.relative_point[1 - i]
else:
xy = np.array(numbers).reshape((-1, 2))
result = np.zeros((xy.shape[0], self.dim))
result[:, :2] = xy
return result
def add_elliptical_arc_to(self, rx, ry, x_axis_rotation, large_arc_flag, sweep_flag, point):
"""
In fact, this method only suits 2d VMobjects.
"""
def close_to_zero(a, threshold=1e-5):
return abs(a) < threshold
@@ -536,19 +509,19 @@ class VMobjectFromSVGPathstring(VMobject):
def get_command_to_function_map(self):
"""
Associates svg command to VMobject function, and
the number of arguments it takes in
the types of arguments it takes in
"""
return {
"M": (self.start_new_path, 1),
"L": (self.add_line_to, 1),
"H": (self.add_line_to, 1),
"V": (self.add_line_to, 1),
"C": (self.add_cubic_bezier_curve_to, 3),
"S": (self.add_smooth_cubic_curve_to, 2),
"Q": (self.add_quadratic_bezier_curve_to, 2),
"T": (self.add_smooth_curve_to, 1),
"A": (self.add_elliptical_arc_to, 1),
"Z": (self.close_path, 0),
"M": (self.start_new_path, "xy"),
"L": (self.add_line_to, "xy"),
"H": (self.add_line_to, "x"),
"V": (self.add_line_to, "y"),
"C": (self.add_cubic_bezier_curve_to, "xyxyxy"),
"S": (self.add_smooth_cubic_curve_to, "xyxy"),
"Q": (self.add_quadratic_bezier_curve_to, "xyxy"),
"T": (self.add_smooth_curve_to, "xy"),
"A": (self.add_elliptical_arc_to, "-----xy"),
"Z": (self.close_path, ""),
}
def get_original_path_string(self):

View File

@@ -129,15 +129,18 @@ class SingleStringTex(VMobject):
def balance_braces(self, tex):
"""
Makes Tex resiliant to unmatched { at start
Makes Tex resiliant to unmatched braces
"""
num_lefts, num_rights = [tex.count(char) for char in "{}"]
while num_rights > num_lefts:
tex = "{" + tex
num_lefts += 1
while num_lefts > num_rights:
tex = tex + "}"
num_rights += 1
num_unclosed_brackets = 0
for char in tex:
if char == "{":
num_unclosed_brackets += 1
elif char == "}":
if num_unclosed_brackets == 0:
tex = "{" + tex
else:
num_unclosed_brackets -= 1
tex += num_unclosed_brackets * "}"
return tex
def get_tex(self):

View File

@@ -1,4 +1,3 @@
import copy
import hashlib
import os
import re

View File

@@ -6,6 +6,9 @@ from manimlib.mobject.types.surface import SGroup
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.mobject.geometry import Square
from manimlib.mobject.geometry import Polygon
from manimlib.mobject.geometry import Line
from manimlib.utils.bezier import interpolate
from manimlib.utils.config_ops import digest_config
from manimlib.utils.space_ops import get_norm
from manimlib.utils.space_ops import z_to_vector
@@ -14,9 +17,9 @@ from manimlib.utils.space_ops import compass_directions
class SurfaceMesh(VGroup):
CONFIG = {
"resolution": (21, 21),
"resolution": (21, 11),
"stroke_width": 1,
"normal_nudge": 1e-3,
"normal_nudge": 1e-2,
"depth_test": True,
"flat_stroke": False,
}
@@ -32,8 +35,11 @@ class SurfaceMesh(VGroup):
full_nu, full_nv = uv_surface.resolution
part_nu, part_nv = self.resolution
u_indices = np.linspace(0, full_nu, part_nu).astype(int)
v_indices = np.linspace(0, full_nv, part_nv).astype(int)
# 'indices' are treated as floats. Later, there will be
# an interpolation between the floor and ceiling of these
# indices
u_indices = np.linspace(0, full_nu - 1, part_nu)
v_indices = np.linspace(0, full_nv - 1, part_nv)
points, du_points, dv_points = uv_surface.get_surface_points_and_nudged_points()
normals = uv_surface.get_unit_normals()
@@ -42,12 +48,21 @@ class SurfaceMesh(VGroup):
for ui in u_indices:
path = VMobject()
full_ui = full_nv * ui
path.set_points_smoothly(nudged_points[full_ui:full_ui + full_nv])
low_ui = full_nv * int(math.floor(ui))
high_ui = full_nv * int(math.ceil(ui))
path.set_points_smoothly(interpolate(
nudged_points[low_ui:low_ui + full_nv],
nudged_points[high_ui:high_ui + full_nv],
ui % 1
))
self.add(path)
for vi in v_indices:
path = VMobject()
path.set_points_smoothly(nudged_points[vi::full_nv])
path.set_points_smoothly(interpolate(
nudged_points[int(math.floor(vi))::full_nv],
nudged_points[int(math.ceil(vi))::full_nv],
vi % 1
))
self.add(path)
@@ -208,6 +223,53 @@ class VCube(VGroup):
self.refresh_unit_normal()
class Dodecahedron(VGroup):
CONFIG = {
"fill_color": BLUE_E,
"fill_opacity": 1,
"stroke_width": 1,
"reflectiveness": 0.2,
"gloss": 0.3,
"shadow": 0.2,
"depth_test": True,
}
def init_points(self):
# Star by creating two of the pentagons, meeting
# back to back on the positive x-axis
phi = (1 + math.sqrt(5)) / 2
x, y, z = np.identity(3)
pentagon1 = Polygon(
[phi, 1 / phi, 0],
[1, 1, 1],
[1 / phi, 0, phi],
[1, -1, 1],
[phi, -1 / phi, 0],
)
pentagon2 = pentagon1.copy().stretch(-1, 2, about_point=ORIGIN)
pentagon2.reverse_points()
x_pair = VGroup(pentagon1, pentagon2)
z_pair = x_pair.copy().apply_matrix(np.array([z, -x, -y]).T)
y_pair = x_pair.copy().apply_matrix(np.array([y, z, x]).T)
self.add(*x_pair, *y_pair, *z_pair)
for pentagon in list(self):
pc = pentagon.copy()
pc.apply_function(lambda p: -p)
pc.reverse_points()
self.add(pc)
# # Rotate those two pentagons by all the axis permuations to fill
# # out the dodecahedron
# Id = np.identity(3)
# for i in range(3):
# perm = [j % 3 for j in range(i, i + 3)]
# for b in [1, -1]:
# matrix = b * np.array([Id[0][perm], Id[1][perm], Id[2][perm]])
# self.add(pentagon1.copy().apply_matrix(matrix, about_point=ORIGIN))
# self.add(pentagon2.copy().apply_matrix(matrix, about_point=ORIGIN))
class Prism(Cube):
CONFIG = {
"dimensions": [3, 2, 1]

View File

@@ -2,12 +2,14 @@ import numpy as np
import moderngl
from manimlib.constants import GREY_C
from manimlib.constants import YELLOW
from manimlib.constants import ORIGIN
from manimlib.mobject.types.point_cloud_mobject import PMobject
from manimlib.utils.iterables import resize_preserving_order
DEFAULT_DOT_RADIUS = 0.05
DEFAULT_GLOW_DOT_RADIUS = 0.2
DEFAULT_GRID_HEIGHT = 6
DEFAULT_BUFF_RATIO = 0.5
@@ -17,6 +19,7 @@ class DotCloud(PMobject):
"color": GREY_C,
"opacity": 1,
"radius": DEFAULT_DOT_RADIUS,
"glow_factor": 0,
"shader_folder": "true_dot",
"render_primitive": moderngl.POINTS,
"shader_dtype": [
@@ -36,6 +39,10 @@ class DotCloud(PMobject):
self.data["radii"] = np.zeros((1, 1))
self.set_radius(self.radius)
def init_uniforms(self):
super().init_uniforms()
self.uniforms["glow_factor"] = self.glow_factor
def to_grid(self, n_rows, n_cols, n_layers=1,
buff_ratio=None,
h_buff_ratio=1.0,
@@ -85,6 +92,12 @@ class DotCloud(PMobject):
def get_radius(self):
return self.get_radii().max()
def set_glow_factor(self, glow_factor):
self.uniforms["glow_factor"] = glow_factor
def get_glow_factor(self):
return self.uniforms["glow_factor"]
def compute_bounding_box(self):
bb = super().compute_bounding_box()
radius = self.get_radius()
@@ -98,8 +111,8 @@ class DotCloud(PMobject):
self.set_radii(scale_factor * self.get_radii())
return self
def make_3d(self, gloss=0.5, shadow=0.2):
self.set_gloss(gloss)
def make_3d(self, reflectiveness=0.5, shadow=0.2):
self.set_reflectiveness(reflectiveness)
self.set_shadow(shadow)
self.apply_depth_test()
return self
@@ -112,5 +125,13 @@ class DotCloud(PMobject):
class TrueDot(DotCloud):
def __init__(self, center=ORIGIN, radius=DEFAULT_DOT_RADIUS, **kwargs):
super().__init__(points=[center], radius=radius, **kwargs)
def __init__(self, center=ORIGIN, **kwargs):
super().__init__(points=[center], **kwargs)
class GlowDot(TrueDot):
CONFIG = {
"glow_factor": 2,
"radius": DEFAULT_GLOW_DOT_RADIUS,
"color": YELLOW,
}

View File

@@ -20,7 +20,8 @@ class Surface(Mobject):
"resolution": (101, 101),
"color": GREY,
"opacity": 1.0,
"gloss": 0.3,
"reflectiveness": 0.3,
"gloss": 0.1,
"shadow": 0.4,
"prefered_creation_axis": 1,
# For du and dv steps. Much smaller and numerical error
@@ -161,6 +162,11 @@ class Surface(Mobject):
tri_is[k::3] = tri_is[k::3][indices]
return self
def always_sort_to_camera(self, camera):
self.add_updater(lambda m: m.sort_faces_back_to_front(
camera.get_location() - self.get_center()
))
# For shaders
def get_shader_data(self):
s_points, du_points, dv_points = self.get_surface_points_and_nudged_points()

View File

@@ -12,6 +12,7 @@ from manimlib.utils.bezier import get_smooth_quadratic_bezier_handle_points
from manimlib.utils.bezier import get_smooth_cubic_bezier_handle_points
from manimlib.utils.bezier import get_quadratic_approximation_of_cubic
from manimlib.utils.bezier import interpolate
from manimlib.utils.bezier import inverse_interpolate
from manimlib.utils.bezier import integer_interpolate
from manimlib.utils.bezier import partial_quadratic_bezier_points
from manimlib.utils.color import rgb_to_hex
@@ -74,7 +75,6 @@ class VMobject(Mobject):
self.needs_new_triangulation = True
self.triangulation = np.zeros(0, dtype='i4')
super().__init__(**kwargs)
self.refresh_unit_normal()
def get_group_class(self):
return VGroup
@@ -135,6 +135,10 @@ class VMobject(Mobject):
mob.draw_stroke_behind_fill = background
return self
def set_backstroke(self, color=BLACK, width=3, background=True):
self.set_stroke(color, width, background=background)
return self
def align_stroke_width_data_to_points(self, recurse=True):
for mob in self.get_family(recurse):
mob.data["stroke_width"] = resize_with_interpolation(
@@ -150,6 +154,7 @@ class VMobject(Mobject):
stroke_rgba=None,
stroke_width=None,
stroke_background=True,
reflectiveness=None,
gloss=None,
shadow=None,
recurse=True):
@@ -177,6 +182,8 @@ class VMobject(Mobject):
background=stroke_background,
)
if reflectiveness is not None:
self.set_reflectiveness(reflectiveness, recurse=recurse)
if gloss is not None:
self.set_gloss(gloss, recurse=recurse)
if shadow is not None:
@@ -185,10 +192,11 @@ class VMobject(Mobject):
def get_style(self):
return {
"fill_rgba": self.data['fill_rgba'],
"stroke_rgba": self.data['stroke_rgba'],
"stroke_width": self.data['stroke_width'],
"fill_rgba": self.data['fill_rgba'].copy(),
"stroke_rgba": self.data['stroke_rgba'].copy(),
"stroke_width": self.data['stroke_width'].copy(),
"stroke_background": self.draw_stroke_behind_fill,
"reflectiveness": self.get_reflectiveness(),
"gloss": self.get_gloss(),
"shadow": self.get_shadow(),
}
@@ -218,16 +226,17 @@ class VMobject(Mobject):
return self
def fade(self, darkness=0.5, recurse=True):
factor = 1.0 - darkness
self.set_fill(
opacity=factor * self.get_fill_opacity(),
recurse=False,
)
self.set_stroke(
opacity=factor * self.get_stroke_opacity(),
recurse=False,
)
super().fade(darkness, recurse)
mobs = self.get_family() if recurse else [self]
for mob in mobs:
factor = 1.0 - darkness
mob.set_fill(
opacity=factor * mob.get_fill_opacity(),
recurse=False,
)
mob.set_stroke(
opacity=factor * mob.get_stroke_opacity(),
recurse=False,
)
return self
def get_fill_colors(self):
@@ -277,9 +286,9 @@ class VMobject(Mobject):
return self.get_stroke_opacities()[0]
def get_color(self):
if self.has_stroke():
return self.get_stroke_color()
return self.get_fill_color()
if self.has_fill():
return self.get_fill_color()
return self.get_stroke_color()
def has_stroke(self):
return self.get_stroke_widths().any() and self.get_stroke_opacities().any()
@@ -504,10 +513,10 @@ class VMobject(Mobject):
nppc = self.n_points_per_curve
remainder = len(points) % nppc
points = points[:len(points) - remainder]
return [
return (
points[i:i + nppc]
for i in range(0, len(points), nppc)
]
)
def get_bezier_tuples(self):
return self.get_bezier_tuples_from_points(self.get_points())
@@ -543,12 +552,35 @@ class VMobject(Mobject):
def get_num_curves(self):
return self.get_num_points() // self.n_points_per_curve
def point_from_proportion(self, alpha):
def quick_point_from_proportion(self, alpha):
# Assumes all curves have the same length, so is inaccurate
num_curves = self.get_num_curves()
n, residue = integer_interpolate(0, num_curves, alpha)
curve_func = self.get_nth_curve_function(n)
return curve_func(residue)
def point_from_proportion(self, alpha):
if alpha <= 0:
return self.get_start()
elif alpha >= 1:
return self.get_end()
partials = [0]
for tup in self.get_bezier_tuples():
# Approximate length with straight line from start to end
arclen = get_norm(tup[0] - tup[-1])
partials.append(partials[-1] + arclen)
full = partials[-1]
if full == 0:
return self.get_start()
# First index where the partial lenth is more alpha times the full length
i = next(
(i for i, x in enumerate(partials) if x >= full * alpha),
len(partials) # Default
)
residue = inverse_interpolate(partials[i - 1] / full, partials[i] / full, alpha)
return self.get_nth_curve_function(i - 1)(residue)
def get_anchors_and_handles(self):
"""
returns anchors1, handles, anchors2,
@@ -629,17 +661,19 @@ class VMobject(Mobject):
area_vect = self.get_area_vector()
area = get_norm(area_vect)
if area > 0:
return area_vect / area
normal = area_vect / area
else:
points = self.get_points()
return get_unit_normal(
normal = get_unit_normal(
points[1] - points[0],
points[2] - points[1],
)
self.data["unit_normal"][:] = normal
return normal
def refresh_unit_normal(self):
for mob in self.get_family():
mob.data["unit_normal"][:] = mob.get_unit_normal(recompute=True)
mob.get_unit_normal(recompute=True)
return self
# Alignment
@@ -701,7 +735,7 @@ class VMobject(Mobject):
if len(points) == 1:
return np.repeat(points, nppc * n, 0)
bezier_groups = self.get_bezier_tuples_from_points(points)
bezier_groups = list(self.get_bezier_tuples_from_points(points))
norms = np.array([
get_norm(bg[nppc - 1] - bg[0])
for bg in bezier_groups
@@ -797,7 +831,7 @@ class VMobject(Mobject):
# how to send the points as to the vertex shader.
# First triangles come directly from the points
if normal_vector is None:
normal_vector = self.get_unit_normal()
normal_vector = self.get_unit_normal(recompute=True)
if not self.needs_new_triangulation:
return self.triangulation
@@ -853,7 +887,7 @@ class VMobject(Mobject):
def triggers_refreshed_triangulation(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
old_points = self.get_points()
old_points = self.get_points().copy()
func(self, *args, **kwargs)
if not np.all(self.get_points() == old_points):
self.refresh_unit_normal()
@@ -999,6 +1033,10 @@ class VGroup(VMobject):
super().__init__(**kwargs)
self.add(*vmobjects)
def __add__(self: 'VGroup', other: 'VMobject' or 'VGroup'):
assert(isinstance(other, VMobject))
return self.add(other)
class VectorizedPoint(Point, VMobject):
CONFIG = {
@@ -1010,7 +1048,8 @@ class VectorizedPoint(Point, VMobject):
}
def __init__(self, location=ORIGIN, **kwargs):
super().__init__(**kwargs)
Point.__init__(self, **kwargs)
VMobject.__init__(self, **kwargs)
self.set_points(np.array([location]))

View File

@@ -56,6 +56,8 @@ class Scene(object):
self.time = 0
self.skip_time = 0
self.original_skipping_status = self.skip_animations
if self.start_at_animation_number is not None:
self.skip_animations = True
# Items associated with interaction
self.mouse_point = Point()
@@ -135,7 +137,7 @@ class Scene(object):
for term in ("play", "wait", "add", "remove", "clear", "save_state", "restore"):
local_ns[term] = getattr(self, term)
log.info("Tips: Now the embed iPython terminal is open. But you can't interact with"
" the window directly. To do so, you need to type `touch()` or `self.interact()`")
" the window directly. To do so, you need to type `touch()` or `self.interact()`")
shell(local_ns=local_ns, stack_depth=2)
# End scene when exiting an embed.
raise EndSceneEarlyException()
@@ -184,6 +186,13 @@ class Scene(object):
for mob in self.mobjects
])
def has_time_based_updaters(self):
return any([
sm.has_time_based_updater()
for mob in self.mobjects()
for sm in mob.get_family()
])
# Related to time
def get_time(self):
return self.time
@@ -271,59 +280,54 @@ class Scene(object):
def update_skipping_status(self):
if self.start_at_animation_number is not None:
if self.num_plays == self.start_at_animation_number:
self.stop_skipping()
self.skip_time = self.time
if not self.original_skipping_status:
self.stop_skipping()
if self.end_at_animation_number is not None:
if self.num_plays >= self.end_at_animation_number:
raise EndSceneEarlyException()
def stop_skipping(self):
if self.skip_animations:
self.skip_animations = False
self.skip_time += self.time
self.virtual_animation_start_time = self.time
self.skip_animations = False
# Methods associated with running animations
def get_time_progression(self, run_time, n_iterations=None, override_skip_animations=False):
def get_time_progression(self, run_time, n_iterations=None, desc="", override_skip_animations=False):
if self.skip_animations and not override_skip_animations:
times = [run_time]
return [run_time]
else:
step = 1 / self.camera.frame_rate
times = np.arange(0, run_time, step)
time_progression = ProgressDisplay(
if self.file_writer.has_progress_display:
self.file_writer.set_progress_display_subdescription(desc)
return times
return ProgressDisplay(
times,
total=n_iterations,
leave=self.leave_progress_bars,
ascii=True if platform.system() == 'Windows' else None
ascii=True if platform.system() == 'Windows' else None,
desc=desc,
)
return time_progression
def get_run_time(self, animations):
return np.max([animation.run_time for animation in animations])
def get_animation_time_progression(self, animations):
run_time = self.get_run_time(animations)
time_progression = self.get_time_progression(run_time)
time_progression.set_description("".join([
f"Animation {self.num_plays}: {animations[0]}",
", etc." if len(animations) > 1 else "",
]))
description = f"{self.num_plays} {animations[0]}"
if len(animations) > 1:
description += ", etc."
time_progression = self.get_time_progression(run_time, desc=description)
return time_progression
def get_wait_time_progression(self, duration, stop_condition):
def get_wait_time_progression(self, duration, stop_condition=None):
kw = {"desc": f"{self.num_plays} Waiting"}
if stop_condition is not None:
time_progression = self.get_time_progression(
duration,
n_iterations=-1, # So it doesn't show % progress
override_skip_animations=True
)
time_progression.set_description(
"Waiting for {}".format(stop_condition.__name__)
)
else:
time_progression = self.get_time_progression(duration)
time_progression.set_description(
"Waiting {}".format(self.num_plays)
)
return time_progression
kw["n_iterations"] = -1 # So it doesn't show % progress
kw["override_skip_animations"] = True
return self.get_time_progression(duration, **kw)
def anims_from_play_args(self, *args, **kwargs):
"""
@@ -475,27 +479,18 @@ class Scene(object):
@handle_play_like_call
def wait(self, duration=DEFAULT_WAIT_TIME, stop_condition=None):
self.update_mobjects(dt=0) # Any problems with this?
if self.should_update_mobjects():
self.lock_static_mobject_data()
time_progression = self.get_wait_time_progression(duration, stop_condition)
last_t = 0
for t in time_progression:
dt = t - last_t
last_t = t
self.update_frame(dt)
self.emit_frame()
if stop_condition is not None and stop_condition():
time_progression.close()
break
self.unlock_mobject_data()
elif self.skip_animations:
# Do nothing
return self
else:
self.update_frame(duration)
n_frames = int(duration * self.camera.frame_rate)
for n in range(n_frames):
self.emit_frame()
self.lock_static_mobject_data()
time_progression = self.get_wait_time_progression(duration, stop_condition)
last_t = 0
for t in time_progression:
dt = t - last_t
last_t = t
self.update_frame(dt)
self.emit_frame()
if stop_condition is not None and stop_condition():
time_progression.close()
break
self.unlock_mobject_data()
return self
def wait_until(self, stop_condition, max_time=60):

View File

@@ -5,6 +5,7 @@ import subprocess as sp
import os
import sys
import platform
from tqdm import tqdm as ProgressDisplay
from manimlib.constants import FFMPEG_BIN
from manimlib.utils.config_ops import digest_config
@@ -35,12 +36,15 @@ class SceneFileWriter(object):
"open_file_upon_completion": False,
"show_file_location_upon_completion": False,
"quiet": False,
"total_frames": 0,
"progress_description_len": 60,
}
def __init__(self, scene, **kwargs):
digest_config(self, kwargs)
self.scene = scene
self.writing_process = None
self.has_progress_display = False
self.init_output_directories()
self.init_audio()
@@ -72,10 +76,14 @@ class SceneFileWriter(object):
return path
def get_default_scene_name(self):
if self.file_name is None:
return self.scene.__class__.__name__
else:
return self.file_name
name = str(self.scene)
saan = self.scene.start_at_animation_number
eaan = self.scene.end_at_animation_number
if saan is not None:
name += f"_{saan}"
if eaan is not None:
name += f"_{eaan}"
return name
def get_resolution_directory(self):
pixel_height = self.scene.camera.pixel_height
@@ -205,15 +213,39 @@ class SceneFileWriter(object):
command += [self.temp_file_path]
self.writing_process = sp.Popen(command, stdin=sp.PIPE)
if self.total_frames > 0:
self.progress_display = ProgressDisplay(
range(self.total_frames),
# bar_format="{l_bar}{bar}|{n_fmt}/{total_fmt}",
leave=False,
ascii=True if platform.system() == 'Windows' else None,
dynamic_ncols=True,
)
self.has_progress_display = True
def set_progress_display_subdescription(self, sub_desc):
desc_len = self.progress_description_len
file = os.path.split(self.get_movie_file_path())[1]
full_desc = f"Rendering {file} ({sub_desc})"
if len(full_desc) > desc_len:
full_desc = full_desc[:desc_len - 4] + "...)"
else:
full_desc += " " * (desc_len - len(full_desc))
self.progress_display.set_description(full_desc)
def write_frame(self, camera):
if self.write_to_movie:
raw_bytes = camera.get_raw_fbo_data()
self.writing_process.stdin.write(raw_bytes)
if self.has_progress_display:
self.progress_display.update()
def close_movie_pipe(self):
self.writing_process.stdin.close()
self.writing_process.wait()
self.writing_process.terminate()
if self.has_progress_display:
self.progress_display.close()
shutil.move(self.temp_file_path, self.final_file_path)
def combine_movie_files(self):
@@ -301,7 +333,8 @@ class SceneFileWriter(object):
self.print_file_ready_message(file_path)
def print_file_ready_message(self, file_path):
log.info(f"File ready at {file_path}")
if not self.quiet:
log.info(f"File ready at {file_path}")
def should_open_file(self):
return any([

View File

@@ -5,7 +5,6 @@ class ThreeDScene(Scene):
CONFIG = {
"camera_config": {
"samples": 4,
"anti_alias_width": 0,
}
}

View File

@@ -1,6 +1,6 @@
uniform vec2 frame_shape;
uniform float anti_alias_width;
uniform vec3 camera_center;
uniform vec3 camera_offset;
uniform mat3 camera_rotation;
uniform float is_fixed_in_frame;
uniform float focal_distance;

View File

@@ -13,39 +13,56 @@ vec4 add_light(vec4 color,
vec3 point,
vec3 unit_normal,
vec3 light_coords,
vec3 cam_coords,
float reflectiveness,
float gloss,
float shadow){
if(gloss == 0.0 && shadow == 0.0) return color;
if(reflectiveness == 0.0 && gloss == 0.0 && shadow == 0.0) return color;
float camera_distance = focal_distance;
vec4 result = color;
// Assume everything has already been rotated such that camera is in the z-direction
vec3 to_camera = vec3(0, 0, camera_distance) - point;
vec3 to_light = light_coords - point;
// cam_coords = vec3(0, 0, focal_distance);
vec3 to_camera = normalize(cam_coords - point);
vec3 to_light = normalize(light_coords - point);
// TODO, do we actually want this? It effectively treats surfaces as two-sided
if(dot(to_camera,unit_normal) < 0){
unit_normal *= -1;
}
// Note, this effectively treats surfaces as two-sided
// if(dot(to_camera, unit_normal) < 0) unit_normal *= -1;
float light_to_normal = dot(to_light, unit_normal);
// When unit normal points towards light, brighten
float bright_factor = max(light_to_normal, 0) * reflectiveness;
// For glossy surface, add extra shine if light beam go towards camera
vec3 light_reflection = -to_light + 2 * unit_normal * dot(to_light, unit_normal);
float dot_prod = dot(normalize(light_reflection), normalize(to_camera));
float shine = gloss * exp(-3 * pow(1 - dot_prod, 2));
float dp2 = dot(normalize(to_light), unit_normal);
float darkening = mix(1, max(dp2, 0), shadow);
return vec4(
darkening * mix(color.rgb, vec3(1.0), shine),
color.a
);
float light_to_cam = dot(light_reflection, to_camera);
float shine = gloss * exp(-3 * pow(1 - light_to_cam, 2));
bright_factor += shine;
result.rgb = mix(result.rgb, vec3(1.0), bright_factor);
if (light_to_normal < 0){
// Darken
result.rgb = mix(result.rgb, vec3(0.0), -light_to_normal * shadow);
}
// float darkening = mix(1, max(light_to_normal, 0), shadow);
// return vec4(
// darkening * mix(color.rgb, vec3(1.0), shine),
// color.a
// );
return result;
}
vec4 finalize_color(vec4 color,
vec3 point,
vec3 unit_normal,
vec3 light_coords,
vec3 cam_coords,
float reflectiveness,
float gloss,
float shadow){
///// INSERT COLOR FUNCTION HERE /////
// The line above may be replaced by arbitrary code snippets, as per
// the method Mobject.set_color_by_code
return add_light(color, point, unit_normal, light_coords, gloss, shadow);
return add_light(
color, point, unit_normal, light_coords, cam_coords,
reflectiveness, gloss, shadow
);
}

View File

@@ -1,5 +1,5 @@
// Assumes the following uniforms exist in the surrounding context:
// uniform vec3 camera_center;
// uniform vec3 camera_offset;
// uniform mat3 camera_rotation;
vec3 get_rotated_surface_unit_normal_vector(vec3 point, vec3 du_point, vec3 dv_point){

View File

@@ -1,6 +1,6 @@
// Assumes the following uniforms exist in the surrounding context:
// uniform float is_fixed_in_frame;
// uniform vec3 camera_center;
// uniform vec3 camera_offset;
// uniform mat3 camera_rotation;
vec3 rotate_point_into_frame(vec3 point){
@@ -15,5 +15,5 @@ vec3 position_point_into_frame(vec3 point){
if(bool(is_fixed_in_frame)){
return point;
}
return rotate_point_into_frame(point - camera_center);
return rotate_point_into_frame(point - camera_offset);
}

View File

@@ -1,6 +1,8 @@
#version 330
uniform vec3 light_source_position;
uniform vec3 camera_position;
uniform float reflectiveness;
uniform float gloss;
uniform float shadow;
uniform float focal_distance;
@@ -71,6 +73,8 @@ void main() {
xyz_coords,
vec3(0.0, 0.0, 1.0),
light_source_position,
camera_position,
reflectiveness,
gloss,
shadow
);

View File

@@ -1,6 +1,8 @@
#version 330
uniform vec3 light_source_position;
uniform vec3 camera_position;
uniform float reflectiveness;
uniform float gloss;
uniform float shadow;
uniform float focal_distance;
@@ -75,7 +77,7 @@ vec2 seek_root(vec2 z, vec2[MAX_DEGREE + 1] coefs, int max_steps, out float n_it
}
z = z - step;
}
n_iters -= clamp((threshold - curr_len) / (last_len - curr_len), 0.0, 1.0);
n_iters -= log(curr_len) / log(threshold);
return z;
}
@@ -118,7 +120,7 @@ void main() {
color = colors[i];
}
}
color *= 1.0 + (0.01 * saturation_factor) * (n_iters - 5 * saturation_factor);
color *= 1.0 + (0.01 * saturation_factor) * (n_iters - 2 * saturation_factor);
if(black_for_cycles > 0 && min_dist > CLOSE_ENOUGH){
color = vec4(0.0, 0.0, 0.0, 1.0);
@@ -151,6 +153,8 @@ void main() {
xyz_coords,
vec3(0.0, 0.0, 1.0),
light_source_position,
camera_position,
reflectiveness,
gloss,
shadow
);

View File

@@ -3,7 +3,7 @@
#INSERT camera_uniform_declarations.glsl
in vec4 color;
in float fill_all; // Either 0 or 1e
in float fill_all; // Either 0 or 1
in float uv_anti_alias_width;
in vec3 xyz_coords;

View File

@@ -11,6 +11,8 @@ uniform float focal_distance;
uniform float is_fixed_in_frame;
// Needed for finalize_color
uniform vec3 light_source_position;
uniform vec3 camera_position;
uniform float reflectiveness;
uniform float gloss;
uniform float shadow;
@@ -44,6 +46,8 @@ void emit_vertex_wrapper(vec3 point, int index){
point,
v_global_unit_normal[index],
light_source_position,
camera_position,
reflectiveness,
gloss,
shadow
);

View File

@@ -13,7 +13,9 @@ uniform float flat_stroke;
//Needed for lighting
uniform vec3 light_source_position;
uniform vec3 camera_position;
uniform float joint_type;
uniform float reflectiveness;
uniform float gloss;
uniform float shadow;
@@ -259,6 +261,8 @@ void main() {
xyz_coords,
v_global_unit_normal[index_map[i]],
light_source_position,
camera_position,
reflectiveness,
gloss,
shadow
);

View File

@@ -1,6 +1,8 @@
#version 330
uniform vec3 light_source_position;
uniform vec3 camera_position;
uniform float reflectiveness;
uniform float gloss;
uniform float shadow;
uniform float focal_distance;
@@ -19,6 +21,8 @@ void main() {
xyz_coords,
normalize(v_normal),
light_source_position,
camera_position,
reflectiveness,
gloss,
shadow
);

View File

@@ -4,6 +4,8 @@ uniform sampler2D LightTexture;
uniform sampler2D DarkTexture;
uniform float num_textures;
uniform vec3 light_source_position;
uniform vec3 camera_position;
uniform float reflectiveness;
uniform float gloss;
uniform float shadow;
uniform float focal_distance;
@@ -36,6 +38,8 @@ void main() {
xyz_coords,
normalize(v_normal),
light_source_position,
camera_position,
reflectiveness,
gloss,
shadow
);

View File

@@ -1,10 +1,13 @@
#version 330
uniform vec3 light_source_position;
uniform vec3 camera_position;
uniform float reflectiveness;
uniform float gloss;
uniform float shadow;
uniform float anti_alias_width;
uniform float focal_distance;
uniform float glow_factor;
in vec4 color;
in float radius;
@@ -22,14 +25,23 @@ void main() {
if (signed_dist > 0.5 * anti_alias_width){
discard;
}
vec3 normal = vec3(diff / radius, sqrt(1 - (dist * dist) / (radius * radius)));
frag_color = finalize_color(
color,
vec3(point.xy, 0.0),
normal,
light_source_position,
gloss,
shadow
);
frag_color = color;
if(gloss > 0 || shadow > 0){
vec3 normal = vec3(diff / radius, sqrt(1 - (dist * dist) / (radius * radius)));
frag_color = finalize_color(
frag_color,
vec3(point.xy, 0.0),
normal,
light_source_position,
camera_position,
reflectiveness,
gloss,
shadow
);
}
if(glow_factor > 0){
frag_color.a *= pow(1 - dist / radius, glow_factor);
}
frag_color.a *= smoothstep(0.5, -0.5, signed_dist / anti_alias_width);
}

View File

@@ -10,6 +10,15 @@ from manimlib.constants import OUT
from manimlib.constants import PI
from manimlib.constants import TAU
from manimlib.utils.iterables import adjacent_pairs
from manimlib.utils.simple_functions import clip
def cross(v1, v2):
return [
v1[1] * v2[2] - v1[2] * v2[1],
v1[2] * v2[0] - v1[0] * v2[2],
v1[0] * v2[1] - v1[1] * v2[0]
]
def get_norm(vect):
@@ -147,6 +156,15 @@ def z_to_vector(vector):
return rotation_matrix(angle, axis=axis)
def rotation_between_vectors(v1, v2):
if np.all(np.isclose(v1, v2)):
return np.identity(3)
return rotation_matrix(
angle=angle_between_vectors(v1, v2),
axis=normalize(np.cross(v1, v2))
)
def angle_of_vector(vector):
"""
Returns polar coordinate theta when vector is project on xy plane
@@ -159,8 +177,7 @@ def angle_between_vectors(v1, v2):
Returns the angle between two 3D vectors.
This angle will always be btw 0 and pi
"""
diff = (angle_of_vector(v2) - angle_of_vector(v1)) % TAU
return min(diff, TAU - diff)
return math.acos(clip(np.dot(normalize(v1), normalize(v2)), -1, 1))
def project_along_vector(point, vector):
@@ -186,14 +203,6 @@ def normalize_along_axis(array, axis, fall_back=None):
return array
def cross(v1, v2):
return np.array([
v1[1] * v2[2] - v1[2] * v2[1],
v1[2] * v2[0] - v1[0] * v2[2],
v1[0] * v2[1] - v1[1] * v2[0]
])
def get_unit_normal(v1, v2, tol=1e-6):
v1 = normalize(v1)
v2 = normalize(v2)

View File

@@ -9,6 +9,7 @@ mapbox-earcut
matplotlib
moderngl
moderngl_window
skia-pathops
pydub
pygments
pyyaml

View File

@@ -1,6 +1,6 @@
[metadata]
name = manimgl
version = 1.2.0
version = 1.3.0
author = Grant Sanderson
author_email= grant@3blue1brown.com
description = Animation engine for explanatory math videos
@@ -28,6 +28,7 @@ install_requires =
matplotlib
moderngl
moderngl_window
skia-pathops
pydub
pygments
pyyaml