例えば円とか長方形とかのMobjectに関する、集合の演算みたいなもん。
以下の4つ。
Difference
(図形のクリップ)Exclusion
(いわゆる排他的論理和)Intersection
(言わずもがな、共通部分)Union
(言わずもがな、和集合)
これらはboolean_opsとして、まとめられているようだ。
ぶっちゃけ、本当にやりたかったこと
本当はね、2023年の東工大の第4問の立体をManim使って描けないかなー?と思ってたんだけど。
穴の空いた立体
件の問題がこちら↓引用
空間において, 軸を軸とする半径2の円柱から, かつで表される角柱の内部を取り除いたものをとする. また, を軸のまわりに回転してから軸のまわりに回転したものをとする. との共通部分の体積を求めよ.
この問題を見た瞬間、
(Manim...使えるか!?)
ってなったけど、結局今回のタイトルのブーリアン演算だと、できなかったんだよね。
最初の発想
まあ、大体の人は想像つくかもしれないけど、円柱に穴空けて、なおかつ、共通部分まで考えるんなら、
「Difference
やIntersection
が使えるんじゃね?」
と思ってて、考えが浅はかだったと即出鼻をくじかれたのだけど。
ま、タイトルにもある通り、最初に挙げた4つ
Difference
Exclusion
Intersection
Union
たちは2次元のMobjectにしか対応してないわけで、3次元のMobjectには使えなかったわけだ。
無知ながらの予想
まず、Manimで円柱を描こうとするなら、Cylinder
というクラスがあるので、それが使えるわけだけど、
描画したところで、どうやらSurface
クラスと同様に媒介変数で処理したようなメッシュで描かれる。
Resolutionを落とせば昔のゲームのポリゴンを彷彿とさせるカクカクした面になるから、「複数の面」を作ってVGroup
でまとめてあるってことなんだろう。
Cube
やPrism
なんかもVGroup
を継承してるんで構成要素は「複数の面」(まぁこいつらの場合は6面で固定だが)ってことなんだろう。
一方、例えばIntersection
の項の引数に注目すると、*vmobjects
とあるので、既にVGroup
で複数の面をまとめているのを引数にすると、その「複数の面」の共通部分になってしまって、当然のごとく、演算結果が空になってしまうってことなんじゃないかな?
あ、ちなみにVGroup
でまとめるとリスト形式として1つのVMobjectと扱われて、そのままIntersection
の引数にはできないので、
"*(アスタリスク)"でアンパックして渡さないと駄目みたい。(試そうと思ってエラーでた)
とりあえず、やってみた
試行錯誤しまくった、自分のベストエフォートは以下に示す。
立体に穴を空けるのができないんなら、実際のこの問題の解答よろしく、断面を積み重ねるような形で作成。
穴が空くように長方形を積み重ねてあげたけど、それだけだと、穴がまったくわからんかったので、
別の色で穴部分を表現することに。
from manim import * def construct(self): self.set_camera_orientation(phi=60*DEGREES,theta=60*DEGREES) axes = ( ThreeDAxes( x_range=(-3,3,1), y_range=(-3,3,1), z_range=(-3,3,1), x_length=6, y_length=6, z_length=6 ) ) segment_num = 80 obj = VGroup() hole = VGroup() for i in range(1,segment_num): k = i / segment_num * 2 fullRect = Rectangle( height=np.sqrt(4-k**2)*2*axes.get_y_unit_size(), width=np.sqrt(4-k**2)*2*axes.get_x_unit_size() ) if k < 1: subA = Rectangle( height=np.sqrt(4-k**2)*2*axes.get_y_unit_size(), width=2*axes.get_x_unit_size() ) subB = Rectangle( height=(np.sqrt(2)-k)*2*axes.get_y_unit_size(), width=np.sqrt(4-k**2)*2*axes.get_x_unit_size(), ) elif k < np.sqrt(2): subA = VMobject() subB = Rectangle( height=(np.sqrt(2)-k)*2*axes.get_y_unit_size(), width=np.sqrt(4-k**2)*2*axes.get_x_unit_size(), ) else: subA = VMobject() subB = VMobject() remRect = Union(subA,subB,color=BLACK,stroke_width=1,fill_opacity=0.01) sliceArea = Difference( fullRect,remRect, color=BLUE, fill_opacity=0.01 ).move_to(axes.c2p(0,0,k)) remRect.move_to(axes.c2p(0,0,k)) obj.add(sliceArea) hole.add(remRect) obj.add( obj.copy().rotate_about_origin(180*DEGREES,axis=X_AXIS) ) hole.add( hole.copy().rotate_about_origin(180*DEGREES,axis=X_AXIS) ) obj_edge = VGroup() obj_edge.add( ParametricFunction( lambda k: np.array([ np.sqrt(4-k**2), np.sqrt(4-k**2), k ]), color=BLUE_E, t_range=[-2,2,1/60] ) ) obj_edge.add( obj_edge.copy().rotate_about_origin(90*DEGREES,Z_AXIS) ) obj_edge.add( obj_edge.copy().rotate_about_origin(180*DEGREES,Z_AXIS) ) axes.add(obj,hole,obj_edge) self.add(axes) self.begin_ambient_camera_rotation(1) self.wait(3) self.move_camera(phi=90*DEGREES) self.wait(3) self.stop_ambient_camera_rotation()
時間があれば、YMMとかに挑戦して、ずんだもんが入試問題を解説する動画とか作れたら楽しそうよね。